integrated_traffic_uniapp/pages/application/onlinexxks/video_study.vue

643 lines
20 KiB
Vue
Raw Normal View History

2024-03-25 19:16:41 +08:00
<template>
<view>
<!-- <cu-custom bgColor="bg-gradual-blueness" :isBack="true">-->
<!-- <block slot="backText">返回</block>-->
<!-- <block slot="content">学习详情</block>-->
<!-- </cu-custom>-->
<view id="fixed">
<view v-if="videoSrc">
<video
id="video"
:src="videoSrc"
:poster="videoPoster"
autoplay
controls
style="width: 100vw"
:show-progress="false"
:enable-progress-gesture="false"
@play="fnPlay"
@pause="fnPause"
@ended="fnEnded"
@timeupdate="fnTimeUpdate"
/>
</view>
<image
v-if="!videoSrc"
:src="baseImgPath+info.COVERPATH"
class="video_bg"
/>
<view class="title">
<text>{{ info.CURRICULUMNAME }}</text>
</view>
<view class="tabs">
<uv-tabs :list="[{name:'视频目录'},{name:'详情'}]" @change="tabsCurrent=$event.index" :scrollable="false"/>
</view>
</view>
<view class="show_container" v-show="tabsCurrent === 1">
<scroll-view scroll-y :style="{ height: `calc(100vh - ${scrollHeight})` }">
<view class="introduce">{{ info.CURRICULUMINTRODUCE }}</view>
<view class="image">
<image :src="baseImgPath+info.COVERPATH" alt=""/>
</view>
</scroll-view>
</view>
<view v-show="tabsCurrent === 0">
<scroll-view scroll-y :style="{ height: `calc(100vh - ${scrollHeight})` }">
<view class="video_container">
<view
class="video_list"
v-for="(item, index) in videoList"
:key="index"
>
<view class="video_list_title">
<image src="/static/study/copy-one.png"/>
<view>{{ item.NAME }}</view>
</view>
<view class="video_list_main">
<view v-if="item.nodes && item.nodes.length > 0">
<view
class="video_list_main_wrap"
v-for="(item1, index1) in item.nodes"
:key="index1"
@click="fnVideoSwitching(item1, 1, index, index1)"
>
<view class="video_list_main_wrap_tit line-1">
{{ item1.COURSEWARENAME }}
</view>
<view class="video_list_main_wrap_info">
<text class="fcb">进度{{ item1.percent }}%</text>
<text>{{ secondsCount(item1.VIDEOTIME) }}</text>
<image src="/static/study/play.png"/>
<button
class="cu-btn round bg-blue sm"
@click.stop="
fnNavigationExercises(item1.VIDEOCOURSEWARE_ID)
"
>
课后练习
</button>
</view>
</view>
</view>
<view v-else>
<view
class="video_list_main_wrap"
@click="fnVideoSwitching(item, 0, index, 0)"
>
<view class="video_list_main_wrap_tit line-1">
{{ item.COURSEWARENAME }}
</view>
<view class="video_list_main_wrap_info">
<text class="fcb">进度{{ item.percent }}%</text>
<text>{{ secondsCount(item.VIDEOTIME) }}</text>
<image src="/static/study/play.png"/>
<button
class="cu-btn round bg-blue sm"
@click.stop="fnNavigationExercises(item.VIDEOCOURSEWARE_ID)"
>
课后练习
</button>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import {basePath, loginUser, baseImgPath} from "@/common/tool";
let faceAuthTimer; // 人脸认证计时器
let throttleTimer;
let throttleFlag;
export default {
data() {
return {
baseImgPath,
CLASSCURRICULUM_ID: '',
CLASS_ID: '',
STUDENT_ID: '',
ISFACE: '',
info: {}, // 课程信息
videoList: [], // 视频列表
tabsCurrent: 0, // 当前tabs
randomTime: 0, // 随机人脸识别认证时间
// randomTime: new Date().getTime() + 60 * 1000, // 1分钟 随机人脸识别认证时间 测试
videoData: {
CURRICULUM_ID: '',
VIDEOCOURSEWARE_ID: '',
CHAPTER_ID: '',
VIDEOTIME: '',
}, // 当前播放视频的对象
index: 0, // 一级目录index
nodeIndex: 0, // 二级目录index
hasNodes: '0', // 是否存在二级目录1是 0否
changeVideoPlayTime: 0, // 当前视频播放进度
serverVideoPlayTime: 0, // 服务器的当前视频播放进度
videoSrc: "", // 视频地址
videoPoster: "", // 视频封面图
scrollHeight: '0px', // scroll-view减去的高度
2024-05-23 15:04:26 +08:00
videoContext: null, // 视频上下文
2024-05-20 18:03:21 +08:00
submitTimeWaitForCount: 0, // 提交时间等待次数
2024-03-25 19:16:41 +08:00
verification:false,// 是否人脸认证
}
},
onLoad(options) {
this.CLASSCURRICULUM_ID = options.CLASSCURRICULUM_ID;
this.CLASS_ID = options.CLASS_ID;
this.STUDENT_ID = options.STUDENT_ID;
2024-05-28 17:34:39 +08:00
this.ISFACE = options.ISFACE;
2024-03-25 19:16:41 +08:00
this.fnInit();
},
onReady() {
uni.createSelectorQuery()
.select("#fixed")
.boundingClientRect((data) => {
this.scrollHeight = data.height + 'px';
})
.exec();
},
mounted() {
uni.$on('verification', () => {
this.verification = true
})
},
beforeDestroy() {
uni.$off('verification')
},
watch: {
verification() {
if (this.verification) {
this.fnInit();
}
},
},
methods: {
post(url, data) {
return new Promise((resolve, reject) => {
if (data && data.loading !== false) {
uni.showLoading({
title: "加载中",
});
}
uni.request({
url: basePath + url,
data: {
CORPINFO_ID: loginUser.CORPINFO_ID,
USER_ID: loginUser.USER_ID,
...data,
},
method: "POST",
header: {
"Content-type": "application/x-www-form-urlencoded",
},
success: (res) => {
if (res.statusCode !== 200) {
uni.showToast({
title: "网络错误请重试," + res.statusCode,
icon: "error",
duration: 2000,
});
}
if (data && data.loading !== false) {
uni.hideLoading();
}
if (res.data.result === "success") {
resolve(res.data);
} else {
uni.showToast({
title: res.data.msg || "系统开小差了",
icon: "none",
duration: 2000,
});
reject(res.data);
}
},
fail: (err) => {
if (data && data.loading !== false) {
uni.hideLoading();
}
uni.showToast({
title: "网络错误请重试",
icon: "none",
duration: 2000,
});
reject(err);
},
});
});
},
throttle(func, wait = 500, immediate = true) {
if (immediate) {
if (!throttleFlag) {
throttleFlag = true
// 如果是立即执行则在wait毫秒内开始时执行
typeof func === 'function' && func()
throttleTimer = setTimeout(() => {
throttleFlag = false
}, wait)
}
} else if (!throttleFlag) {
throttleFlag = true
// 如果是非立即执行则在wait毫秒内的结束处执行
throttleTimer = setTimeout(() => {
throttleFlag = false
typeof func === 'function' && func()
}, wait)
}
},
secondsCount(second) {
if (!second) return 0;
const h = parseInt((second / 60 / 60) % 24, 10);
const m = parseInt((second / 60) % 60, 10);
const s = parseInt(second % 60, 10);
return (
(h < 10 ? "0" + h : h) +
":" +
(m < 10 ? "0" + m : m) +
":" +
(s < 10 ? "0" + s : s)
);
},
async fnInit() {
await this.fnGetData();
if (this.ISFACE === "1") {
if (this.verification) {
await this.fnGetVideoPlayInfo();
} else {
uni.showModal({
title: "温馨提示",
confirmText: "同意并继续",
content:
"重要提醒:尊敬的用户,根据规定我们会在您学习过程中多次进行人脸识别认证,为了保护您的隐私请您在摄像设备视野内确保衣冠整齐。",
success: (res) => {
if (res.cancel) {
uni.navigateBack();
}
},
});
}
}
},
async fnGetData() {
const resData = await this.post('/app/stagestudentrelation/getMyTask',{
CLASSCURRICULUM_ID: this.CLASSCURRICULUM_ID,
CLASS_ID: this.CLASS_ID,
STUDENT_ID: this.STUDENT_ID,
});
if (resData.pd.VIDEOLIST && resData.pd.VIDEOLIST.length > 0) {
resData.pd.VIDEOLIST.forEach((item) => {
if (item.nodes && item.nodes.length > 0) {
item.nodes.forEach((nodeItem) => {
let percent;
if (nodeItem.PLAYCOUNT > 0) {
percent = 100;
} else {
const resourceTime = parseFloat(nodeItem.RESOURCETIME || 0);
const videoTime = parseFloat(nodeItem.VIDEOTIME || 0);
const temp = Math.floor(
(resourceTime / videoTime) * 10000,
).toString();
percent = temp.substring(0, temp.length) / 100;
}
nodeItem.percent = percent;
});
} else {
let percent;
if (item.PLAYCOUNT > 0) {
percent = 100;
} else {
const resourceTime = parseFloat(item.RESOURCETIME || 0);
const videoTime = parseFloat(item.VIDEOTIME || 0);
const temp = Math.floor(
(resourceTime / videoTime) * 10000,
).toString();
percent = temp.substring(0, temp.length) / 100;
}
item.percent = percent;
}
});
}
this.info = resData.pd;
this.videoList = resData.pd.VIDEOLIST;
},
async fnVideoSwitching(videoData, hasNodes, index, index1) {
2024-05-23 15:04:26 +08:00
this.videoContext && this.videoContext.pause();
2024-05-20 18:03:21 +08:00
this.submitTimeWaitForCount = 0;
2024-03-25 19:16:41 +08:00
if (this.changeVideoPlayTime !== 0) {
await this.fnSubmitPlayTime("0", this.changeVideoPlayTime);
this.changeVideoPlayTime = 0;
}
this.videoData = videoData;
this.firstIndex = index;
this.nodeIndex = index1;
this.hasNodes = hasNodes.toString();
if (this.ISFACE === "1") {
await this.fnNavigationFaceAuth();
} else {
this.videoSrc = "";
this.videoPoster = "";
await this.fnGetVideoPlayInfo();
}
},
async fnGetVideoPlayInfo() {
const resData = await this.post('/app/audioOrVideo/getPlayInfo',{
VIDEOCOURSEWARE_ID: this.videoData.VIDEOCOURSEWARE_ID,
CURRICULUM_ID: this.videoData.CURRICULUM_ID,
});
this.videoSrc = resData.PLAYURLMP4 || resData.PLAYURL;
this.videoPoster = resData.COVERURL;
await this.fnGetVideoPlayProgress();
},
async fnGetVideoPlayProgress() {
const resData = await this.post('/app/coursestudyvideorecord/getVideoProgress',{
VIDEOCOURSEWARE_ID: this.videoData.VIDEOCOURSEWARE_ID,
CURRICULUM_ID: this.videoData.CURRICULUM_ID,
CLASS_ID: this.CLASS_ID,
STUDENT_ID: this.STUDENT_ID,
});
if (!resData.pd.RESOURCETIME) {
await this.fnSubmitPlayTime("0", 0);
}
2024-05-20 18:03:21 +08:00
this.serverVideoPlayTime = Math.floor(resData.pd.RESOURCETIME) || 0
2024-03-25 19:16:41 +08:00
if (this.ISFACE === "1") {
if (this.verification) {
this.fnCreateVideo();
} else {
this.fnCreateVideo();
}
} else {
this.fnCreateVideo();
}
},
fnCreateVideo() {
2024-05-23 15:04:26 +08:00
if (!this.videoContext) {
this.videoContext = uni.createVideoContext("video");
}
this.videoContext.play();
2024-05-23 15:40:03 +08:00
this.serverVideoPlayTime !== 0 && this.videoContext.seek(this.serverVideoPlayTime);
2024-03-25 19:16:41 +08:00
},
2024-05-23 15:04:26 +08:00
fnTimeUpdate(event) {
this.throttle(async () => {
2024-03-25 19:16:41 +08:00
const currentTime = event.detail.currentTime;
if (currentTime - this.serverVideoPlayTime >= 10) {
uni.navigateBack();
return;
}
2024-05-20 18:03:21 +08:00
//app视频自带bug判断当前视频进度与服务器进度差如果小于10秒则退出重新进
2024-05-23 15:40:03 +08:00
// if(currentTime !== 0 && Math.abs(currentTime - this.serverVideoPlayTime) > 10) {
// this.videoContext.pause();
// uni.showModal({
// title: "提示",
// content: "app开发环境问题偶现当前视频进度获取不准确为了防止进度被覆盖请退出重新进入",
// showCancel:false,
// success: (res) => {
// uni.navigateBack();
// return;
// },
// });
// }
2024-03-25 19:16:41 +08:00
this.changeVideoPlayTime = currentTime;
if (currentTime - this.serverVideoPlayTime >= 5) {
2024-05-20 18:03:21 +08:00
this.serverVideoPlayTime = currentTime;
this.submitTimeWaitForCount = ++this.submitTimeWaitForCount;
}
if (this.submitTimeWaitForCount === 6) {
this.submitTimeWaitForCount = 0;
2024-03-25 19:16:41 +08:00
await this.fnSubmitPlayTime("0", currentTime);
}
2024-05-23 15:04:26 +08:00
}, 1000);
2024-03-25 19:16:41 +08:00
},
async fnSubmitPlayTime(IS_END, RESOURCETIME) {
2024-05-20 18:03:21 +08:00
if(!this.videoData.VIDEOCOURSEWARE_ID) {
return
}
2024-03-25 19:16:41 +08:00
const resData = await this.post('/app/coursestudyvideorecord/save',{
USERNAME: loginUser.NAME,
VIDEOCOURSEWARE_ID: this.videoData.VIDEOCOURSEWARE_ID,
CURRICULUM_ID: this.videoData.CURRICULUM_ID,
CHAPTER_ID: this.videoData.CHAPTER_ID,
RESOURCETIME,
IS_END,
CLASS_ID: this.CLASS_ID,
CLASSCURRICULUM_ID: this.CLASSCURRICULUM_ID,
STUDENT_ID: this.STUDENT_ID,
loading: false,
});
this.serverVideoPlayTime = RESOURCETIME;
if (this.hasNodes === "1") {
let percent = 0;
if (resData.pd.PLAYCOUNT > 0) {
percent = 100;
} else {
const resourceTime = parseFloat(resData.pd.RESOURCETIME || 0);
const videoTime = parseFloat(
this.videoList[this.firstIndex].nodes[this.nodeIndex].VIDEOTIME ||
0,
);
const temp = Math.floor(
(resourceTime / videoTime) * 10000,
).toString();
percent = temp.substring(0, temp.length) / 100;
}
this.videoList[this.firstIndex].nodes[this.nodeIndex].percent = percent;
} else {
let percent = 0;
if (resData.pd.PLAYCOUNT > 0) {
percent = 100;
} else {
const resourceTime = parseFloat(resData.pd.RESOURCETIME || 0);
const videoTime = parseFloat(
this.videoList[this.firstIndex].VIDEOTIME || 0,
);
const temp = Math.floor(
(resourceTime / videoTime) * 10000,
).toString();
percent = temp.substring(0, temp.length) / 100;
}
this.videoList[this.firstIndex].percent = percent;
}
if (resData.pd.CANEXAM === "1") {
2024-05-23 15:04:26 +08:00
this.videoContext.pause();
2024-03-25 19:16:41 +08:00
uni.showModal({
title: "提示",
content: "当前班级内所有课程均已学完,是否直接参加考试?",
confirmButtonText: "是",
cancelButtonText: "否",
success: (res) => {
if (res.confirm) {
2024-05-23 15:04:26 +08:00
this.videoContext && this.videoContext.exitFullScreen()
2024-03-25 19:16:41 +08:00
uni.navigateTo({
url: '/pages/application/onlinexxks/course_exam?STAGEEXAMPAPERINPUT_ID=' + resData.paper.STAGEEXAMPAPERINPUT_ID + '&STAGEEXAMPAPER_ID=' + resData.paper.STAGEEXAMPAPERINPUT_ID + '&CLASS_ID=' + this.CLASS_ID + '&POST_ID=' + resData.pd.POST_ID + '&STUDENT_ID=' + this.STUDENT_ID + '&NUMBEROFEXAMS=' + resData.pd.NUMBEROFEXAMS + '&entrySite=video_study'
})
} else if (res.cancel) {
2024-05-23 15:04:26 +08:00
this.videoContext.play();
2024-03-25 19:16:41 +08:00
}
},
});
}
},
fnEnded() {
2024-05-20 18:03:21 +08:00
if (this.changeVideoPlayTime - this.serverVideoPlayTime >= 10) {
uni.navigateBack();
return;
}
2024-03-25 19:16:41 +08:00
this.changeVideoPlayTime = 0;
this.fnSubmitPlayTime("1", 0);
2024-05-23 15:04:26 +08:00
this.videoContext.pause();
2024-03-25 19:16:41 +08:00
this.fnClearInterval();
},
fnPlay() {
if (this.ISFACE === "1") {
this.fnClearInterval();
if (this.randomTime === 0) {
this.randomTime = new Date().getTime() + 60 * 1000 * 10;
}
faceAuthTimer = setInterval(async () => {
if (new Date().getTime() >= this.randomTime) {
2024-05-20 18:03:21 +08:00
this.randomTime = 0;
2024-03-25 19:16:41 +08:00
await this.fnSubmitPlayTime("0", this.changeVideoPlayTime);
await this.fnNavigationFaceAuth();
}
}, 1000);
}
},
fnPause() {
this.fnClearInterval();
},
fnClearInterval() {
faceAuthTimer && clearInterval(faceAuthTimer);
faceAuthTimer = null;
},
async fnNavigationFaceAuth() {
this.verification = false
const params = JSON.stringify({
CLASS_ID: this.CLASS_ID,
STUDENT_ID: this.STUDENT_ID,
CURRICULUM_ID: this.videoData.CURRICULUM_ID,
CHAPTER_ID: this.videoData.CHAPTER_ID,
VIDEOCOURSEWARE_ID: this.videoData.VIDEOCOURSEWARE_ID,
})
2024-05-23 15:04:26 +08:00
this.videoContext && this.videoContext.exitFullScreen()
2024-05-15 14:16:58 +08:00
const resData = await this.post('/app/user/getUserFace', {
USERNAME: loginUser.NAME,
USER_ID: loginUser.USER_ID,
});
if (!resData.pd.PORTRAIT) {
uni.showModal({
title: "温馨提示",
confirmText: "确定",
content: "您当前还未进行人脸认证,请先进行认证",
});
} else {
uni.navigateTo({
url: '/pages/application/onlinexxks/face/index?params=' + params
})
}
2024-03-25 19:16:41 +08:00
},
fnNavigationExercises(VIDEOCOURSEWARE_ID) {
uni.navigateTo({
url: '/pages/application/onlinexxks/exercises?VIDEOCOURSEWARE_ID=' + VIDEOCOURSEWARE_ID
})
},
},
}
</script>
<style scoped lang="scss">
.video_bg, #video {
width: 100%;
height: 450upx;
}
.title {
font-weight: bold;
background: #ffffff;
padding: 20upx;
font-size: 34upx;
}
.tabs {
background: #ffffff;
padding: 0 40upx;
margin-top: 20upx;
}
.video_container {
width: 100%;
background: #ffffff;
padding: 40upx;
margin-top: 20upx;
box-sizing: border-box;
.video_list_title {
display: flex;
align-items: center;
margin-left: -10upx;
image {
width: 48upx;
height: 48upx;
}
}
.video_list_main {
margin-top: 20upx;
.video_list_main_wrap {
width: 100%;
background: #fafafa;
padding: 20upx;
border-radius: 10upx;
font-size: 26upx;
box-sizing: border-box;
color: #666666;
margin-bottom: 20upx;
.video_list_main_wrap_tit {
width: 100%;
}
.video_list_main_wrap_info {
flex: 1;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10upx;
image {
width: 28upx;
height: 28upx;
}
}
}
}
}
.fcb {
color: #3c9cff;
}
.show_container {
background: #ffffff;
padding: 40upx;
.introduce {
word-break: break-all;
}
.image {
margin-top: 20upx;
image {
width: 100%;
height: 400upx;
}
}
}
</style>