|
@@ -0,0 +1,439 @@
|
|
|
|
+<template>
|
|
|
|
+ <div class="app-container" v-loading.fullscreen.lock="loading">
|
|
|
|
+ <!-- 直播回放开关区域 -->
|
|
|
|
+
|
|
|
|
+ <!-- 回放内容区域 -->
|
|
|
|
+ <div class="playback-content">
|
|
|
|
+ <div>
|
|
|
|
+ <span>预告内容:</span>
|
|
|
|
+ <el-button class="upload-btn" type="normal" @click="handleUploadVideo">上传视频</el-button>
|
|
|
|
+ <!-- <el-button-->
|
|
|
|
+ <!-- class="upload-btn"-->
|
|
|
|
+ <!-- type="text"-->
|
|
|
|
+ <!-- icon="el-icon-plus"-->
|
|
|
|
+ <!-- @click="handleUploadVideo"-->
|
|
|
|
+ <!-- >上传视频</el-button>-->
|
|
|
|
+ <!-- <span class="upload-tip">上传视频大小不可超过5GB</span>-->
|
|
|
|
+ </div>
|
|
|
|
+ <el-dialog title="添加直播预告" :visible.sync="open" width="900px" append-to-body>
|
|
|
|
+ <el-form ref="form" :model="form" label-width="80px">
|
|
|
|
+ <video-upload
|
|
|
|
+ :type = "1"
|
|
|
|
+ :isPrivate = "isPrivate"
|
|
|
|
+ :fileKey.sync = "form.fileKey"
|
|
|
|
+ :fileSize.sync = "form.fileSize"
|
|
|
|
+ :videoUrl.sync="videoUrl"
|
|
|
|
+ :fileName.sync="form.fileName"
|
|
|
|
+ :line_2.sync="form.lineTwo"
|
|
|
|
+ :line_1.sync="form.lineOne"
|
|
|
|
+ :thumbnail.sync="form.thumbnail"
|
|
|
|
+ :uploadType.sync="form.uploadType"
|
|
|
|
+ :isTranscode.sync="form.isTranscode"
|
|
|
|
+ :transcodeFileKey.sync="form.transcodeFileKey"
|
|
|
|
+ @video-duration="handleVideoDuration"
|
|
|
|
+ @change="handleVideoChange"
|
|
|
|
+ ref="videoUpload"
|
|
|
|
+ append-to-body
|
|
|
|
+ />
|
|
|
|
+ </el-form>
|
|
|
|
+ <div slot="footer" class="dialog-footer">
|
|
|
|
+ <el-button type="primary" @click="submitForm">确 定</el-button>
|
|
|
|
+ <el-button @click="open = false">取 消</el-button>
|
|
|
|
+ </div>
|
|
|
|
+ </el-dialog>
|
|
|
|
+
|
|
|
|
+ <!-- 视频列表 -->
|
|
|
|
+ <el-table
|
|
|
|
+ :data="videoList"
|
|
|
|
+ border
|
|
|
|
+ style="width: 100%; margin-top: 10px"
|
|
|
|
+ >
|
|
|
|
+ <el-table-column prop="duration" label="时长" />
|
|
|
|
+ <el-table-column prop="updateTime" label="更新时间" />
|
|
|
|
+ <el-table-column label="视频地址(可点击查看)" prop="videoUrl" >
|
|
|
|
+ <template slot-scope="scope">
|
|
|
|
+ <el-tooltip
|
|
|
|
+ :content="scope.row.videoUrl"
|
|
|
|
+ placement="top"
|
|
|
|
+ effect="dark"
|
|
|
|
+ >
|
|
|
|
+ <a
|
|
|
|
+ :href="scope.row.videoUrl"
|
|
|
|
+ target="_blank"
|
|
|
|
+ class="video-url-container"
|
|
|
|
+ rel="noopener noreferrer"
|
|
|
|
+ >
|
|
|
|
+ {{
|
|
|
|
+ scope.row.videoUrl.length > 32
|
|
|
|
+ ? scope.row.videoUrl.substring(0, 32) + '...'
|
|
|
|
+ : scope.row.videoUrl
|
|
|
|
+ }}
|
|
|
|
+ <i class="el-icon-external-link video-url-icon"></i>
|
|
|
|
+ </a>
|
|
|
|
+ </el-tooltip>
|
|
|
|
+ </template>
|
|
|
|
+ </el-table-column>
|
|
|
|
+ </el-table>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+</template>
|
|
|
|
+
|
|
|
|
+<script>
|
|
|
|
+import { getLive,} from '@/api/live/live'
|
|
|
|
+import {getLiveVideoByLiveId,addLiveVideo} from '@/api/live/liveVideo'
|
|
|
|
+import VideoUpload from "@/components/LiveVideoUpload/index.vue";
|
|
|
|
+
|
|
|
|
+export default {
|
|
|
|
+ name: "Preview",
|
|
|
|
+ components: {VideoUpload},
|
|
|
|
+ data() {
|
|
|
|
+ return {
|
|
|
|
+ loading: true,
|
|
|
|
+ videoList: [
|
|
|
|
+
|
|
|
|
+ ], // 视频列表数据,实际需从接口获取
|
|
|
|
+ liveId: null,
|
|
|
|
+ liveInfo: null,
|
|
|
|
+ isPrivate:null,
|
|
|
|
+ // 表单参数
|
|
|
|
+ form: {
|
|
|
|
+ uploadType: 1,
|
|
|
|
+ isTranscode:0,
|
|
|
|
+ transcodeFileKey:null
|
|
|
|
+ },
|
|
|
|
+ videoUrl:"",
|
|
|
|
+ // 是否显示弹出层
|
|
|
|
+ open: false,
|
|
|
|
+
|
|
|
|
+ };
|
|
|
|
+ },
|
|
|
|
+ watch: {
|
|
|
|
+ // 监听路由的 query 参数变化
|
|
|
|
+ '$route.query': {
|
|
|
|
+ handler(newQuery) {
|
|
|
|
+ if (this.$route.params.liveId) {
|
|
|
|
+ this.liveId = this.$route.params.liveId;
|
|
|
|
+ }else {
|
|
|
|
+ this.liveId = this.$route.query.liveId;
|
|
|
|
+ }
|
|
|
|
+ if(this.liveId == null) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ this.getLive();
|
|
|
|
+ },
|
|
|
|
+ // 初始化时立即执行一次
|
|
|
|
+ immediate: true
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ created() {
|
|
|
|
+ // if (this.$route.params.liveId) {
|
|
|
|
+ // this.liveId = this.$route.params.liveId;
|
|
|
|
+ // }else {
|
|
|
|
+ // this.liveId = this.$route.query.liveId;
|
|
|
|
+ // }
|
|
|
|
+ // if(this.liveId == null) {
|
|
|
|
+ // this.$message.error("页面错误,请联系管理员");
|
|
|
|
+ // return;
|
|
|
|
+ // }
|
|
|
|
+ // this.getLive();
|
|
|
|
+ },
|
|
|
|
+ methods: {
|
|
|
|
+ submitForm() {
|
|
|
|
+ this.open = false;
|
|
|
|
+ const doParam = {
|
|
|
|
+ liveId: this.liveId,
|
|
|
|
+ videoUrl: this.videoUrl,
|
|
|
|
+ videoType: 1,
|
|
|
|
+ duration: this.form.duration,
|
|
|
|
+ }
|
|
|
|
+ addLiveVideo(doParam).then(response => {
|
|
|
|
+ if (response.code == 200) {
|
|
|
|
+ this.$message.success("上传成功");
|
|
|
|
+ this.videoList = []
|
|
|
|
+ this.videoList.push(doParam)
|
|
|
|
+ } else {
|
|
|
|
+ this.$message.warning(response.msg);
|
|
|
|
+ }
|
|
|
|
+ this.$refs.form.resetFields();
|
|
|
|
+ })
|
|
|
|
+ },
|
|
|
|
+ handleVideoDuration(duration) {
|
|
|
|
+ this.form.duration = duration;
|
|
|
|
+ },
|
|
|
|
+ handleVideoChange(videoUrl,lineOne){
|
|
|
|
+ this.videoUrl = videoUrl;
|
|
|
|
+ this.form.videoUrl = videoUrl;
|
|
|
|
+ console.log(this.videoUrl)
|
|
|
|
+ },
|
|
|
|
+ getLiveVideo() {
|
|
|
|
+ getLiveVideoByLiveId(this.liveId).then(res => {
|
|
|
|
+ let dataEntity =
|
|
|
|
+ {
|
|
|
|
+ duration: "00:00",
|
|
|
|
+ status: "回放中",
|
|
|
|
+ updateTime: "2025-09-08 14:28:24",
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ //将秒数转为时分秒
|
|
|
|
+ dataEntity.duration = this.convertSeconds(res.data.duration);
|
|
|
|
+ dataEntity.updateTime = res.data.updateTime;
|
|
|
|
+ dataEntity.videoUrl = res.data.videoUrl;
|
|
|
|
+ dataEntity.videoName = this.extractFileName(dataEntity.videoUrl)
|
|
|
|
+
|
|
|
|
+ this.videoList.push(dataEntity);
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ /**
|
|
|
|
+ * 提取文件名称核心方法
|
|
|
|
+ * 逻辑:通过split('/')分割路径,取最后一个非空元素作为文件名称
|
|
|
|
+ */
|
|
|
|
+ extractFileName(data) {
|
|
|
|
+ try {
|
|
|
|
+ this.errorMsg = '';
|
|
|
|
+ const trimmedUrl = data.trim();
|
|
|
|
+ // 用'/'分割路径,过滤空字符串(避免路径末尾有'/'导致的空元素)
|
|
|
|
+ const pathSegments = trimmedUrl.split('/').filter(segment => segment);
|
|
|
|
+ if (pathSegments.length === 0) {
|
|
|
|
+ throw new Error('输入的路径格式无效,请检查后重新输入');
|
|
|
|
+ }
|
|
|
|
+ // 最后一个分段即为文件名称
|
|
|
|
+ const fileName = pathSegments[pathSegments.length - 1];
|
|
|
|
+ // 简单校验是否为常见视频文件格式(可选,根据需求调整)
|
|
|
|
+ const videoExtensions = ['mp4', 'mov', 'avi', 'flv', 'mkv'];
|
|
|
|
+ const fileExtension = fileName.split('.').pop()?.toLowerCase();
|
|
|
|
+ if (!fileExtension || !videoExtensions.includes(fileExtension)) {
|
|
|
|
+ this.errorMsg = '提示:提取到的文件可能不是常见视频格式,请注意校验';
|
|
|
|
+ }
|
|
|
|
+ return fileName;
|
|
|
|
+ } catch (err) {
|
|
|
|
+ this.errorMsg = err.message;
|
|
|
|
+ return '';
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ convertSeconds(data) {
|
|
|
|
+ // 确保输入是有效的数字
|
|
|
|
+ const totalSeconds = Math.max(0, parseInt(data) || 0);
|
|
|
|
+
|
|
|
|
+ // 计算时、分、秒
|
|
|
|
+ const hours = Math.floor(totalSeconds / 3600);
|
|
|
|
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
|
|
+ const seconds = totalSeconds % 60;
|
|
|
|
+
|
|
|
|
+ // 格式化每个部分为两位数
|
|
|
|
+ return `${this.padWithZero(hours)}:${this.padWithZero(minutes)}:${this.padWithZero(seconds)}`;
|
|
|
|
+ },
|
|
|
|
+ padWithZero(num) {
|
|
|
|
+ // 将数字转换为两位数格式
|
|
|
|
+ return num.toString().padStart(2, '0');
|
|
|
|
+ },
|
|
|
|
+ getLive() {
|
|
|
|
+ getLive(this.liveId).then(res => {
|
|
|
|
+ this.liveInfo = res.data;
|
|
|
|
+ if (res.data.liveType == 1) {
|
|
|
|
+ this.getLiveVideo();
|
|
|
|
+ }
|
|
|
|
+ this.loading = false;
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ resetForm() {
|
|
|
|
+ this.replayForm={
|
|
|
|
+ isPlaybackOpen: false, // 直播回放开关状态
|
|
|
|
+ playbackMode: "1", // 回放模式,默认模式一
|
|
|
|
+ validityType: "days", // 回放有效期类型,默认天数
|
|
|
|
+ validDays: 7, // 有效天数
|
|
|
|
+ isSpeedAllowed: "1", // 是否允许倍速播放,默认允许
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ // 上传视频处理
|
|
|
|
+ handleUploadVideo() {
|
|
|
|
+ // 模拟上传视频逻辑,实际需调用上传组件或接口
|
|
|
|
+ if (this.liveInfo.liveType == 1) {
|
|
|
|
+ this.open = true;
|
|
|
|
+ }else{
|
|
|
|
+ this.$message.error("仅直播可上传预告片段");
|
|
|
|
+ }
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+</script>
|
|
|
|
+
|
|
|
|
+<style scoped>
|
|
|
|
+.live-playback-setting {
|
|
|
|
+ font-family: "Microsoft Yahei", sans-serif;
|
|
|
|
+ color: #333;
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ padding: 20px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.switch-area {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+.switch-area .switch-label {
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+ font-size: 14px;
|
|
|
|
+}
|
|
|
|
+.switch-area .switch-desc {
|
|
|
|
+ color: #999;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.playback-mode {
|
|
|
|
+ background-color: #f9fafc;
|
|
|
|
+ padding: 15px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+.playback-mode .mode-title {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+}
|
|
|
|
+.playback-mode .mode-option {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
+}
|
|
|
|
+.playback-mode .mode-option .el-radio {
|
|
|
|
+ margin-left: 2%;
|
|
|
|
+}
|
|
|
|
+.playback-mode .mode-option label {
|
|
|
|
+ font-size: 13px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.validity-period {
|
|
|
|
+ background-color: #f9fafc;
|
|
|
|
+ padding: 15px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
+}
|
|
|
|
+.validity-period .period-title {
|
|
|
|
+ font-size: 14px;
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
+}
|
|
|
|
+.validity-period .period-option {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
+}
|
|
|
|
+.validity-period .period-option .el-radio {
|
|
|
|
+ margin-left: 2%;
|
|
|
|
+}
|
|
|
|
+.validity-period .period-option label {
|
|
|
|
+ font-size: 13px;
|
|
|
|
+}
|
|
|
|
+.validity-period .period-desc {
|
|
|
|
+ color: #999;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-top: 5px;
|
|
|
|
+ margin-left: 2%;
|
|
|
|
+}
|
|
|
|
+.validity-period .day-input {
|
|
|
|
+ width: 60px;
|
|
|
|
+ height: 28px;
|
|
|
|
+ border: 1px solid #ddd;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ padding: 0 8px;
|
|
|
|
+ margin: 0 5px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.speed-play {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+ margin-top: 15px;
|
|
|
|
+}
|
|
|
|
+.speed-play .speed-label {
|
|
|
|
+ font-size: 13px;
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+.speed-play .el-radio {
|
|
|
|
+ margin-right: 5px;
|
|
|
|
+}
|
|
|
|
+.speed-play .speed-desc {
|
|
|
|
+ color: #999;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.pro-tag {
|
|
|
|
+ display: inline-block;
|
|
|
|
+ background-color: #007bff;
|
|
|
|
+ color: #fff;
|
|
|
|
+ font-size: 10px;
|
|
|
|
+ padding: 2px 5px;
|
|
|
|
+ border-radius: 3px;
|
|
|
|
+ margin-left: 5px;
|
|
|
|
+ vertical-align: middle;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.playback-content {
|
|
|
|
+ margin-top: 20px;
|
|
|
|
+}
|
|
|
|
+.playback-content .upload-btn {
|
|
|
|
+ display: inline-block;
|
|
|
|
+ background-color: #fff;
|
|
|
|
+ border: 1px dashed #ddd;
|
|
|
|
+ color: #007bff;
|
|
|
|
+ padding: 8px 15px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ font-size: 13px;
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
+}
|
|
|
|
+.playback-content .upload-btn:hover {
|
|
|
|
+ border-color: #007bff;
|
|
|
|
+}
|
|
|
|
+.playback-content .upload-tip {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #999;
|
|
|
|
+ margin-left: 10px;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+.video-item {
|
|
|
|
+ display: flex;
|
|
|
|
+ align-items: center;
|
|
|
|
+}
|
|
|
|
+.video-cover {
|
|
|
|
+ width: 80px;
|
|
|
|
+ height: 45px;
|
|
|
|
+ border-radius: 4px;
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+.video-info .video-name {
|
|
|
|
+ font-size: 13px;
|
|
|
|
+ margin-bottom: 3px;
|
|
|
|
+}
|
|
|
|
+.video-info .video-desc {
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ color: #999;
|
|
|
|
+}
|
|
|
|
+.video-duration,
|
|
|
|
+.video-source,
|
|
|
|
+.video-status,
|
|
|
|
+.video-update-time {
|
|
|
|
+ color: #666;
|
|
|
|
+}
|
|
|
|
+.video-status .status-dot {
|
|
|
|
+ display: inline-block;
|
|
|
|
+ width: 8px;
|
|
|
|
+ height: 8px;
|
|
|
|
+ background-color: #007bff;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ margin-right: 5px;
|
|
|
|
+ vertical-align: middle;
|
|
|
|
+}
|
|
|
|
+.video-operation .el-button {
|
|
|
|
+ color: #007bff;
|
|
|
|
+ font-size: 12px;
|
|
|
|
+ margin-right: 10px;
|
|
|
|
+}
|
|
|
|
+.video-url-container {
|
|
|
|
+ cursor: pointer;
|
|
|
|
+ white-space: nowrap;
|
|
|
|
+ overflow: hidden;
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
+ max-width: 100%;
|
|
|
|
+}
|
|
|
|
+</style>
|