|
|
@@ -0,0 +1,519 @@
|
|
|
+<template>
|
|
|
+ <div class="app-container" v-loading.fullscreen.lock="loading">
|
|
|
+ <!-- 直播回放开关区域 -->
|
|
|
+
|
|
|
+ <div class="switch-area">
|
|
|
+ <span class="switch-label">直播回放:</span>
|
|
|
+ <el-switch
|
|
|
+ v-model="replayForm.isPlaybackOpen"
|
|
|
+ @change="handlePlaybackSwitch"
|
|
|
+ />
|
|
|
+ <span class="switch-desc" style="color: #9c9c9c;margin-left: 10px">开启回放,直播结束后学员可查看回放视频</span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 回放模式区域 -->
|
|
|
+ <div class="playback-mode" v-if="replayForm.isPlaybackOpen">
|
|
|
+ <div class="mode-title">回放模式:</div>
|
|
|
+ <div class="mode-option">
|
|
|
+ <el-radio v-model="replayForm.playbackMode" label="1">模式一:以直播间全部直播视频作为回看</el-radio>
|
|
|
+ </div>
|
|
|
+<!-- <div class="mode-option">-->
|
|
|
+<!-- <el-radio v-model="playbackMode" label="2">模式二:以直播间最后一次直播视频作为回看</el-radio>-->
|
|
|
+<!-- </div>-->
|
|
|
+<!-- <div class="mode-option">-->
|
|
|
+<!-- <el-radio v-model="playbackMode" label="3">模式三:自主选择本直播间或素材库任一视频作为回看</el-radio>-->
|
|
|
+<!-- </div>-->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 回放有效期区域 -->
|
|
|
+ <div class="validity-period" v-if="replayForm.isPlaybackOpen">
|
|
|
+ <div class="period-title">回放有效期:</div>
|
|
|
+ <div class="period-option">
|
|
|
+ <el-radio @change="updateForm" v-model="replayForm.validityType" label="permanent">永久有效</el-radio>
|
|
|
+ </div>
|
|
|
+ <div class="period-option">
|
|
|
+ <el-radio v-model="replayForm.validityType" label="days">
|
|
|
+ 直播结束后
|
|
|
+ <el-input
|
|
|
+ v-model.number="replayForm.validDays"
|
|
|
+ type="number"
|
|
|
+ @change="updateForm"
|
|
|
+ :min="1"
|
|
|
+ placeholder="天数"
|
|
|
+ />
|
|
|
+ 天有效
|
|
|
+ <el-tooltip
|
|
|
+ content="以直播实际结束时间为准"
|
|
|
+ placement="top"
|
|
|
+ effect="light"
|
|
|
+ >
|
|
|
+ <i class="el-icon-question" style="color: #999; margin-left: 5px" />
|
|
|
+ </el-tooltip> </el-radio>
|
|
|
+ </div>
|
|
|
+ <div class="period-desc">
|
|
|
+ 到期后,学员无法观看回放内容
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 倍速播放区域 -->
|
|
|
+ <div class="speed-play">
|
|
|
+ <div class="speed-label">倍速播放/快进:</div>
|
|
|
+ <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="1">允许</el-radio>
|
|
|
+ <el-radio @change="updateForm" v-model="replayForm.isSpeedAllowed" label="0">禁止</el-radio>
|
|
|
+ <span class="speed-desc">禁止时,课程未学完学员不可倍速播放、拖动进度条</span>
|
|
|
+ <span class="pro-tag">专业版</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 回放内容区域 -->
|
|
|
+ <div class="playback-content">
|
|
|
+ <div>
|
|
|
+ <span>回放内容:</span>
|
|
|
+<!-- <el-button-->
|
|
|
+<!-- class="upload-btn"-->
|
|
|
+<!-- type="text"-->
|
|
|
+<!-- icon="el-icon-plus"-->
|
|
|
+<!-- @click="handleUploadVideo"-->
|
|
|
+<!-- >上传视频</el-button>-->
|
|
|
+<!-- <span class="upload-tip">上传视频大小不可超过5GB</span>-->
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 视频列表 -->
|
|
|
+ <el-table
|
|
|
+ :data="videoList"
|
|
|
+ border
|
|
|
+ style="width: 100%; margin-top: 10px"
|
|
|
+ >
|
|
|
+ <el-table-column prop="videoName" label="视频名称">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <div class="video-item">
|
|
|
+<!-- <img-->
|
|
|
+<!-- :src="scope.row.videoCover"-->
|
|
|
+<!-- class="video-cover"-->
|
|
|
+<!-- alt="视频封面"-->
|
|
|
+<!-- />-->
|
|
|
+ <div class="video-info">
|
|
|
+ <div class="video-name">{{ scope.row.videoName }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="duration" label="时长" />
|
|
|
+ <el-table-column prop="status" label="状态">
|
|
|
+ <template slot-scope="scope">
|
|
|
+ <span v-if="scope.row.status === '回放中'">
|
|
|
+ <span class="status-dot" />{{ scope.row.status }}
|
|
|
+ </span>
|
|
|
+ <span v-else>{{ scope.row.status }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <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-column label="操作" prop="videoUrl" >-->
|
|
|
+<!-- <template slot-scope="scope">-->
|
|
|
+
|
|
|
+<!-- <!– <el-button–>-->
|
|
|
+<!-- <!– type="text"–>-->
|
|
|
+<!-- <!– size="small"–>-->
|
|
|
+<!-- <!– @click="handleCut(scope.row)"–>-->
|
|
|
+<!-- <!– >剪切</el-button>–>-->
|
|
|
+<!-- <!– <el-button–>-->
|
|
|
+<!-- <!– type="text"–>-->
|
|
|
+<!-- <!– size="small"–>-->
|
|
|
+<!-- <!– @click="handleGenerateAiSummary(scope.row)"–>-->
|
|
|
+<!-- <!– >生成AI速览</el-button>–>-->
|
|
|
+<!-- <!– <el-button–>-->
|
|
|
+<!-- <!– type="text"–>-->
|
|
|
+<!-- <!– size="small"–>-->
|
|
|
+<!-- <!– @click="handleGenerateSubtitle(scope.row)"–>-->
|
|
|
+<!-- <!– >生成字幕</el-button>–>-->
|
|
|
+<!-- <!– <el-button–>-->
|
|
|
+<!-- <!– type="text"–>-->
|
|
|
+<!-- <!– size="small"–>-->
|
|
|
+<!-- <!– @click="handlePlaybackSegment(scope.row)"–>-->
|
|
|
+<!-- <!– >回放片段</el-button>–>-->
|
|
|
+<!-- </template>-->
|
|
|
+<!-- </el-table-column>-->
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script>
|
|
|
+import {getLive, updateLive,} from '@/api/live/live'
|
|
|
+import {getLiveVideoByLiveId,} from '@/api/live/liveVideo'
|
|
|
+
|
|
|
+export default {
|
|
|
+ name: "LiveReplay",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ loading: true,
|
|
|
+ replayForm:{
|
|
|
+ isPlaybackOpen: false, // 直播回放开关状态
|
|
|
+ playbackMode: "1", // 回放模式,默认模式一
|
|
|
+ validityType: "days", // 回放有效期类型,默认天数
|
|
|
+ validDays: 7, // 有效天数
|
|
|
+ isSpeedAllowed: "1", // 是否允许倍速播放,默认允许
|
|
|
+ },
|
|
|
+ videoList: [
|
|
|
+
|
|
|
+ ], // 视频列表数据,实际需从接口获取
|
|
|
+ liveId: null,
|
|
|
+ liveInfo: null,
|
|
|
+
|
|
|
+ };
|
|
|
+ },
|
|
|
+ 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: {
|
|
|
+ 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.liveConfig){
|
|
|
+ this.replayForm = JSON.parse(res.data.liveConfig);
|
|
|
+ this.getLiveVideo();
|
|
|
+ }else{
|
|
|
+ this.resetForm();
|
|
|
+ }
|
|
|
+ this.loading = false;
|
|
|
+ });
|
|
|
+ },
|
|
|
+ resetForm() {
|
|
|
+ this.replayForm={
|
|
|
+ isPlaybackOpen: false, // 直播回放开关状态
|
|
|
+ playbackMode: "1", // 回放模式,默认模式一
|
|
|
+ validityType: "days", // 回放有效期类型,默认天数
|
|
|
+ validDays: 7, // 有效天数
|
|
|
+ isSpeedAllowed: "1", // 是否允许倍速播放,默认允许
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 直播回放开关变更处理
|
|
|
+ handlePlaybackSwitch(val) {
|
|
|
+ if (this.liveInfo.liveType != 1 && this.liveInfo.liveType != 3) {
|
|
|
+ this.replayForm.isPlaybackOpen = !val;
|
|
|
+ this.$message.error("直播回放开关仅支持直播");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!this.liveInfo.finishTime) {
|
|
|
+ this.replayForm.isPlaybackOpen = !val;
|
|
|
+ this.$message.error("直播未结束");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ this.replayForm.isPlaybackOpen = val;
|
|
|
+ this.updateForm()
|
|
|
+ },
|
|
|
+ updateForm(){
|
|
|
+ let param = {
|
|
|
+ liveId: this.liveId,
|
|
|
+ startTime:this.liveInfo.startTime,
|
|
|
+ finishTime:this.liveInfo.finishTime,
|
|
|
+ liveConfig: JSON.stringify(this.replayForm)
|
|
|
+ };
|
|
|
+ updateLive(param).then(res => {
|
|
|
+ if (res.code != 200) {
|
|
|
+ this.$message.error(res.msg);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 上传视频处理
|
|
|
+ handleUploadVideo() {
|
|
|
+ // 模拟上传视频逻辑,实际需调用上传组件或接口
|
|
|
+ this.$message.info("点击了上传视频按钮,实际需对接上传逻辑");
|
|
|
+ },
|
|
|
+ // 剪切视频处理
|
|
|
+ handleCut(video) {
|
|
|
+ this.$message.info(`点击了剪切视频,视频名称:${video.videoName}`);
|
|
|
+ // 实际剪切逻辑
|
|
|
+ },
|
|
|
+ // 生成AI速览处理
|
|
|
+ handleGenerateAiSummary(video) {
|
|
|
+ this.$message.info(`点击了生成AI速览,视频名称:${video.videoName}`);
|
|
|
+ // 实际生成AI速览逻辑
|
|
|
+ },
|
|
|
+ // 生成字幕处理
|
|
|
+ handleGenerateSubtitle(video) {
|
|
|
+ this.$message.info(`点击了生成字幕,视频名称:${video.videoName}`);
|
|
|
+ // 实际生成字幕逻辑
|
|
|
+ },
|
|
|
+ // 回放片段处理
|
|
|
+ handlePlaybackSegment(video) {
|
|
|
+ this.$message.info(`点击了回放片段,视频名称:${video.videoName}`);
|
|
|
+ // 实际回放片段逻辑
|
|
|
+ },
|
|
|
+ },
|
|
|
+};
|
|
|
+</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>
|