Files
learning-system-portal/src/views/course/aiSet/aiAbstract.vue
2025-12-17 18:04:08 +08:00

458 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="aiAbstract">
<div class="ai-left" v-if="sections.length > 0">
<div class="left-title">{{courseInfo.name}}</div>
<ul class="ai-list" >
<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="item.id">
<div class="ai-item-title">{{item.chapterName}}-{{'视频' + (index + 1)}}</div>
</li>
</template>
</ul>
</div>
<div class="ai-right">
<div class="right-title">
<h3>{{videoName}}</h3>
<div>
<el-button type="primary" @click="handleSummaryStatus">{{courseInfo.aiAbstract == 1 ? '下架' : '上架'}}本课程AI摘要</el-button>
</div>
</div>
<div class="ai-content">
<div class="videoBox">
<videoPlayer
:src="blobUrl"
:blobId="blobId"
style="height: auto;"> </videoPlayer>
<div class="video-content">
<h4>视频摘要</h4>
<p>
{{videoSummary}}
</p>
</div>
</div>
<div class="videoOperation">
<div class="opera-title">
<h4>课程摘要</h4>
<div class="opera-btn">
<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 == 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>
</div>
</div>
<div class="opera-content" v-show="type != 4">
<span v-show="type == 1">{{ aiAbstract }}</span>
<p v-show="type == 2" style="color: rgba(207, 207, 207, 1);text-align: center;margin-top: 48%;">AI 摘要重新生成中过程可能耗时较长<br>无需在此等待哦</p>
<img v-show="type == 3" src="@/assets/images/course/generationFailed.png" alt="" width="150" height="159" style="display: flex;margin: 35% auto 0 auto;">
</div>
<el-input v-show="type == 4"
type="textarea"
placeholder="请输入内容"
autosize
v-model="aiAbstract">
</el-input>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapGetters, mapMutations } from "vuex";
import videoPlayer from "@/components/VideoPlayer/index.vue";
import apiStudy from "@/api/modules/courseStudy.js";
import cookies from "vue-cookies";
import apiAiVideo from "@/api/modules/courseAiVideo.js";
import apiCourse from '@/api/modules/course.js';
import { encrypt } from "@/utils/jsencrypt.js";
export default {
name: 'aiAbstract',
// ai播放器相关
components: {
videoPlayer
},
data() {
return {
chapterList: [],
videoName: '',
currentVideo: {},
aiAbstract: '',
type: '1', // 1: 正常 2: 生成中 3: 错误 4: 编辑中
courseId: '',
courseInfo: {},
sections: [],
blobId: '',
blobUrl: '',
videoSummary: '',
summaryTimer: null
}
},
beforeDestroy() {
// 组件销毁前清除定时器
if (this.summaryTimer) {
clearInterval(this.summaryTimer);
this.summaryTimer = null;
}
},
created() {
this.courseId = this.$route.query.id;
console.log(this.courseId);
this.getCourseInfo();
this.getCourseSummary();
},
mounted() {
},
methods: {
...mapMutations({
SET_selectableLang: 'video/SET_selectableLang',
SET_courseInfo: 'video/SET_courseInfo',
}),
// 上下架状态
handleSummaryStatus() {
apiCourse.benchAiSet({courseList:[{id: this.courseId, aiAbstract: this.courseInfo.aiAbstract == 1 ? 0 : 1}]}).then(res => {
if(res.status === 200){
this.$message.success(this.courseInfo.aiAbstract == 1 ? '下架成功!' : '上架成功!');
this.getCourseInfo();
}else{
this.$message.error(this.courseInfo.aiAbstract == 1 ? '下架失败!' : '上架失败!');
}
})
},
// 下载摘要 - 不需要
downloadSummary() {
apiAiVideo.downloadSummaryTxt(this.courseId)
.then((rs) => {
console.log('下载',rs);
// 1. 创建 Blob 的临时 URL
const url = URL.createObjectURL(rs);
// 2. 创建隐藏的 <a> 标签
const a = document.createElement('a');
a.href = url;
a.download = '课程摘要.txt'; // 设置下载的文件名
a.style.display = 'none';
// 3. 添加到文档中并触发点击
document.body.appendChild(a);
a.click();
// 4. 清理
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url); // 释放 URL 对象
}, 100);
})
},
updateSummary() {
apiAiVideo.updateVideoSummary({
courseId: this.courseId,
summaryContent: this.aiAbstract,
})
.then((rs) => {
if (rs.rspCode == '0000') {
this.type = '1';
this.getCourseSummary();
}
})
},
retrySummaryTxt() {
this.type = '2';
apiAiVideo.retrySummaryTxt({
courseId: this.courseId,
})
.then((rs) => {
if (rs.rspCode == '0000') {
this.type = '1';
this.getCourseSummary();
} else {
this.type = '3';
}
})
},
getVideoSummary(id) {
apiAiVideo.selectById(id)
.then((rs) => {
this.videoSummary = ''
if (rs.rspCode == '0000') {
this.videoSummary = rs.data.briefSummary || '';
} else {
this.$message.error('获取视频摘要失败!');
}
})
.catch((err) => {
this.videoSummary = ''
this.$message.error('获取视频摘要失败!');
})
},
getCourseSummary() {
apiAiVideo.getCourse({
courseId: this.courseId,
})
.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 || '';
})
.catch(() => {
// 请求失败时也清除定时器
if (this.summaryTimer) {
clearInterval(this.summaryTimer);
this.summaryTimer = null;
}
})
},
createPlayUrl(fid, u) {
let nowDate = new Date();
let ctime = parseInt(nowDate.getTime() / 1000);
let beforeUrl = parseInt(nowDate.getTime() / 1000) + "/" + fid;
let urlSign = encodeURIComponent(encrypt(beforeUrl));
cookies.set("PLAYSIGN_TIME", ctime); //写客户端的cookie保存
//以下判断是为了区分本地环境和服务器环境
if (process.env.NODE_ENV == "development") {
this.blobUrl = process.env.VUE_APP_FILE_BASE_URL + u;
} else {
this.blobUrl =
process.env.VUE_APP_BASE_API +
"/xboe/m/course/cware/resource?sign=" +
urlSign;
}
},
handleSelectVideo(item, index) {
console.log('选择了该视频', item);
this.SET_selectableLang(item?.boeaiSubtitleRspList);
this.videoName = item.chapterName ? item.chapterName + "-视频" + index : this.courseInfo.name;
this.currentVideo = item;
let curriculumData = {};
if (item.content.startsWith("{")) {
curriculumData = JSON.parse(item.content);
} else {
curriculumData.url = item.content;
}
this.getVideoSummary(item.id);
this.blobId = item.id;
this.createPlayUrl(item.contentRefId, curriculumData.url);
},
getCourseInfo() {
apiStudy.studyIndexPost({
cid: this.courseId,
addView: false,
audiences: '',
})
.then((rs) => {
if (rs.status == 200) {
this.courseInfo = rs.result.course || {};
this.SET_courseInfo(this.courseInfo);
const contents = rs.result.contents?.filter(item => item.contentType == 10) || [];
this.sections = rs.result.sections || [];
if (this.sections.length > 0) {
let treeList = [];
let hasOne = false;
this.sections.forEach(section => {
let treeNode = {
...section,
children: [],
};
contents.forEach(item => {
if (section.id == item.csectionId) {
item.chapterName = section.name || '';
treeNode.children.push(item);
if (!hasOne) {
this.handleSelectVideo(item, 1);
hasOne = true;
}
}
})
treeList.push(treeNode);
})
this.chapterList = treeList;
} else {
this.handleSelectVideo(contents[0], 1);
}
console.log('33333',this.courseInfo, this.courseInfo.name);
this.aiAbstract = this.courseInfo.summaryContent
if (contents.length == 0) {
$this.$message.error("课程内容已删除或课程已不再使用");
return;
}
if (!rs.result.course.enabled) {
$this.$message.error(
"十分抱歉,此课程已停用,如需使用,请联系管理员。"
);
return;
}
}
})
}
}
};
</script>
<style lang="scss" scoped>
.aiAbstract{
height: 100%;
display: flex;
padding: 10px;
justify-content: space-between;
gap: 15px;
background: #f4f7fa;
.ai-left{
padding: 9px 10px;
width: 24%;
border-radius: 10px;
background: rgba(255, 255, 255, 1);
.left-title{
background: rgba(239, 244, 252, 1);
padding: 15px;
text-align: center;
color: rgba(75, 92, 118, 1);
font-family: Noto Sans SC;
font-size: 16px;
font-weight: 400;
line-height: 23px;
letter-spacing: 0px;
}
.ai-list{
margin: 0;
.ai-item{
cursor: pointer;
padding: 15px;
text-align: center;
color: rgba(75, 92, 118, 1);
font-family: Noto Sans SC;
font-size: 16px;
font-weight: 400;
line-height: 23px;
letter-spacing: 0px;
padding: 17px 31px;
border-bottom: 1px solid rgba(240, 240, 240, 1);
text-align: left;
&:hover{
background: rgba(240, 240, 240, 1);
}
&.active{
color: rgba(64, 158, 255, 1);
font-weight: 500;
}
}
}
}
.ai-right{
flex: 1;
display: flex;
flex-direction: column;
border-radius: 10px;
background: rgba(255, 255, 255, 1);
.right-title{
display: flex;
padding: 0 28px;
height: 76px;
min-height: 76px;
border-bottom: 1px solid rgba(229, 231, 235, 1);
display: flex;
justify-content: space-between;
align-items: center;
h3{
color: rgba(17, 24, 39, 1);
font-family: Noto Sans SC;
font-size: 18px;
font-weight: 600;
line-height: 26px;
margin: 0;
}
}
.ai-content{
flex: 1;
padding: 24px 30px;
display: flex;
gap: 30px;
.videoBox{
width: 55%;
display: flex;
flex-direction: column;
gap: 24px;
.video-content{
flex: 1;
h4{
margin: 0 0 10px 0;
color: rgba(17, 24, 39, 1);
font-family: Noto Sans SC;
font-size: 16px;
font-weight: 500;
line-height: 23px;
}
p{
color: rgba(17, 24, 39, 1);
font-family: Noto Sans SC;
font-size: 14px;
font-weight: 400;
line-height: 20px;
}
}
}
.videoOperation{
flex: 1;
display: flex;
flex-direction: column;
gap: 15px;
.opera-title{
display: flex;
justify-content: space-between;
align-items: center;
h4{
margin: 0;
color: rgba(17, 24, 39, 1);
font-family: Noto Sans SC;
font-size: 16px;
font-weight: 500;
line-height: 23px;
}
.opera-btn{
display: flex;
}
}
.opera-content{
flex: 1;
border-radius: 7px;
background: rgba(249, 250, 251, 1);
padding: 15px;
color: rgba(17, 24, 39, 1);
font-family: Noto Sans SC;
font-size: 14px;
font-weight: 400;
line-height: 20px;
letter-spacing: 0px;
}
}
}
}
}
::v-deep .el-textarea{
flex: 1;
}
::v-deep .el-textarea__inner{
height: 100%!important;
}
</style>