|
|
@@ -1,6 +1,7 @@
|
|
|
<template>
|
|
|
<!-- AI发展历程 -->
|
|
|
<div class="home-container">
|
|
|
+
|
|
|
<!-- 展开收起侧边栏 -->
|
|
|
<div
|
|
|
class="icon-expand"
|
|
|
@@ -106,12 +107,13 @@
|
|
|
</div>
|
|
|
<div class="box-video">
|
|
|
<video
|
|
|
- class="full-box-video"
|
|
|
+ class="full-box-video video-js vjs-default-skin"
|
|
|
:src="course.courseVideoPath"
|
|
|
controlsList="nodownload"
|
|
|
controls
|
|
|
@timeupdate="handleTimeUpdate"
|
|
|
@play="checkVideoPermission"
|
|
|
+ @seeked="handleSeeked"
|
|
|
ref="videoRef"
|
|
|
>
|
|
|
<source :src="course.courseVideoPath" type="video/mp4" />
|
|
|
@@ -131,7 +133,7 @@
|
|
|
<!-- 下一个视频 -->
|
|
|
<div class="caret-right" @click="playNextVideo">
|
|
|
<el-button type="warning" round
|
|
|
- >下一节
|
|
|
+ >下一节
|
|
|
<img :src="rightImg" alt="Right" />
|
|
|
</el-button>
|
|
|
</div>
|
|
|
@@ -262,7 +264,17 @@
|
|
|
<script setup>
|
|
|
import { ref, onMounted, onUnmounted, onBeforeUnmount,computed} from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
-import { Expand, Fold, Memo } from '@element-plus/icons-vue'
|
|
|
+import videojs from 'video.js';
|
|
|
+import 'video.js/dist/video-js.css';
|
|
|
+import '@videojs/http-streaming'; // 支持HLS分片
|
|
|
+const videoPlayer = ref(null);
|
|
|
+let player = null;
|
|
|
+
|
|
|
+import {
|
|
|
+ Expand,
|
|
|
+ Fold,
|
|
|
+ Memo
|
|
|
+} from '@element-plus/icons-vue'
|
|
|
import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
|
|
|
import {
|
|
|
ElMessage,
|
|
|
@@ -299,6 +311,7 @@ const toggleDrawer = () => {
|
|
|
drawerVisible.value = !drawerVisible.value
|
|
|
}
|
|
|
|
|
|
+
|
|
|
// 返回上一页
|
|
|
const goBack = () => {
|
|
|
router.go(-1)
|
|
|
@@ -318,11 +331,11 @@ const videoPathMap = ref({})
|
|
|
|
|
|
//课程小节字典(需要新加接口调取字典)
|
|
|
const menuDict = ref({
|
|
|
- 1: '课前回顾',
|
|
|
- 2: '课程引入',
|
|
|
- 3: '知识讲解',
|
|
|
- 4: '趣味实操',
|
|
|
- 5: '课程总结'
|
|
|
+ "1": "课前回顾",
|
|
|
+ "2": "课程引入",
|
|
|
+ "3": "知识讲解",
|
|
|
+ "4": "趣味实操",
|
|
|
+ "5": "课程总结",
|
|
|
})
|
|
|
|
|
|
// 渲染 课程数据结构 以及 视频
|
|
|
@@ -335,37 +348,44 @@ onMounted(async () => {
|
|
|
console.log(res);
|
|
|
courseList.value = res.data
|
|
|
//课程数据
|
|
|
- courseList.value.forEach((courseTemp, index) => {
|
|
|
- let menuIndex = courseTemp.courseLabel + '-' + (index + 1)
|
|
|
- // 填充大纲小节
|
|
|
- let menu = menuItems.value.find(
|
|
|
- menu => courseTemp.courseLabel === menu.index
|
|
|
- )
|
|
|
- if (menu) {
|
|
|
- //小节
|
|
|
- menu.children = menu.children || []
|
|
|
+ courseList.value.forEach((courseTemp,index) => {
|
|
|
+ let menuIndex = courseTemp.courseLabel + '-' + (index+1);
|
|
|
+ //填充大纲小节
|
|
|
+ let menu = menuItems.value.find(menu => courseTemp.courseLabel === menu.index);
|
|
|
+
|
|
|
+ if (menu){//小节
|
|
|
+ menu.children = menu.children || [];
|
|
|
menu.children.push({
|
|
|
key: menuIndex,
|
|
|
index: menuIndex,
|
|
|
title: courseTemp.courseName
|
|
|
})
|
|
|
- } else {
|
|
|
- //大节
|
|
|
- menuItems.value.push({
|
|
|
+ }else {//大节
|
|
|
+ let temp = {
|
|
|
key: menuIndex,
|
|
|
index: courseTemp.courseLabel,
|
|
|
title: menuDict.value[courseTemp.courseLabel]
|
|
|
- })
|
|
|
+ }
|
|
|
+ if (courseTemp.courseLabel === "3"){
|
|
|
+ temp.children = [{
|
|
|
+ key: menuIndex,
|
|
|
+ index: menuIndex,
|
|
|
+ title: courseTemp.courseName
|
|
|
+ }]
|
|
|
+ }
|
|
|
+ menuItems.value.push(temp)
|
|
|
}
|
|
|
|
|
|
- courseTemp['key'] = menuIndex
|
|
|
+ courseTemp["key"] = menuIndex;
|
|
|
videoPathMap.value[menuIndex] = courseTemp
|
|
|
|
|
|
+
|
|
|
//确定默认课程
|
|
|
if (index === 0) {
|
|
|
- course.value = courseTemp
|
|
|
+ course.value = courseTemp;
|
|
|
}
|
|
|
})
|
|
|
+
|
|
|
} catch (error) {
|
|
|
console.error('获取课程数据失败:', error)
|
|
|
}
|
|
|
@@ -377,7 +397,7 @@ onMounted(async () => {
|
|
|
}
|
|
|
// 在视频元素上添加时间更新事件监听
|
|
|
if (videoRef.value) {
|
|
|
- // videoRef.value.addEventListener('timeupdate', onTimeUpdate)
|
|
|
+ videoRef.value.addEventListener('timeupdate', handleTimeUpdate)
|
|
|
}
|
|
|
})
|
|
|
|
|
|
@@ -388,7 +408,7 @@ const handleClose = () => {}
|
|
|
// 菜单选择的处理函数
|
|
|
const handleSelect = index => {
|
|
|
//测试账号禁用视频
|
|
|
- if (disableVideo(index)) return
|
|
|
+ if (disableVideo(index))return;
|
|
|
|
|
|
const findTitle = items => {
|
|
|
for (const item of items) {
|
|
|
@@ -409,9 +429,9 @@ const handleSelect = index => {
|
|
|
course.value = videoPathMap.value[index]
|
|
|
// 切换标题后,关闭抽屉
|
|
|
drawerVisible.value = false
|
|
|
- } else {
|
|
|
+ }else {
|
|
|
//视频不存在
|
|
|
- Message().notifyWarning('视频不存在!', true)
|
|
|
+ Message().notifyWarning('视频不存在!', true);
|
|
|
}
|
|
|
|
|
|
//测试账号禁用视频
|
|
|
@@ -437,7 +457,7 @@ const flattenMenuItems = () => {
|
|
|
// 播放下一个视频
|
|
|
const playNextVideo = () => {
|
|
|
//测试账号禁用视频
|
|
|
- if (disableVideo()) return
|
|
|
+ if (disableVideo())return;
|
|
|
|
|
|
const allIndices = flattenMenuItems()
|
|
|
const currentIndexInList = allIndices.indexOf(course.value.key)
|
|
|
@@ -453,16 +473,16 @@ const playNextVideo = () => {
|
|
|
}
|
|
|
|
|
|
// 重置
|
|
|
- pausedIndices = ref({ time: [], newTime: [] })
|
|
|
- userMessage = ref('')
|
|
|
- messageHistory = ref([])
|
|
|
+ pausedIndices.value = [];
|
|
|
+ userMessage.value = ''
|
|
|
+ messageHistory.value = []
|
|
|
}
|
|
|
|
|
|
// 切换视频
|
|
|
// 播放上一个视频
|
|
|
const playPreviousVideo = () => {
|
|
|
//测试账号禁用视频
|
|
|
- if (disableVideo()) return
|
|
|
+ if (disableVideo())return;
|
|
|
|
|
|
const allIndices = flattenMenuItems()
|
|
|
const currentIndexInList = allIndices.indexOf(course.value.key)
|
|
|
@@ -481,7 +501,7 @@ const playPreviousVideo = () => {
|
|
|
// 尝试播放视频,处理浏览器自动播放限制
|
|
|
const tryPlayVideo = () => {
|
|
|
//测试账号禁用视频
|
|
|
- if (disableVideo()) return
|
|
|
+ if (disableVideo())return;
|
|
|
|
|
|
const playPromise = videoRef.value.play()
|
|
|
if (playPromise !== undefined) {
|
|
|
@@ -489,6 +509,16 @@ const tryPlayVideo = () => {
|
|
|
console.error('视频播放失败,可能是浏览器自动播放限制:', error)
|
|
|
})
|
|
|
}
|
|
|
+ player = videojs(videoPlayer.value, {
|
|
|
+ controls: true,
|
|
|
+ sources: [
|
|
|
+ {
|
|
|
+ src: course.value.courseVideoPath,
|
|
|
+ type: 'video/mp4'
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ preload:'metadata' // 仅加载元数据,避免预加载整个文件
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
// 检查视频播放权限
|
|
|
@@ -498,45 +528,31 @@ const checkVideoPermission = () => {
|
|
|
videoRef.value.pause()
|
|
|
}
|
|
|
}
|
|
|
- //记录已暂停的内容
|
|
|
- setVideoStop()
|
|
|
-}
|
|
|
+};
|
|
|
|
|
|
//禁用视频
|
|
|
const disableVideo = (index = course.value.key) => {
|
|
|
- let dis = [
|
|
|
- '3-7',
|
|
|
- '3-8',
|
|
|
- '3-9',
|
|
|
- '3-10',
|
|
|
- '3-11',
|
|
|
- '3-12',
|
|
|
- '3-13',
|
|
|
- '4-14',
|
|
|
- '5-15'
|
|
|
- ]
|
|
|
-
|
|
|
- if (
|
|
|
- localStorage.getItem('userName') === 'aiTest' &&
|
|
|
- dis.indexOf(index) !== -1
|
|
|
- ) {
|
|
|
+ let dis = ["3-7","3-8","3-9","3-10","3-11","3-12","3-13","4-14","5-15"]
|
|
|
+
|
|
|
+ if (localStorage.getItem('userName') === "aiTest" &&
|
|
|
+ dis.indexOf(index) !== -1) {
|
|
|
if (videoRef.value) {
|
|
|
// 记录当前播放时间
|
|
|
videoRef.value.pause()
|
|
|
// 阻止用户跳转到新的时间点,将播放时间重置为之前的时间
|
|
|
- videoRef.value.currentTime = 0
|
|
|
+ videoRef.value.currentTime = 0;
|
|
|
}
|
|
|
//提示禁用// 显示消息框
|
|
|
- Message().notifyWarning('您的账号并未开放此课程!', true)
|
|
|
- return true
|
|
|
+ Message().notifyWarning('您的账号并未开放此课程!', true);
|
|
|
+ return true;
|
|
|
}
|
|
|
- return false
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
// 视频 ref
|
|
|
const videoRef = ref(null)
|
|
|
// 记录已经暂停过的时间点索引
|
|
|
-let pausedIndices = ref({ time: [], newTime: [] })
|
|
|
+let pausedIndices = ref([])
|
|
|
// 试题弹框显示状态
|
|
|
const questionDialogVisible = ref(false)
|
|
|
// 当前显示的试题
|
|
|
@@ -560,17 +576,15 @@ const handleTimeUpdate = () => {
|
|
|
|
|
|
if (!course.value.courseConfigList) return
|
|
|
course.value.courseConfigList.forEach(courseCofig => {
|
|
|
+
|
|
|
//暂停时间
|
|
|
let time = courseCofig.ccTime
|
|
|
// 检查是否到达时间点且还未暂停过
|
|
|
- let timeIndex = pausedIndices.value.time.indexOf(time)
|
|
|
+ if (currentTime === time && !pausedIndices.value.includes(time) ) {
|
|
|
|
|
|
- if (
|
|
|
- currentTime === time &&
|
|
|
- (timeIndex === -1 ||
|
|
|
- Date.now() - pausedIndices.value.newTime[timeIndex] > 1000)
|
|
|
- ) {
|
|
|
videoRef.value.pause()
|
|
|
+ //记录暂停时间
|
|
|
+ pausedIndices.value.push(currentTime)
|
|
|
|
|
|
// 显示对应的问题
|
|
|
if (courseCofig.ccQuestContent) {
|
|
|
@@ -586,27 +600,16 @@ const handleTimeUpdate = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+// 视频完成拖动进度条时触发的方法
|
|
|
+const handleSeeked = () => {
|
|
|
+ pausedIndices.value = [];
|
|
|
+}
|
|
|
+
|
|
|
// 关闭试题弹框
|
|
|
const handleCloseQuestionDialog = () => {
|
|
|
questionDialogVisible.value = false
|
|
|
// 继续播放视频
|
|
|
videoRef.value.play()
|
|
|
-
|
|
|
- //记录已暂停的内容
|
|
|
- setVideoStop()
|
|
|
-}
|
|
|
-
|
|
|
-//记录已暂停的内容
|
|
|
-const setVideoStop = () => {
|
|
|
- const currentTime = parseInt(videoRef.value.currentTime)
|
|
|
- let timeIndex = pausedIndices.value.time.indexOf(currentTime)
|
|
|
- if (timeIndex === -1) {
|
|
|
- pausedIndices.value.time.push(currentTime)
|
|
|
- pausedIndices.value.newTime.push(Date.now())
|
|
|
- } else {
|
|
|
- pausedIndices.value.time[timeIndex] = currentTime
|
|
|
- pausedIndices.value.newTime[timeIndex] = Date.now()
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
// 提交答案
|
|
|
@@ -616,9 +619,6 @@ const handleSubmitAnswer = () => {
|
|
|
// 继续播放视频
|
|
|
videoRef.value.play()
|
|
|
selectedOption.value = null
|
|
|
-
|
|
|
- //记录已暂停的内容
|
|
|
- setVideoStop()
|
|
|
}
|
|
|
|
|
|
// 发送消息
|
|
|
@@ -678,7 +678,7 @@ const SearchInput = ref('')
|
|
|
const querySearch = (queryString, cb) => {
|
|
|
const sections = getAllCourseSections();
|
|
|
const results = queryString
|
|
|
- ? sections.filter(section =>
|
|
|
+ ? sections.filter(section =>
|
|
|
section.title.toLowerCase().includes(queryString.toLowerCase())
|
|
|
)
|
|
|
: sections;
|
|
|
@@ -829,6 +829,9 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
|
|
|
.el-menu ::v-deep(.el-sub-menu__title:hover .el-sub-menu__icon-arrow) {
|
|
|
color: black; // 与悬停状态的文字颜色保持一致
|
|
|
}
|
|
|
+.el-menu ::v-deep(.el-icon svg) {
|
|
|
+ font-size: rpx(9);
|
|
|
+}
|
|
|
|
|
|
.mb-2 {
|
|
|
color: white;
|
|
|
@@ -1007,6 +1010,7 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
|
|
|
height: rpx(300);
|
|
|
video.full-box-video {
|
|
|
height: rpx(280);
|
|
|
+ width: 67%;
|
|
|
margin: 0 auto;
|
|
|
border-radius: rpx(12);
|
|
|
object-fit: cover;
|
|
|
@@ -1084,9 +1088,9 @@ video::-webkit-media-controls-panel {
|
|
|
border: none;
|
|
|
border-radius: rpx(20);
|
|
|
background: linear-gradient(
|
|
|
- 135deg,
|
|
|
- $light-color,
|
|
|
- #d8bfd8
|
|
|
+ 135deg,
|
|
|
+ $light-color,
|
|
|
+ #d8bfd8
|
|
|
); // 柔和的蓝紫色渐变
|
|
|
overflow: hidden;
|
|
|
display: flex; // 添加 flex 布局
|