ai视频二期功能提交-编辑ai翻译

This commit is contained in:
sunli_tydic
2025-12-17 18:04:08 +08:00
parent 04e163b63e
commit 423275ed08
3 changed files with 379 additions and 38 deletions

View File

@@ -92,7 +92,8 @@ const getTextDetail = function(data) {
* { * {
"videoId": "VIDEO_001", "videoId": "VIDEO_001",
"language": "zh-CN", "language": "zh-CN",
"vttContent": "dddd" "subtitleData": "dddd",
"updateStatus": 1, // 0-(重新生成)更新当前语种 1-同步更新
} }
*/ */
const updateText = function(data) { const updateText = function(data) {

View File

@@ -4,7 +4,7 @@
<div class="left-title">{{courseInfo.name}}</div> <div class="left-title">{{courseInfo.name}}</div>
<ul class="ai-list" > <ul class="ai-list" >
<template v-for="c in chapterList"> <template v-for="c in chapterList">
<li class="ai-item" v-for="(item, index) in c.children" @click="handleSelectVideo(item, index+1)" :class="{'active': currentVideo.id === item.id}" :key="index"> <li class="ai-item" v-for="(item, index) in c.children" @click="handleSelectVideo(item, index+1)" :class="{'active': currentVideo.id === item.id}" :key="item.id">
<div class="ai-item-title">{{item.chapterName}}-{{'视频' + (index + 1)}}</div> <div class="ai-item-title">{{item.chapterName}}-{{'视频' + (index + 1)}}</div>
</li> </li>
</template> </template>
@@ -34,7 +34,7 @@
<div class="opera-title"> <div class="opera-title">
<h4>课程摘要</h4> <h4>课程摘要</h4>
<div class="opera-btn"> <div class="opera-btn">
<el-button v-if="type == 1" type="primary" plain round size="mini" @click="retrySummaryTxt">重新生成</el-button> <el-button v-if="type == 1 || type == 3" type="primary" plain round size="mini" @click="retrySummaryTxt">重新生成</el-button>
<el-button v-if="type == 1" type="primary" plain round size="mini" @click="type = 4">编辑</el-button> <el-button v-if="type == 1" type="primary" plain round size="mini" @click="type = 4">编辑</el-button>
<el-button v-if="type == 4" plain round size="mini" @click="type = 1">取消</el-button> <el-button v-if="type == 4" plain round size="mini" @click="type = 1">取消</el-button>
<el-button v-if="type == 4" type="primary" plain round size="mini" @click="updateSummary">确认</el-button> <el-button v-if="type == 4" type="primary" plain round size="mini" @click="updateSummary">确认</el-button>
@@ -84,6 +84,14 @@ export default {
blobId: '', blobId: '',
blobUrl: '', blobUrl: '',
videoSummary: '', videoSummary: '',
summaryTimer: null
}
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.summaryTimer) {
clearInterval(this.summaryTimer);
this.summaryTimer = null;
} }
}, },
created() { created() {
@@ -181,7 +189,36 @@ export default {
courseId: this.courseId, courseId: this.courseId,
}) })
.then((rs) => { .then((rs) => {
// 任务状态 courseSummaryTaskStatus 0-未开始 1-执行中 2-执行完成 3-执行失败 4-重试中
// 页面状态 type 1: 正常 2: 生成中 3: 错误 4: 编辑中
let taskStatus = rs.data.courseSummaryTaskStatus;
// 统一清除可能存在的旧定时器
if (this.summaryTimer) {
clearInterval(this.summaryTimer);
this.summaryTimer = null;
}
if (taskStatus == 1 || taskStatus == 4) {
this.type = 2;
// 设置新定时器每隔1秒调用一次
this.summaryTimer = setInterval(() => {
this.getCourseSummary();
}, 5000);
} else if (taskStatus == 3) {
this.type = 3;
} else {
this.type = 1;
}
this.aiAbstract = rs.data.summaryContent || ''; this.aiAbstract = rs.data.summaryContent || '';
})
.catch(() => {
// 请求失败时也清除定时器
if (this.summaryTimer) {
clearInterval(this.summaryTimer);
this.summaryTimer = null;
}
}) })
}, },
createPlayUrl(fid, u) { createPlayUrl(fid, u) {

View File

@@ -4,8 +4,8 @@
<div class="left-title">{{courseInfo.name}}</div> <div class="left-title">{{courseInfo.name}}</div>
<ul class="ai-list" > <ul class="ai-list" >
<template v-for="c in chapterList"> <template v-for="c in chapterList">
<li class="ai-item" v-for="(item, index) in c.children" @click="handleSelectVideo(item, index+1)" :class="{'active': currentVideo.id === item.id}" :key="index"> <li class="ai-item" v-for="item in c.children" @click="handleSelectVideo(item)" :class="{'active': currentVideo.id === item.id}" :key="item.id">
<div class="ai-item-title">{{item.chapterName}}-{{'视频' + (index + 1)}}</div> <div class="ai-item-title">{{item.chapterName}}-{{'视频' + item.videoIndex}}</div>
</li> </li>
</template> </template>
</ul> </ul>
@@ -43,16 +43,16 @@
<div class="videoOperation"> <div class="videoOperation">
<div class="opera-title"> <div class="opera-title">
<span>目标语种</span> <span>目标语种</span>
<el-select v-model="value" placeholder="请选择目标语种" style="flex: 1;"> <el-select v-model="currentLang" placeholder="请选择目标语种" style="flex: 1;">
<el-option <el-option
v-for="item in selectAllLang" v-for="item in currentLangObjList"
:key="item.srclang" :key="item.srclang"
:label="item.label" :label="item.label"
:value="item.srclang" :value="item.srclang"
> >
</el-option> </el-option>
</el-select> </el-select>
<el-button type="primary" @click="type = 2">加载字幕</el-button> <el-button type="primary" @click="handleLoadSubtitle()">加载字幕</el-button>
</div> </div>
<div class="opera-content"> <div class="opera-content">
<div class="bg-gray" v-show="type != 4"> <div class="bg-gray" v-show="type != 4">
@@ -68,10 +68,11 @@
v-model="aiTranslate"> v-model="aiTranslate">
</el-input> </el-input>
<div class="opera-btn"> <div class="opera-btn">
<el-button v-show="type == 1" type="primary" plain round size="mini" @click="updateDialogVisible = true">重新生成</el-button> <el-button v-show="type == 1 && currentLang != 'zh-CN'" type="primary" plain round size="mini" @click="handleGenerate()">重新生成</el-button>
<el-button v-show="type == 1 && currentLang == 'zh-CN'" type="primary" plain round size="mini" @click="updateDialogVisible = true">同步更新</el-button>
<el-button v-show="type == 1" type="primary" plain round size="mini" @click="type = 4">编辑</el-button> <el-button v-show="type == 1" type="primary" plain round size="mini" @click="type = 4">编辑</el-button>
<el-button v-show="type == 4" plain round size="mini" @click="type = 1">取消</el-button> <el-button v-show="type == 4" plain round size="mini" @click="type = 1">取消</el-button>
<el-button v-show="type == 4" type="primary" plain round size="mini" @click="type = 1">确认</el-button> <el-button v-show="type == 4" type="primary" plain round size="mini" @click="submitSubtitle">确认</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -86,17 +87,17 @@
<p style="text-align: center;">系统将根据当前最新中文内容重新生成其他语种的翻译您此前对翻译的修改将会丢失</p> <p style="text-align: center;">系统将根据当前最新中文内容重新生成其他语种的翻译您此前对翻译的修改将会丢失</p>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="updateDialogVisible = false"> </el-button> <el-button @click="updateDialogVisible = false"> </el-button>
<el-button style="margin-left: 60px;" type="primary" @click="updateDialogVisible = false">确认同步更新</el-button> <el-button style="margin-left: 60px;" type="primary" @click="handleSyncUpdate()">确认同步更新</el-button>
</span> </span>
</el-dialog> </el-dialog>
<el-dialog <el-dialog
title="AIf翻译" title="AI翻译"
:visible.sync="selectDialogVisible" :visible.sync="selectDialogVisible"
width="500px" width="500px"
class="select-dialog"> class="select-dialog">
<div class="select-dialog-content"> <div class="select-dialog-content">
<p>请选择该课程所支持语种</p> <p>请选择该课程所支持语种</p>
<el-select v-model="selectLang" placeholder="请选择目标语种" style="width: 100%;" multiple> <el-select v-model="currentLangList" placeholder="请选择目标语种" style="width: 100%;" multiple>
<el-option <el-option
v-for="item in selectAllLang" v-for="item in selectAllLang"
:key="item.srclang" :key="item.srclang"
@@ -118,6 +119,10 @@
import videoPlayer from "@/components/VideoPlayer/index.vue"; import videoPlayer from "@/components/VideoPlayer/index.vue";
import { mapGetters, mapMutations } from 'vuex'; import { mapGetters, mapMutations } from 'vuex';
import apiCourse from '@/api/modules/course.js'; import apiCourse from '@/api/modules/course.js';
import apiAiVideo from "@/api/modules/courseAiVideo.js";
import apiStudy from "@/api/modules/courseStudy.js";
import cookies from "vue-cookies";
import { encrypt } from "@/utils/jsencrypt.js";
export default { export default {
name: 'aiTranslate', name: 'aiTranslate',
// ai播放器相关 // ai播放器相关
@@ -126,20 +131,8 @@ export default {
}, },
data() { data() {
return { return {
courseName: '企业经营法则--课程单元', aiTranslate: '',
testUrl: 'https://vjs.zencdn.net/v/oceans.mp4', type: 5, // 1: 正常 2: 生成中 3: 错误 4: 编辑中 5: 未选择语种
currentVideo: {},
aiTranslate: `
00:00:01/00:00:03
Hello everyone in the audience
00:00:03/00:00:05
today I want to share with you the topic of
00:00:05/00:00:09
"The Development History and Future Prospects of Computer Technology -
`,
type: '1', // 1: 正常 2: 生成中 3: 错误 4: 编辑中 5: 未选择语种
selectedLang: [ selectedLang: [
{ {
label: '英文', label: '英文',
@@ -166,13 +159,15 @@ today I want to share with you the topic of
srclang: 'fr', srclang: 'fr',
aiTranslate: 0, aiTranslate: 0,
}, },
], ], // 当前课程支持的语种 - 待开发
currentLangList: [], // 当前课程支持的语种 - 回显下拉框
currentLangObjList: [], // 当前课程支持的语种对象 - 右侧下拉选项
currentLang: '', // 当前选中的语种 - 展示字幕
subtitleObj: {}, // 当前语种的字幕对象
updateDialogVisible: false, updateDialogVisible: false,
selectDialogVisible: false, selectDialogVisible: false,
selectLang: [],
chapterList: [], chapterList: [],
videoName: '', videoName: '',
testUrl: 'https://vjs.zencdn.net/v/oceans.mp4',
currentVideo: {}, currentVideo: {},
aiAbstract: '', aiAbstract: '',
courseId: '', courseId: '',
@@ -183,7 +178,7 @@ today I want to share with you the topic of
} }
}, },
computed: { computed: {
...mapGetters(['selectAllLang']), ...mapGetters(['selectableLang', 'selectAllLang']),
}, },
mounted() { mounted() {
this.courseId = this.$route.query.id; this.courseId = this.$route.query.id;
@@ -198,6 +193,220 @@ today I want to share with you the topic of
setLanguage(){ setLanguage(){
this.selectDialogVisible = true; this.selectDialogVisible = true;
}, },
// 加载字幕
handleLoadSubtitle() {
//type 1: 正常 2: 生成中 3: 错误 4: 编辑中 5: 未选择语种
console.log('当前视频', this.currentVideo);
if (this.currentLang == '') {
this.$message.error('请选择目标语种!');
return;
}
const subtitleList = this.currentVideo?.boeaiSubtitleRspList || [];
this.subtitleObj = subtitleList.filter(item => item.language == this.currentLang)[0] || {};
if (!this.subtitleObj.subtitleData) {
this.type = 2
return;
}
// 模拟加载test.json数据实际项目中应从接口获取
try {
const subtitleDataList = JSON.parse(this.subtitleObj.subtitleData);
console.log('格式化后的字幕', subtitleDataList);
// 将JSON转换为指定文本格式
this.aiTranslate = this.jsonToTextFormat(subtitleDataList);
this.type = 1;
} catch (error) {
this.type = 3;
this.$message.error('字幕数据错误,请重新生成!');
return;
}
},
// 将JSON格式转换为文本格式
jsonToTextFormat(jsonData) {
return jsonData.map(item => {
// 格式化时间
const formatTime = (seconds) => {
const h = Math.floor(seconds / 3600).toString().padStart(2, '0');
const m = Math.floor((seconds % 3600) / 60).toString().padStart(2, '0');
const s = Math.floor(seconds % 60).toString().padStart(2, '0');
return `${h}:${m}:${s}`;
};
const startTime = formatTime(item.start);
const endTime = formatTime(item.end);
// 提取纯文本内容(去除时间戳)
const content = item.text.replace(/\[\d{2}:\d{2}:\d{2}\]\s*/g, '');
return `${startTime}/${endTime}\n${content}`;
}).join('\n\n');
},
// 将文本格式转换为JSON格式
textToJsonFormat(text) {
const lines = text.trim().split('\n');
const jsonData = [];
const timePattern = /^(\d{2}:\d{2}:\d{2})\/(\d{2}:\d{2}:\d{2})$/;
let currentTime = '';
let currentContent = '';
let id = 0;
for (const line of lines) {
const trimmedLine = line.trim();
// 检查是否是时间行
if (timePattern.test(trimmedLine)) {
// 如果已有内容,先保存上一个条目
if (currentTime && currentContent) {
const [startTime, endTime] = currentTime.split('/');
jsonData.push({
id: id++,
start: this.timeToSeconds(startTime),
end: this.timeToSeconds(endTime),
text: `${currentContent.trim()}`,
original_text: '',
confidence: 0.64
});
currentContent = '';
}
currentTime = trimmedLine;
} else if (trimmedLine) {
// 内容行(非空行)
if (currentContent) {
currentContent += '\n' + trimmedLine;
} else {
currentContent = trimmedLine;
}
} else if (currentContent) {
// 空行,添加换行符到内容中
currentContent += '\n';
}
}
// 保存最后一个条目
if (currentTime && currentContent) {
const [startTime, endTime] = currentTime.split('/');
jsonData.push({
id: id++,
start: this.timeToSeconds(startTime),
end: this.timeToSeconds(endTime),
text: `${currentContent.trim()}`,
original_text: '',
confidence: 0.64
});
}
return jsonData;
},
// 时间字符串转秒数
timeToSeconds(timeStr) {
const [h, m, s] = timeStr.split(':').map(Number);
return h * 3600 + m * 60 + s;
},
// 校验文本格式
validateTextFormat(text) {
// console.log('实际文本内容:', JSON.stringify(text));
const lines = text.trim().split('\n');
// console.log('lines数组:', lines);
// console.log('lines数组长度:', lines.length);
const timePattern = /^(\d{2}:\d{2}:\d{2})\/(\d{2}:\d{2}:\d{2})$/;
// 如果文本为空,直接返回错误
if (lines.length === 0) {
return { valid: false, message: '文本内容不能为空' };
}
let expectingContent = false;
let previousEndTime = -1; // 上一个字幕的结束时间
let lineNumber = 0;
let currentContent = '';
for (const line of lines) {
lineNumber++;
const trimmedLine = line.trim();
// 如果是空行,跳过
if (!trimmedLine) {
// 如果正在收集内容,继续收集
if (expectingContent) {
currentContent += '\n';
}
continue;
}
if (expectingContent) {
// 检查是否是新的时间行
// console.log(`第${lineNumber}行expectingContent=true内容"${trimmedLine}"`);
if (timePattern.test(trimmedLine)) {
// console.log(`第${lineNumber}行,内容:"${trimmedLine}"被识别为新的时间行`);
// 是新的时间行,检查当前内容是否为空
if (!currentContent.trim()) {
return { valid: false, message: `时间行后缺少字幕内容` };
}
// 解析时间并检查连贯性
const [startTimeStr, endTimeStr] = trimmedLine.split('/');
const startTime = this.timeToSeconds(startTimeStr);
const endTime = this.timeToSeconds(endTimeStr);
// 检查时间范围是否有效
if (endTime < startTime) {
return { valid: false, message: `时间范围错误,结束时间必须大于开始时间` };
}
// 检查时间顺序和重叠
if (startTime < previousEndTime) {
return { valid: false, message: `时间与上一行时间重叠或顺序错误` };
}
// 更新上一个结束时间
previousEndTime = endTime;
// 重置内容收集,准备处理下一个字幕的内容
currentContent = '';
expectingContent = true;
// console.log('处理完新的时间行后expectingContent设置为:', expectingContent);
} else {
// console.log(`第${lineNumber}行,内容:"${trimmedLine}"被识别为内容行`);
// 是内容行,继续收集内容
currentContent += (currentContent ? '\n' : '') + trimmedLine;
}
} else {
// 应该是时间行
// console.log(`第${lineNumber}行expectingContent=false内容"${trimmedLine}"`);
if (!timePattern.test(trimmedLine)) {
// console.log('timePattern.test(trimmedLine)',trimmedLine, timePattern.test(trimmedLine));
return { valid: false, message: `时间格式错误请使用HH:MM:SS/HH:MM:SS格式` };
}
// console.log(`第${lineNumber}行,内容:"${trimmedLine}"被识别为时间行`);
// 解析时间并检查连贯性
const [startTimeStr, endTimeStr] = trimmedLine.split('/');
const startTime = this.timeToSeconds(startTimeStr);
const endTime = this.timeToSeconds(endTimeStr);
// 检查时间范围是否有效
if (endTime <= startTime) {
return { valid: false, message: `时间范围错误,结束时间必须大于开始时间` };
}
// 检查时间顺序和重叠
if (startTime < previousEndTime) {
return { valid: false, message: `时间顺序错误,与上一个字幕存在时间重叠` };
}
previousEndTime = endTime;
expectingContent = true;
currentContent = '';
console.log('处理完正常时间行后expectingContent设置为:', expectingContent);
}
}
// 检查最后一个时间行是否有内容
if (expectingContent && !currentContent.trim()) {
return { valid: false, message: `时间行后缺少字幕内容` };
}
return { valid: true, message: '格式正确' };
},
// 上下架ai翻译
handleSummaryStatus() { handleSummaryStatus() {
apiCourse.benchAiSet({courseList:[{id: this.courseId, aiTranslate: this.courseInfo.aiTranslate == 1 ? 0 : 1}]}).then(res => { apiCourse.benchAiSet({courseList:[{id: this.courseId, aiTranslate: this.courseInfo.aiTranslate == 1 ? 0 : 1}]}).then(res => {
if(res.status === 200){ if(res.status === 200){
@@ -208,6 +417,64 @@ today I want to share with you the topic of
} }
}) })
}, },
// 提交编辑后的字幕
submitSubtitle() {
// 校验文本格式
const validateResult = this.validateTextFormat(this.aiTranslate);
if (!validateResult.valid) {
this.$message.error(validateResult.message);
return;
}
// 转换为JSON格式
const jsonData = this.textToJsonFormat(this.aiTranslate);
console.log('转换后的JSON数据:', jsonData);
// 模拟发送给接口(实际项目中应调用真实接口)
apiAiVideo.updateText({
videoId: this.currentVideo.id,
language: this.currentLang,
subtitleData: JSON.stringify(jsonData),
updateStatus: 0, // 0-(重新生成)更新当前语种 1-同步更新
}).then(res => {
this.$message.success('字幕提交成功!');
this.getCourseInfo();
});
},
// 确认同步更新
handleSyncUpdate() {
// 转换为JSON格式
const jsonData = this.textToJsonFormat(this.aiTranslate);
console.log('转换后的JSON数据:', jsonData);
apiAiVideo.updateText({
videoId: this.currentVideo.id,
language: this.currentLang,
subtitleData: JSON.stringify(jsonData),
updateStatus: 1, // 0-(重新生成)更新当前语种 1-同步更新
}).then(res => {
this.$message.success('字幕提交成功!');
this.getCourseInfo();
});
},
// 重新生成
handleGenerate() {
this.$confirm('此操作将中文重新生成译文, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 转换为JSON格式
apiAiVideo.updateText({
videoId: this.currentVideo.id,
language: this.currentLang,
updateStatus: 0, // 0-(重新生成)更新当前语种 1-同步更新
}).then(res => {
this.$message.success('字幕提交成功!');
this.getCourseInfo();
});
}).catch(() => {});
},
// 创建播放地址
createPlayUrl(fid, u) { createPlayUrl(fid, u) {
let nowDate = new Date(); let nowDate = new Date();
let ctime = parseInt(nowDate.getTime() / 1000); let ctime = parseInt(nowDate.getTime() / 1000);
@@ -224,10 +491,19 @@ today I want to share with you the topic of
urlSign; urlSign;
} }
}, },
handleSelectVideo(item, index) { // 选择视频
this.SET_selectableLang(item?.boeaiSubtitleRspList); handleSelectVideo(item, isRefresh) {
this.videoName = item.chapterName ? item.chapterName + "-视频" + index : this.courseInfo.name;
this.currentVideo = item; this.currentVideo = item;
this.SET_selectableLang(item?.boeaiSubtitleRspList);
this.videoName = item.chapterName ? item.chapterName + "-视频" + item.videoIndex : this.courseInfo.name;
// 编辑时 刷新时保留选择
if (isRefresh) {
this.handleLoadSubtitle();
} else {
this.currentLang = '';
this.aiTranslate = '';
this.type = 5;
}
let curriculumData = {}; let curriculumData = {};
if (item.content.startsWith("{")) { if (item.content.startsWith("{")) {
curriculumData = JSON.parse(item.content); curriculumData = JSON.parse(item.content);
@@ -237,6 +513,20 @@ today I want to share with you the topic of
this.blobId = item.id; this.blobId = item.id;
this.createPlayUrl(item.contentRefId, curriculumData.url); this.createPlayUrl(item.contentRefId, curriculumData.url);
}, },
// 根据课程支持语种 设置 右侧下拉框选项currentLangObjList 和 中间下拉框值 currentLangList
setCurrentLang(langList) {
this.currentLangList = langList;
let currentLangObjList = [];
this.selectAllLang.forEach(item => {
langList.forEach(item2 => {
if (item.srclang === item2) {
currentLangObjList.push(item)
}
})
})
this.currentLangObjList = currentLangObjList;
},
// 获取课程信息 - 获取课程信息、章节信息、视频信息 选择视频后 将字幕信息存储到vuex中
getCourseInfo() { getCourseInfo() {
apiStudy.studyIndexPost({ apiStudy.studyIndexPost({
cid: this.courseId, cid: this.courseId,
@@ -249,6 +539,7 @@ today I want to share with you the topic of
this.SET_courseInfo(this.courseInfo); this.SET_courseInfo(this.courseInfo);
const contents = rs.result.contents?.filter(item => item.contentType == 10) || []; const contents = rs.result.contents?.filter(item => item.contentType == 10) || [];
this.sections = rs.result.sections || []; this.sections = rs.result.sections || [];
this.setCurrentLang(this.courseInfo.languageCode || [])
if (this.sections.length > 0) { if (this.sections.length > 0) {
let treeList = []; let treeList = [];
let hasOne = false; let hasOne = false;
@@ -257,12 +548,20 @@ today I want to share with you the topic of
...section, ...section,
children: [], children: [],
}; };
let videoIndex = 1;
contents.forEach(item => { contents.forEach(item => {
if (section.id == item.csectionId) { if (section.id == item.csectionId) {
item.chapterName = section.name || ''; item.chapterName = section.name || '';
item.videoIndex = videoIndex++;
treeNode.children.push(item); treeNode.children.push(item);
if (!hasOne) { // 刷新时保留选择
this.handleSelectVideo(item, 1); if(this.currentVideo.id) {
if(this.currentVideo.id == item.id) {
this.handleSelectVideo(item, true);
}
hasOne = true;
} else if (!hasOne) {
this.handleSelectVideo(item);
hasOne = true; hasOne = true;
} }
} }
@@ -271,9 +570,9 @@ today I want to share with you the topic of
}) })
this.chapterList = treeList; this.chapterList = treeList;
} else { } else {
this.handleSelectVideo(contents[0], 1); this.handleSelectVideo(contents[0], this.currentVideo.id?true:false);
} }
console.log('33333',this.courseInfo, this.courseInfo.name); console.log('课程数据',this.courseInfo, this.courseInfo.name);
this.aiAbstract = this.courseInfo.summaryContent this.aiAbstract = this.courseInfo.summaryContent
if (contents.length == 0) { if (contents.length == 0) {
$this.$message.error("课程内容已删除或课程已不再使用"); $this.$message.error("课程内容已删除或课程已不再使用");
@@ -343,6 +642,7 @@ today I want to share with you the topic of
} }
} }
.ai-right{ .ai-right{
height: 100%;
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -366,6 +666,7 @@ today I want to share with you the topic of
} }
} }
.ai-content{ .ai-content{
height: calc(100% - 76px);
flex: 1; flex: 1;
padding: 24px 30px; padding: 24px 30px;
display: flex; display: flex;
@@ -427,6 +728,7 @@ today I want to share with you the topic of
} }
} }
.opera-content{ .opera-content{
height: calc(100% - 40px);
position: relative; position: relative;
flex: 1; flex: 1;
color: rgba(17, 24, 39, 1); color: rgba(17, 24, 39, 1);
@@ -441,6 +743,7 @@ today I want to share with you the topic of
background: rgba(249, 250, 251, 1); background: rgba(249, 250, 251, 1);
height: 100%; height: 100%;
padding: 15px; padding: 15px;
overflow-y: auto;
} }
.opera-btn{ .opera-btn{
position: absolute; position: absolute;