diff --git a/src/api/modules/course.js b/src/api/modules/course.js index 39a88137..f76839c7 100644 --- a/src/api/modules/course.js +++ b/src/api/modules/course.js @@ -4,6 +4,7 @@ * **/ import ajax from '@/utils/xajax.js' +import ajax2 from '../unionAjax.js'; /** * 保存课程基本信息,新增和更新都是此方式 @@ -42,8 +43,8 @@ import ajax from '@/utils/xajax.js' ] } */ -const saveBase = function(data) { - return ajax.postJson('/xboe/m/course/manage/save', data); +const saveBase = function (data) { + return ajax.postJson('/xboe/m/course/manage/save', data); } @@ -52,39 +53,39 @@ const saveBase = function(data) { * 仅仅是保存课程信息,不包括教师信息 * @param {Object} data */ -const saveOnlyCourse = function(data) { - return ajax.postJson('/xboe/m/course/manage/save-only-course', data); +const saveOnlyCourse = function (data) { + return ajax.postJson('/xboe/m/course/manage/save-only-course', data); } /**提交课程*/ -const submitCourse = function(data) { - return ajax.postJson('/xboe/m/course/manage/submit', data); +const submitCourse = function (data) { + return ajax.postJson('/xboe/m/course/manage/submit', data); } /**撤销已提交审核的课程*/ -const revokeSubmit = function(id) { - return ajax.post('/xboe/m/course/manage/revoke', {id}); +const revokeSubmit = function (id) { + return ajax.post('/xboe/m/course/manage/revoke', { id }); } /** * 复制课程 * @param {Object} */ -const copyCourse = function(data) { - return ajax.post('/xboe/m/course/manage/copy',data); +const copyCourse = function (data) { + return ajax.post('/xboe/m/course/manage/copy', data); } /* 查询课程是否有重复名称 */ -const isRedoName=function(){ - return ajax.get('/xboe/m/course/manage/isRedoName'); +const isRedoName = function () { + return ajax.get('/xboe/m/course/manage/isRedoName'); } /* 查询当前添加课程是否已有 courseName 要添加的课程姓名 */ -const isCourseName=function(courseName,courseId){ - return ajax.get(`/xboe/m/course/manage/isCourseName?courseName=${courseName}&courseId=${courseId}`); +const isCourseName = function (courseName, courseId) { + return ajax.get(`/xboe/m/course/manage/isCourseName?courseName=${courseName}&courseId=${courseId}`); } /** * 查询修改日志,列表,不分页 @@ -95,16 +96,16 @@ const isCourseName=function(courseName,courseId){ name: 修改人 } */ -const findUpdateLogs = function(params) { - return ajax.post('/xboe/m/course/manage/upldate-logs',params); +const findUpdateLogs = function (params) { + return ajax.post('/xboe/m/course/manage/upldate-logs', params); } /** * 根据id获取修改的详细信息 * @param {Object} id */ -const getUpdateLog = function(id) { - return ajax.get('/xboe/m/course/manage/upldate-log-detail?id='+id); +const getUpdateLog = function (id) { + return ajax.get('/xboe/m/course/manage/upldate-log-detail?id=' + id); } /** @@ -139,8 +140,8 @@ const getUpdateLog = function(id) { ] } */ -const saveContent = function(data) { - return ajax.postJson('/xboe/m/course/content/save', data); +const saveContent = function (data) { + return ajax.postJson('/xboe/m/course/content/save', data); } @@ -159,18 +160,18 @@ const saveContent = function(data) { * ] * @returns */ -const updateContentOrders = function(cid,items) { - return ajax.postJson('/xboe/m/course/content/update-orders/'+cid, items); +const updateContentOrders = function (cid, items) { + return ajax.postJson('/xboe/m/course/content/update-orders/' + cid, items); } /** * 课程的详细信息 * @param {String} id */ -const detail = function(id) { - return ajax.get('/xboe/m/course/manage/detail?id=' + id); +const detail = function (id) { + return ajax.get('/xboe/m/course/manage/detail?id=' + id); } -const getDictIds = function(pid,type) { +const getDictIds = function (pid, type) { return ajax.get(`/xboe/m/course/manage/getDictIds?pid=${pid}&type=${type}`); } /** @@ -181,8 +182,8 @@ const getDictIds = function(pid,type) { name:'' } */ -const updateContentName = function(data) { - return ajax.post('/xboe/m/course/content/update-name', data); +const updateContentName = function (data) { + return ajax.post('/xboe/m/course/content/update-name', data); } /** @@ -194,8 +195,8 @@ const updateContentName = function(data) { erasable:是否物理删除,此值是课程信息中系统带过来的字段,直接使用它就可以了 } */ -const delContent = function(data) { - return ajax.post('/xboe/m/course/content/delete', data); +const delContent = function (data) { + return ajax.post('/xboe/m/course/content/delete', data); } /** @@ -207,40 +208,40 @@ const delContent = function(data) { parentId: 上级id。如果没有可以填“-1”字符串 orderIndex:显示顺序,顺序索引,整数 */ -const saveSection = function(data) { - return ajax.post('/xboe/m/course/content/save-section', data); +const saveSection = function (data) { + return ajax.post('/xboe/m/course/content/save-section', data); } /** * 删除章节目录,注意只有目录下没有学习内容时才允许删除 * @param {Object} data */ -const delSection = function(id) { - return ajax.post('/xboe/m/course/content/delete-section?id=' + id); +const delSection = function (id) { + return ajax.post('/xboe/m/course/content/delete-section?id=' + id); } /** * 根据课程学习内容的id。获取作业信息,只有学习内容是作业时才会有信息 * @param {Object} ccid */ -const getHomework = function(ccid) { - return ajax.post('/xboe/m/course/content/homework?ccid=' + ccid); +const getHomework = function (ccid) { + return ajax.post('/xboe/m/course/content/homework?ccid=' + ccid); } /** * 根据课程学习内容的id。获取考试信息,只有学习内容是考试时才会有信息 * @param {Object} ccid */ -const getExam = function(ccid) { - return ajax.post('/xboe/m/course/content/exam?ccid=' + ccid); +const getExam = function (ccid) { + return ajax.post('/xboe/m/course/content/exam?ccid=' + ccid); } /** * 根据课程学习内容的id。获取评估信息,评估内容可以获取 * @param {Object} ccid */ -const getAssess = function(ccid) { - return ajax.post('/xboe/m/course/content/assess?ccid=' + ccid); +const getAssess = function (ccid) { + return ajax.post('/xboe/m/course/content/assess?ccid=' + ccid); } /** @@ -265,21 +266,21 @@ const getAssess = function(ccid) { name 课程名称 */ -const pageList = function(query) { - return ajax.post('/xboe/m/course/manage/pagelist', query); +const pageList = function (query) { + return ajax.post('/xboe/m/course/manage/pagelist', query); } /** * 课程管理分页查询(新) * @param {Object} query */ -const managePage = function(query) { - return ajax.postJson('/xboe/m/course/manage/page', query); +const managePage = function (query) { + return ajax.postJson('/xboe/m/course/manage/page', query); } /**计算待审核课程*/ -const countWaitAudit = function() { +const countWaitAudit = function () { return ajax.get('/xboe/m/course/manage/wait-audit-num'); } @@ -288,8 +289,8 @@ const countWaitAudit = function() { * 当前用户需要审核的课程列表 * @param {Object} query 同pageList */ -const auditList = function(query) { - return ajax.post('/xboe/m/course/manage/audit-pagelist', query); +const auditList = function (query) { + return ajax.post('/xboe/m/course/manage/audit-pagelist', query); } @@ -297,8 +298,8 @@ const auditList = function(query) { * 【已移到courseAudit中】 * 教师需要审核的课程列表 */ -const teacherAuditList = function(query) { - return ajax.post('/xboe/m/course/audit/teacher-course', query); +const teacherAuditList = function (query) { + return ajax.post('/xboe/m/course/audit/teacher-course', query); } /** @@ -307,16 +308,16 @@ const teacherAuditList = function(query) { * @param {Object} data * {courseId:课程id,teacherId:指定的审核人教师的id,teacherName:教师名称,remark:备注} */ -const auditAppoint = function(data) { - return ajax.post('/xboe/m/course/audit/appoint', data); +const auditAppoint = function (data) { + return ajax.post('/xboe/m/course/audit/appoint', data); } /** * 获取审核信息,上面教师点击审核课程时,用于查询,上面“转审”时,用户填写的备注信息 * @param {courseId:'课程id',teacherId:'可以不填写,系统会查询当前人'} data */ -const getAuditInfo = function(data) { - return ajax.post('/xboe/m/course/audit/infos', data); +const getAuditInfo = function (data) { + return ajax.post('/xboe/m/course/audit/infos', data); } @@ -324,56 +325,56 @@ const getAuditInfo = function(data) { * 管理员的课程审核处理 * @param {Object} query {id:课程id,title:课程的名称, Boolean pass 是否通过,remark 备注} */ -const audit = function(data) { - return ajax.post('/xboe/m/course/manage/audit', data); +const audit = function (data) { + return ajax.post('/xboe/m/course/manage/audit', data); } /** * 审核记录列表,分页查询 */ -const auditPageRecords = function(data) { - return ajax.post('/xboe/m/course/audit/page-records', data); +const auditPageRecords = function (data) { + return ajax.post('/xboe/m/course/audit/page-records', data); } /** * 审核记录列表,要卖课程id,查询出审核列记录信息 * { courseId:必须} */ -const auditCourseRecords = function(data) { - return ajax.post('/xboe/m/course/audit/course-records',data); +const auditCourseRecords = function (data) { + return ajax.post('/xboe/m/course/audit/course-records', data); } /** * 管理员的课程发布,当前已经不再使用了 * @param {Object} query {ids:课程id,多个使用逗号分隔,title:课程的名称, Boolean pass 是否发布} */ -const publish = function(data) { - return ajax.post('/xboe/m/course/manage/publish', data); +const publish = function (data) { + return ajax.post('/xboe/m/course/manage/publish', data); } -const auditAndPublish=function(data) { - return ajax.post('/xboe/m/course/manage/audit-publish', data); +const auditAndPublish = function (data) { + return ajax.post('/xboe/m/course/manage/audit-publish', data); } /** * 设置top * @param {Object} query {ids:课程id,多个使用逗号分隔,title:课程的名称,Boolean top 是否置顶} */ -const setTop = function(data) { - return ajax.post('/xboe/m/course/manage/top', data); +const setTop = function (data) { + return ajax.post('/xboe/m/course/manage/top', data); } /** * 是否展示置顶相关功能 */ -const showSetTop = function() { +const showSetTop = function () { return ajax.get('/xboe/m/course/manage/show-settop'); } /** * 获取置顶课程列表 */ -const fetchTopCourseList = function() { +const fetchTopCourseList = function () { return ajax.get('/xboe/m/course/manage/topList'); } @@ -381,7 +382,7 @@ const fetchTopCourseList = function() { * 更新置顶课程排序 * @param {Array<{id:string,sortWeight:number}>} data */ -const updateTopCourseSort = function(data) { +const updateTopCourseSort = function (data) { return ajax.postJson('/xboe/m/course/manage/top-sortchange', data); } @@ -389,8 +390,8 @@ const updateTopCourseSort = function(data) { * 管理员的设置启用停用 * @param {Object} query {ids:课程id,多个使用逗号分隔,title:课程的名称, Boolean enabled 是否启用} */ -const setEnabled = function(data) { - return ajax.post('/xboe/m/course/manage/enabled', data); +const setEnabled = function (data) { + return ajax.post('/xboe/m/course/manage/enabled', data); } /** @@ -398,34 +399,34 @@ const setEnabled = function(data) { * erasable 此值是课程信息带过来的,直接传就可以 * @param {Object} query {id:课程id,多个使用逗号分隔,Boolean erasable 是否物理删除,title:课程的名称, remark 备注} */ -const del = function(data) { - return ajax.post('/xboe/m/course/manage/delete', data); +const del = function (data) { + return ajax.post('/xboe/m/course/manage/delete', data); } /* 详情 */ -const detailFew=function(id){ - return ajax.get('/xboe/m/course/portal/detail-few?id=' + id); +const detailFew = function (id) { + return ajax.get('/xboe/m/course/portal/detail-few?id=' + id); } /* 直接审核,教师提交审核 */ -const sumbits=function(data){ - return ajax.post('/xboe/m/course/manage/sumbits',data); +const sumbits = function (data) { + return ajax.post('/xboe/m/course/manage/sumbits', data); } /* 教师授课记录 */ -const teacherCourse=function(teacherId){ - return ajax.get('/xboe/m/course/manage/teacher-course?teacherId='+teacherId); +const teacherCourse = function (teacherId) { + return ajax.get('/xboe/m/course/manage/teacher-course?teacherId=' + teacherId); } /* 教师授课记录导出 @param teacherId 教师id */ -const exportTeacherCourse=function(teacherId){ - return ajax.post('/xboe/m/course/manage/export-teacher-course?teacherId='+teacherId) +const exportTeacherCourse = function (teacherId) { + return ajax.post('/xboe/m/course/manage/export-teacher-course?teacherId=' + teacherId) } /* *待审核课程记录导出 @@ -445,25 +446,25 @@ const exportTeacherCourse=function(teacherId){ type:课程类型,10微课,21在线课(直播);20:在线课( 录播);30:面授课;40:混合式, name 课程名称 */ -const exportCourseAudit=function(query){ - return ajax.post('/xboe/m/course/manage/exportCourseAudit',query); +const exportCourseAudit = function (query) { + return ajax.post('/xboe/m/course/manage/exportCourseAudit', query); } /* 参数同上待审核课程记录导出 课程的导出和已审核的课程导出 */ -const exportCourse = function(query) { - return ajax.get({ - url: '/xboe/m/course/manage/export', - method: 'get', - params: query, - responseType: 'blob' - }); +const exportCourse = function (query) { + return ajax.get({ + url: '/xboe/m/course/manage/export', + method: 'get', + params: query, + responseType: 'blob' + }); } //判断受众id是否有关联 -const queryCrowd=function(query){ - return ajax.postJson('/xboe/m/course/manage/queryCrowd',query); +const queryCrowd = function (query) { + return ajax.postJson('/xboe/m/course/manage/queryCrowd', query); } /** @@ -472,69 +473,78 @@ const queryCrowd=function(query){ * ids * } * */ -const ids=function (data){ - return ajax.postJson('/xboe/m/course/manage/ids',data); +const ids = function (data) { + return ajax.postJson('/xboe/m/course/manage/ids', data); } -const saveTip = function() { +const saveTip = function () { return ajax.postJson('/xboe/m/course/manage/saveTip'); } /** * 获取我开发的课程列表 */ -const courseList = function(data) { +const courseList = function (data) { return ajax.postJson('/xboe/m/course/manage/develop_page', data); } +// ai播放器相关 - 批量AI设置 +const benchAiSet = function (data) { + return ajax.postJson('/xboe/m/course/manage/benchAiSet', data); +} +const listByUser = function (data) { + return ajax2.get('/manageApi/admin/thirdApi/permission/listByUser?permissionType=PAGE'); +} export default { - saveBase, - submitCourse, - revokeSubmit, - copyCourse, - findUpdateLogs, - getUpdateLog, - detail, - getDictIds, - saveContent, - pageList, - managePage, - setEnabled, - del, - publish, - saveSection, - getHomework, - countWaitAudit, - auditList, - teacherAuditList, - auditAppoint, - getAuditInfo, - audit, - auditPageRecords, - auditCourseRecords, - auditAndPublish, - getAssess, - setTop, - showSetTop, - fetchTopCourseList, - updateTopCourseSort, - delSection, - getExam, - delContent, - updateContentName, - updateContentOrders, - saveOnlyCourse, - isRedoName, - isCourseName, - detailFew, - sumbits, - teacherCourse, - exportTeacherCourse, - exportCourseAudit, - exportCourse, - queryCrowd, + saveBase, + submitCourse, + revokeSubmit, + copyCourse, + findUpdateLogs, + getUpdateLog, + detail, + getDictIds, + saveContent, + pageList, + managePage, + setEnabled, + del, + publish, + saveSection, + getHomework, + countWaitAudit, + auditList, + teacherAuditList, + auditAppoint, + getAuditInfo, + audit, + auditPageRecords, + auditCourseRecords, + auditAndPublish, + getAssess, + setTop, + showSetTop, + fetchTopCourseList, + updateTopCourseSort, + delSection, + getExam, + delContent, + updateContentName, + updateContentOrders, + saveOnlyCourse, + isRedoName, + isCourseName, + detailFew, + sumbits, + teacherCourse, + exportTeacherCourse, + exportCourseAudit, + exportCourse, + queryCrowd, ids, saveTip, - courseList + courseList, + benchAiSet, + listByUser, } diff --git a/src/assets/images/course/courseAbstract.png b/src/assets/images/course/courseAbstract.png new file mode 100644 index 00000000..180aeaac Binary files /dev/null and b/src/assets/images/course/courseAbstract.png differ diff --git a/src/assets/images/course/courseNew.png b/src/assets/images/course/courseNew.png new file mode 100644 index 00000000..13d81fcd Binary files /dev/null and b/src/assets/images/course/courseNew.png differ diff --git a/src/assets/images/course/generationFailed.png b/src/assets/images/course/generationFailed.png new file mode 100644 index 00000000..5ca255d5 Binary files /dev/null and b/src/assets/images/course/generationFailed.png differ diff --git a/src/assets/images/course/languageIcon.png b/src/assets/images/course/languageIcon.png new file mode 100644 index 00000000..454e67cb Binary files /dev/null and b/src/assets/images/course/languageIcon.png differ diff --git a/src/assets/images/course/noData.png b/src/assets/images/course/noData.png new file mode 100644 index 00000000..822040e7 Binary files /dev/null and b/src/assets/images/course/noData.png differ diff --git a/src/assets/images/course/selectLanguage.png b/src/assets/images/course/selectLanguage.png new file mode 100644 index 00000000..f13efb3a Binary files /dev/null and b/src/assets/images/course/selectLanguage.png differ diff --git a/src/assets/images/course/wengaoTip.png b/src/assets/images/course/wengaoTip.png new file mode 100644 index 00000000..f83ce6fb Binary files /dev/null and b/src/assets/images/course/wengaoTip.png differ diff --git a/src/assets/styles/btn.scss b/src/assets/styles/btn.scss index e6ba1a8e..0cb69515 100644 --- a/src/assets/styles/btn.scss +++ b/src/assets/styles/btn.scss @@ -97,3 +97,29 @@ font-size: 14px; border-radius: 4px; } +// 已下架 +.custom-takeout{ + display: inline-block; + padding: 3px 13px; + border-radius: 20px; + font-size: 12px; + background: rgba(254, 249, 195, 1); + color: rgba(133, 77, 14, 1); + font-size: 12px; + font-weight: 500; + line-height: 17px; + letter-spacing: 0px; +} +// 已上架 +.custom-putaway{ + display: inline-block; + padding: 3px 13px; + border-radius: 20px; + font-size: 12px; + background: rgba(220, 252, 231, 1); + color: rgba(22, 101, 52, 1); + font-size: 12px; + font-weight: 500; + line-height: 17px; + letter-spacing: 0px; +} \ No newline at end of file diff --git a/src/components/Course/aiScript.vue b/src/components/Course/aiScript.vue new file mode 100644 index 00000000..7fd1c883 --- /dev/null +++ b/src/components/Course/aiScript.vue @@ -0,0 +1,439 @@ + + + + + \ No newline at end of file diff --git a/src/components/Course/courseForm.vue b/src/components/Course/courseForm.vue index 05018a13..54a9da41 100644 --- a/src/components/Course/courseForm.vue +++ b/src/components/Course/courseForm.vue @@ -208,6 +208,44 @@ placeholder="请尽量填写课程简介,用于列表中显示,可以让用户更容易了解课程信息"> + + +
+
+ + + + +
+
+
+
+ AI摘要 + + + + +
+
+ AI文稿 + + + + +
+
+
+ AI翻译语种 + + + + + + +
+
+
+
@@ -390,6 +428,44 @@ placeholder="请尽量填写课程简介,用于列表中显示,可以让用户更容易了解课程信息"> + + +
+
+ + + + +
+
+
+
+ AI摘要 + + + + +
+
+ AI文稿 + + + + +
+
+
+ AI翻译语种 + + + + + + +
+
+
+
+
+
+ {{!currentLang ? 'AI翻译' : currentLangLabel}} +
+
    +
  • {{ item.label }}
  • +
+
+
+
|
+
+ 字幕 + + +
+
|
+
{{currentSpeed === 1 ? '倍速' : `${currentSpeed}x`}}
@@ -224,6 +252,7 @@ import volumeBar from "@/components/VideoPlayer/volume-bar.vue"; import progressBar from "@/components/VideoPlayer/progress-bar.vue"; import playerBarrageScreen from "@/components/VideoPlayer/player-barrage-screen.vue"; +import { mapGetters, mapMutations } from 'vuex'; export default { name: "barrage-videoplayer", @@ -301,12 +330,24 @@ export default { fullTimeFormat: "00:00:00", // 视频总长度的文字 barrageTimelineStart: 0, // 弹幕时间轴的起始时间点(手动调整进度条触发更新) isInit:false, // 是否初始化过 + // ai播放器相关 + isSubtitle: true, // 是否开启字幕 + currentLangLabel:'', // 当前字幕语言 }; }, + // ai播放器相关 + computed: { + ...mapGetters(['selectableLang','currentLang','courseInfo']), + isAiTranslate () { + return this.courseInfo?.aiSet == 1 && this.courseInfo?.aiTranslate == 1 && this.selectableLang && this.selectableLang.length > 0; + } + }, created() { - + // ai播放器相关 + this.SET_currentLang(''); }, mounted() { + console.log('---',this.isAiTranslate,this.courseInfo,'courseInfo'); this.videoDom = this.$refs.video; this.videoDom.focus({preventScroll: true}); let speedValue=localStorage.getItem('boe_video_speed'); @@ -317,7 +358,8 @@ export default { } setInterval(() => { - console.log('当前状态:',this.currentProgress,this.isDrag,this.videoDom.currentTime , this.videoDom.duration) + this.SET_duration(this.videoDom.duration); + console.log('当前状态:',localStorage.getItem('videoProgressData'),this.currentProgress,this.isDrag,this.videoDom.currentTime , this.videoDom.duration) // 视频播放时本地记录视频实时播放时长,视频设置了禁止拖动时执行 if(!this.isDrag){ var time = localStorage.getItem('videoProgressData') @@ -372,6 +414,7 @@ export default { //if() //console.log(this.videoDom.readyState,'this.videoDom.readyState'); }, 1000); + // 视频dom监听器,用于控制鼠标的显示 this.videoDom.addEventListener("mousemove", () => { this.isCursorStatic = false; @@ -411,6 +454,13 @@ export default { // }); }, methods: { + // ai播放器相关 + ...mapMutations({ + SET_currentLang: 'video/SET_currentLang', + SET_currentTime: 'video/SET_currentTime', + SET_selectableLang: 'video/SET_selectableLang', + SET_duration: 'video/SET_duration', + }), //当视频由于需要缓冲下一帧而停止,解决一直计时的问题 onWaiting(){ console.log('触发了onWairing'); @@ -624,6 +674,8 @@ export default { }, onAudioTimeUpdate() { const currentTime = this.$refs.video.currentTime; + // ai播放器相关 + this.SET_currentTime(currentTime) this.$emit('onTimeUpdate', currentTime); }, /** @@ -641,9 +693,77 @@ export default { this.$emit('onFullscreen',false);//全屏 } } + }, + /** ai播放器相关 + * 切换字幕 + */ + toggleSubtitle(value) { + if (this.videoDom && this.videoDom.textTracks && this.videoDom.textTracks.length >0) { + if (!value) { + // 关闭字幕 + this.videoDom.textTracks[this.videoDom.textTracks.length - 1].mode = 'hidden'; + } else { + // 打开字幕 + this.videoDom.textTracks[this.videoDom.textTracks.length - 1].mode = 'showing'; + } + } + }, + /** ai播放器相关 + * 切换字幕语言 + */ + changeLang(item = {}) { + this.SET_currentLang(item.srclang); + this.currentLangLabel = item.label; + console.log("changeLang",item); + // 先移除所有字幕轨道 + Array.from(this.videoDom.querySelectorAll('track')).forEach(t => t.remove()); + if(!item.vttContent){ + console.log("字幕内容为空!") + return; + } + if(!item.srcUrl){ + try{ + const blob = new Blob([item.vttContent], { type: 'text/vtt' }); + item.srcUrl = URL.createObjectURL(blob); + }catch(e){ + console.log("字幕格式错误",e) + } + } + const trackEl = document.createElement('track'); + trackEl.kind = 'subtitles'; + trackEl.srclang = item.srclang; + trackEl.label = item.label; + trackEl.src = item.srcUrl; + trackEl.default = true; // 确保字幕默认启用 + // 使用箭头函数保持this上下文 + trackEl.addEventListener('load', () => { + console.log('字幕加载成功!'); + // console.log('#########Track cues:', trackEl.track.cues); + }); + + trackEl.addEventListener('error', () => { + console.error('字幕加载失败!'); + }); + + // 确保视频已加载到可添加轨道的状态 + if (this.videoDom.readyState >= 1) { + this.videoDom.appendChild(trackEl); + this.videoDom.textTracks[this.videoDom.textTracks.length - 1].mode = 'showing'; + } else { + this.videoDom.addEventListener('loadedmetadata', () => { + this.videoDom.appendChild(trackEl); + this.videoDom.textTracks[this.videoDom.textTracks.length - 1].mode = 'showing'; + }, { once: true }); + } + }, + seekToTime(time) { + if (!this.videoDom) return; + this.videoDom.currentTime = time + 0.01; + this.isPlaying = true; + this.videoDom.play(); + }, - } }, watch: { currentVolume: function () { @@ -668,9 +788,37 @@ export default { // } // }, src: function () { - // 当视频地址变更时,重载视频 + // 当视频地址变更时,先重置字幕再重载视频 + this.isPlaying = false; + // 重置字幕相关状态 + this.SET_currentLang(''); + this.currentLangLabel = ''; + + // 移除所有现有字幕轨道元素 + Array.from(this.videoDom.querySelectorAll('track')).forEach(t => t.remove()); + + // 更彻底地清除字幕:重置所有textTracks + Array.from(this.videoDom.textTracks).forEach(track => { + track.mode = 'hidden'; + // 尝试移除所有cues(浏览器支持的话) + if (track.cues) { + while (track.cues.length > 0) { + track.cues.remove(0); + } + } + }); + + // 重载视频 this.videoDom.load(); - this.isPlaying = false + + // 如果有默认语言且支持AI翻译,重新设置字幕 + // if (this.isAiTranslate && this.selectableLang && this.selectableLang.length > 0) { + // // 找到默认语言或第一个可用语言 + // const defaultLang = this.selectableLang.find(lang => lang.srclang === 'zh-CN') || this.selectableLang[0]; + // if (defaultLang) { + // this.changeLang(defaultLang); + // } + // } }, }, }; @@ -907,6 +1055,12 @@ export default { color: #fff; margin-bottom: 0.5rem; } +.box-aiTranslate{ + display: flex; + align-items: center; + justify-content: center; + gap: 0.4rem; +} @media (device-width: 100vw) { .player-controls-btn .player-controls-icon { /* height: 26px; */ @@ -918,4 +1072,22 @@ export default { height: 100px; } } +video::cue { + /* color: #fff; */ + /* background-color: transparent; */ + /* font-size: 0.85em; */ + /* font-family: 'Arial', sans-serif; + -webkit-text-stroke: 4px #000; + text-stroke: 4px #000; */ + + /* text-shadow: + 2px 2px 0 #000, + -2px 2px 0 #000, + 2px -2px 0 #000, + -2px -2px 0 #000, + 0 2px 0 #000, + 2px 0 0 #000, + 0 -2px 0 #000, + -2px 0 0 #000; */ +} diff --git a/src/data/pages.js b/src/data/pages.js index 87de1d28..3f1b8274 100644 --- a/src/data/pages.js +++ b/src/data/pages.js @@ -122,6 +122,8 @@ export const iframes=[ {title:'课程管理', path:'/iframe/course/manages',hidden:false,component:'course/ManageList'}, {title:'课程管理新版', path:'/iframe/course/manage-remote',hidden:false,component:'course/ManageListRemote'}, {title:'课程管理新版', path:'/iframe/course/coursemanage-remote',hidden:false,component:'course/CourseManageRemote'}, + {title:'ai摘要', path:'/iframe/course/aiAbstract',hidden:false,component:'course/aiSet/aiAbstract'}, + {title:'ai翻译', path:'/iframe/course/aiTranslate',hidden:false,component:'course/aiSet/aiTranslate'}, {title:'考试试题管理', path:'/iframe/exam/questions',hidden:false,component:'exam/Question'}, {title:'查看答卷', path:'/iframe/exam/viewanswer',hidden:false,component:'exam/viewAnswer'}, {title:'考试试卷管理', path:'/iframe/exam/papers',hidden:false,component:'exam/TestPaper'}, diff --git a/src/security.js b/src/security.js index 9788cc03..a0fccc63 100644 --- a/src/security.js +++ b/src/security.js @@ -29,6 +29,19 @@ router.beforeEach((to, from, next) => { // 在免登录白名单,直接进入 next() }else{ + // if (!store.getters.init) { + // store.commit('app/SET_INITDATA',true); + // let myRouters=routers(); + // store.dispatch('GenerateRoutes',{routers:myRouters}).then(accessRoutes=>{ + // console.log('accessRoutes::',accessRoutes) + // router.addRoutes(accessRoutes) // 动态添加可访问路由表 + // next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 + // }); + // } else { + // to.meta.keepAlive = true + // next(); + // } + // return; if(getToken()){ if(to.path === '/login'){ // 如果是外部用户,把配置的路由跳转到个人中心 diff --git a/src/store/getters.js b/src/store/getters.js index 999ef0c6..a9956d27 100644 --- a/src/store/getters.js +++ b/src/store/getters.js @@ -28,5 +28,12 @@ const getters = { studyTaskCount:state => state.user.studyTaskCount, praisesUnicom:state =>state.pdf.praisesUnicom, favoritesUnicom:state =>state.pdf.favoritesUnicom, + // ai播放器相关 + selectAllLang:state => state.video.selectAllLang, + selectableLang:state => state.video.selectableLang, + currentLang:state => state.video.currentLang, + currentTime:state => state.video.currentTime, + courseInfo:state => state.video.courseInfo, + duration:state => state.video.duration, } export default getters diff --git a/src/store/index.js b/src/store/index.js index ff0d0868..a44d8dba 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -12,6 +12,7 @@ import resOwner from './modules/resOwner' import majorType from './modules/majorType' import orgDomain from './modules/orgDomain' import pdf from './modules/pdf' +import video from './modules/video' // ai播放器相关 Vue.use(Vuex) @@ -27,7 +28,8 @@ const store = new Vuex.Store({ resOwner, majorType, orgDomain, - pdf + pdf, + video }, getters }) diff --git a/src/store/modules/video.js b/src/store/modules/video.js new file mode 100644 index 00000000..7227e9e7 --- /dev/null +++ b/src/store/modules/video.js @@ -0,0 +1,171 @@ +// ai播放器相关 + +/** + * +selectAllLang: [ + { + key: 'ZH_CN', + srclang: 'zh-CN', + label: '中文', + name: '中文', + }, + { + key: 'EN_US', + srclang: 'en-US', + label: '英语', + name: 'English', + }, + { + key: 'JA_JP', + srclang: 'ja-JP', + label: '日语', + name: '日本語', + }, + { + key: 'KO_KR', + srclang: 'ko-KR', + label: '韩语', + name: '한국어', + }, + { + key: 'FR_FR', + srclang: 'fr-FR', + label: '法语', + name: 'français', + }, + { + key: 'DE_DE', + srclang: 'de-DE', + label: '德语', + name: 'Deutsch', + }, + { + key: 'ES_ES', + srclang: 'es-ES', + label: '西班牙语', + name: 'español', + }, + { + key: 'RU_RU', + srclang: 'ru-RU', + label: '俄语', + name: 'русский', + }, + { + key: 'PT_BR', + srclang: 'pt-BR', + label: '葡萄牙语', + name: 'português', + }, + { + key: 'IT_IT', + srclang: 'it-IT', + label: '意大利语', + name: 'italiano', + }, + { + key: 'AR_SA', + srclang: 'ar-SA', + label: '阿拉伯语', + name: 'العربية', + }, + { + key: 'TH_TH', + srclang: 'th-TH', + label: '泰语', + name: 'ไทย', + }, + { + key: 'VI_VN', + srclang: 'vi-VN', + label: '越南语', + name: 'tiếng Việt', + }, + { + key: 'ID_ID', + srclang: 'id-ID', + label: '印度尼西亚语', + name: 'Bahasa Indonesia', + }, + { + key: 'HI_IN', + srclang: 'hi-IN', + label: '印地语', + name: 'हिन्दी', + } + ], // 全部语言列表 +*/ + +const state = { + selectAllLang: [ + { + key: 'ZH_CN', + srclang: 'zh-CN', + label: '中文', + name: '中文', + }, + { + key: 'EN_US', + srclang: 'en-US', + label: '英语', + name: 'English', + }, + { + key: 'VI_VN', + srclang: 'vi-VN', + label: '越南语', + name: 'tiếng Việt', + }, + { + key: 'ES_ES', + srclang: 'es-ES', + label: '西班牙语', + name: 'español', + }, + ], // 一期语言列表 + selectableLang: [], // 可选语言列表+字幕信息 + currentLang: '', // 当前选中语言 + currentTime: -1, // 当前视频时间 + courseInfo: {}, + duration: 0, // 视频时长 +} + +const mutations = { + SET_currentLang: (state, lang) => { + state.currentLang = lang + }, + SET_selectableLang: (state, list = []) => { + let selectableLang = [] + list.forEach(item => { + let selectItem = state.selectAllLang.find(selectItem => selectItem.srclang === item.language) + if (selectItem) { + selectableLang.push({ + ...item, + ...selectItem, + }) + } + }) + state.selectableLang = selectableLang + }, + SET_currentTime: (state, time) => { + state.currentTime = time + }, + SET_courseInfo: (state, info) => { + state.courseInfo = info + }, + SET_duration: (state, duration) => { + state.duration = duration + }, +} + +const actions = { + +} + +export default { + namespaced: true, + state, + mutations, + actions +} + diff --git a/src/views/course/ManageList.vue b/src/views/course/ManageList.vue index d0c2b60d..f6f86a0e 100644 --- a/src/views/course/ManageList.vue +++ b/src/views/course/ManageList.vue @@ -60,101 +60,108 @@
搜索 - 重置 + 重置
- - - 新建课程 - + + + + 新建课程 + 设置语种 + 开启AI处理 +
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +
@@ -287,6 +294,236 @@
+ + + + +
+
请选择课程所支持语种
+ + + +
+
+ 注:仅支持对已开启AI处理的课程进行批量语种设置;所选的课程中有{{languageSetting.aiSetNoNum}}个未开启AI处理的课程,以上配置仅对{{languageSetting.aiSetNum}}个已开启AI处理的课程生效。 +
+ +
+ + + +
+ +
+ + + + + AI处理: + + + {{ aiProcessSetting.aiSet === 1 ? '开启' : '关闭' }} + + +
+ +
+ +
+ + + + + AI摘要: + + + {{ aiProcessSetting.aiAbstract === 1 ? '开启' : '关闭' }} + + + +
+ + +
+ + + + + AI文稿: + + + {{ aiProcessSetting.aiDraft === 1 ? '开启' : '关闭' }} + + + +
+ + +
+ + + + + AI翻译语种: + + + + +
+
+ + +
+ 注:已跳过{{aiProcessSetting.aiSetNum}}个已开启AI处理的课程,仅更新剩余{{aiProcessSetting.aiSetNoNum}}个 +
+
+ + 取消 + 确认 + +
+ + + +
+ + +
+ + + + + AI功能状态: + + + {{ aiSetting.aiSet === 1 ? '已开放' : '未开放' }} + + +
+
+ +
+ AI摘要状态: + + {{ aiSetting.aiAbstract === 1 ? '已上架' : '已下架' }} + +
+ + {{ aiSetting.aiAbstract === 1 ? '下架' : '上架' }} + + 编辑 +
+
+ + +
+ AI文稿状态: + + {{ aiSetting.aiDraft === 1 ? '已上架' : '已下架' }} + +
+ + {{ aiSetting.aiDraft === 1 ? '下架' : '上架' }} + +
+
+ + +
+ AI翻译状态: + + {{ aiSetting.aiTranslate === 1 ? '已上架' : '已下架' }} + +
+ + {{ aiSetting.aiTranslate === 1 ? '下架' : '上架' }} + + 编辑 +
+
+ + +
+ 支持语种: +
+
+ {{ getLanguageName(lang) }} + 已下架 +
+
+ + + +
+
+
+ + 取消 + 确认 + +
@@ -299,7 +536,7 @@ import auditCourse2 from '@/components/Course/auditCourse2.vue'; import adminPage from '@/components/Administration/adminPage.vue'; import apiResowner from '../../api/modules/resowner.js'; import apiType from '../../api/modules/type.js' -import {courseType} from '../../utils/tools.js'; +import {courseType, deepCopy} from '../../utils/tools.js'; import apiCourse from '../../api/modules/course.js'; // import {resOwnerIndexName,sysTypeIndexName} from '@/utils/type.js'; import { mapGetters,mapActions } from 'vuex'; @@ -307,8 +544,9 @@ import apiUserbasic from "@/api/boe/userbasic.js" export default { name: 'manageCourse', components: {courseForm, manager, auditCourse1, auditCourse2,adminPage}, + // ai播放器相关 computed: { - ...mapGetters(['resOwnerMap','sysTypeMap','userInfo']), + ...mapGetters(['resOwnerMap','sysTypeMap','userInfo', 'selectAllLang']), }, data() { return { @@ -392,9 +630,38 @@ export default { }, extendRefId:'', extendRefType:'', + // ai播放器相关 + aiPermission: false, + selectedCourses: [], //已选课程 + languageSetting: { // 设置语种弹框 + dlgShow: false, + languageCode: ['zh-CN', 'en-US'] // 默认选中的语种 + }, + aiProcessSetting: { // 开启AI处理弹框 + dlgShow: false, + aiSet: 1, + aiAbstract: 1, + aiDraft: 1, + languageCode: ['zh-CN', 'en-US'] // 默认选中的语种 + }, + aiSetting: { // AI设置弹框 + dlgShow: false, + courseId: '', + aiSet: 1, + aiAbstract: 1, // 1:上架 0:下架 + aiDraft: 1, // 1:上架 0:下架 + aiTranslate: 1, // 1:上架 0:下架 + languageCode: ['zh-CN', 'en-US', 'vi-VN'] // 支持的语种 + }, + aiSetTip: '是否将课程进行AI处理', //提示信息 + aiAbstractTip: '一键提炼课程视频核心要点,助力学员课前高效掌握重点,快速筛选学习资源', // 提示信息 + aiDraftTip: '分段展示视频内容并精准同步时间轴,实现视频进度与文稿双向定位,学习内容触手可及', //提示信息 + aiTranslateTip: '智能转换视频字幕与语音为多语种,支持全球学员按需切换语言,打破学习边界', // 提示信息 }; }, mounted() { + // ai播放器相关 + this.getAiPermission(); this.getAudiences() let chooseFlag=this.$route.query.f; this.extendRefId=this.$route.query.refId; @@ -426,6 +693,7 @@ export default { }, methods: { + getAudiences(){ apiUserbasic.getInAudienceIds().then(res=>{ if (res.status == 200) { @@ -449,6 +717,7 @@ export default { inputOn() { this.$forceUpdate(); }, + // 置顶 setTop(row) { let params = { @@ -885,6 +1154,150 @@ export default { saveNewCatalogZhang() { this.catalogs.addNewZhang = false; }, + + + // ai播放器相关 + getLanguageName(lang){ + return this.selectAllLang.find(item => item.srclang === lang)?.label || ''; + }, + handleSelectionChange(val){ + this.selectedCourses = val; + console.log(val); + }, + // 获取选中课程的AI信息 + getAIInfoByList(list = []) { + let selectNum = 0; // 选中的课程数量 + let aiSetNum = 0; // 已设置AI的课程数量 + let aiSetNoNum = 0; // 未设置AI的课程数量 + list.forEach(item => { + if(item.aiSet === 1){ + aiSetNum++; + }else{ + aiSetNoNum++; + } + }); + return { + selectNum, + aiSetNum, + aiSetNoNum + } + }, + // AI设置 + setAI(row) { + console.log('row', row); + this.aiSetting = { + dlgShow: true, + ...row + }; + }, + changeAIKey(key) { + this.aiSetting[key] = this.aiSetting[key] === 1 ? 0 : 1; + }, + // 确认AI设置 + confirmAISetting() { + const item = deepCopy(this.aiSetting); + item.languageStatus = item.aiSet; + item.languageCode = item.languageCode || []; + if (!item.languageCode.includes('zh-CN')) { + item.languageCode.unshift('zh-CN'); // 默认添加中文 去重 + } + this._benchAiSet([item], (res) => { + this.$message.success('AI设置保存成功'); + this.aiSetting.dlgShow = false; + // 可以选择是否刷新列表数据 + this.searchData(); + }, (res) => { + this.$message.error('AI设置保存失败!'); + }) + }, + setLanguage() { + if (this.selectedCourses.length > 0) { + this.languageSetting = {...{ + dlgShow: true, + languageCode: ['zh-CN', 'en-US'] // 默认选中的语种 + }, ...this.getAIInfoByList(this.selectedCourses)} + } + }, + enableAI() { + // 开启AI处理按钮点击事件 + if (this.selectedCourses.length > 0) { + this.aiProcessSetting = {...{ + dlgShow: true, + aiSet: 1, + aiAbstract: 1, + aiDraft: 1, + languageCode: ['zh-CN', 'en-US'] // 默认选中的语种 + }, ...this.getAIInfoByList(this.selectedCourses)} + } + }, + // 批量设置语种 - 确认 + confirmLanguageSetting() { + const courseList = deepCopy(this.selectedCourses); + let languageCode = deepCopy(this.languageSetting.languageCode || []); + if (!languageCode.includes('zh-CN')) { + languageCode.unshift('zh-CN'); // 默认添加中文 去重 + } + courseList.forEach(item => { + item.languageCode = languageCode; + item.aiTranslate = item.aiSet; + item.languageStatus = item.aiSet; + }) + this._benchAiSet(courseList, (res) => { + this.$message.success('设置语种成功!'); + this.languageSetting.dlgShow = false; + // 可以选择是否刷新列表数据 + this.searchData(); + }, (res) => { + this.$message.error('设置语种失败!'); + }) + }, + + // 批量开启AI处理 - 确认 + confirmAiProcess() { + // 获取AI处理配置 + let { aiSet, aiAbstract, aiDraft, languageCode } = this.aiProcessSetting; + const courseList = deepCopy(this.selectedCourses); + languageCode = languageCode || []; + if (!languageCode.includes('zh-CN')) { + languageCode.unshift('zh-CN'); // 默认添加中文 去重 + } + courseList.forEach(item => { + item.aiSet = aiSet; + item.aiAbstract = aiAbstract; + item.aiDraft = aiDraft; + item.aiTranslate = aiSet; + item.languageStatus = aiSet; + item.languageCode = languageCode; + }) + this._benchAiSet(courseList, (res) => { + this.$message.success('开启AI处理成功!'); + this.aiProcessSetting.dlgShow = false; + // 可以选择是否刷新列表数据 + this.searchData(); + }, (res) => { + this.$message.error('开启AI处理失败!'); + }) + }, + + _benchAiSet(courseList, successCB, failCB) { + apiCourse.benchAiSet({courseList}).then(res => { + if(res.status === 200){ + successCB && successCB(res); + }else{ + failCB && failCB(res); + } + }) + }, + getAiPermission() { + apiCourse.listByUser({}).then(res => { + console.log('res', res); + if(res.code === 200){ + let index = res.data.findIndex(item => item.permissionCode === 'KjbAiSetCode'); + this.aiPermission = index !== -1; + console.log('index', index, this.aiPermission); + } + }) + } } }; @@ -953,4 +1366,22 @@ export default { .el-dialog__body { overflow: hidden; } + + .form-item{ + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 10px; + } +.tips { + color: #f56c6c; + font-size: 12px; + margin: 10px 0; + line-height: 1.5; +} +.languages-list{ + display: flex; + flex-wrap: wrap; + gap: 20px; +} diff --git a/src/views/course/aiSet/aiAbstract.vue b/src/views/course/aiSet/aiAbstract.vue new file mode 100644 index 00000000..2efd2c32 --- /dev/null +++ b/src/views/course/aiSet/aiAbstract.vue @@ -0,0 +1,245 @@ + + + + + diff --git a/src/views/course/aiSet/aiTranslate.vue b/src/views/course/aiSet/aiTranslate.vue new file mode 100644 index 00000000..28f146b6 --- /dev/null +++ b/src/views/course/aiSet/aiTranslate.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/src/views/portal/course/Index.vue b/src/views/portal/course/Index.vue index 336bc801..9dd9ff78 100644 --- a/src/views/portal/course/Index.vue +++ b/src/views/portal/course/Index.vue @@ -206,8 +206,25 @@
开课时间:{{ cinfo.startTime }}
- -
+ +
+ +
+

课程摘要

+
{{ cinfo.summaryContent }}
+
+ + 暂无数据 +
+
+ + 摘要 +
{{ cinfo.teacher }} @@ -217,13 +234,13 @@ {{ formatNum(cinfo.studies) }}人学习
-
+
- {{ toScore(cinfo.score) }}分 + {{ toScore(cinfo.score) }}分
-
未评分
+
未评分
@@ -2710,3 +2727,43 @@ a.custom2 { } } + + \ No newline at end of file diff --git a/src/views/study/coursenew.vue b/src/views/study/coursenew.vue index a6c6cbde..cd08ffc7 100644 --- a/src/views/study/coursenew.vue +++ b/src/views/study/coursenew.vue @@ -1,21 +1,21 @@ -