638 lines
20 KiB
Vue
638 lines
20 KiB
Vue
<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">
|
||
<web-view-video
|
||
ref="video"
|
||
:src="videoSrc"
|
||
:poster="videoPoster"
|
||
:play-time="serverVideoPlayTime"
|
||
@play="fnPlay"
|
||
@pause="fnPause"
|
||
@ended="fnEnded"
|
||
@timeupdate="fnTimeUpdate"
|
||
/>
|
||
<!-- <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";
|
||
import WebViewVideo from './web_view_video.vue'
|
||
|
||
let faceAuthTimer; // 人脸认证计时器
|
||
let throttleTimer;
|
||
let throttleFlag;
|
||
export default {
|
||
components: {
|
||
WebViewVideo,
|
||
},
|
||
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减去的高度
|
||
submitTimeWaitForCount: 0, // 提交时间等待次数
|
||
verification:false,// 是否人脸认证
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.CLASSCURRICULUM_ID = options.CLASSCURRICULUM_ID;
|
||
this.CLASS_ID = options.CLASS_ID;
|
||
this.STUDENT_ID = options.STUDENT_ID;
|
||
this.ISFACE = options.ISFACE;
|
||
// this.ISFACE = '0';
|
||
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')
|
||
this.fnClearInterval()
|
||
},
|
||
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) {
|
||
this.$refs.video && this.$refs.video.fn('destroy')
|
||
this.videoSrc = "";
|
||
this.videoPoster = "";
|
||
this.submitTimeWaitForCount = 0;
|
||
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 {
|
||
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,
|
||
});
|
||
await this.fnGetVideoPlayProgress(resData.PLAYURL, resData.COVERURL);
|
||
},
|
||
async fnGetVideoPlayProgress(PLAYURL, COVERURL) {
|
||
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);
|
||
}
|
||
this.serverVideoPlayTime = Math.floor(resData.pd.RESOURCETIME) || 0
|
||
this.videoSrc = PLAYURL;
|
||
this.videoPoster = COVERURL;
|
||
},
|
||
fnTimeUpdate(event) {
|
||
this.throttle(async () => {
|
||
if (event - this.serverVideoPlayTime >= 10) {
|
||
uni.navigateBack();
|
||
return;
|
||
}
|
||
this.changeVideoPlayTime = event;
|
||
if (event - this.serverVideoPlayTime >= 5) {
|
||
this.serverVideoPlayTime = event;
|
||
this.submitTimeWaitForCount = ++this.submitTimeWaitForCount;
|
||
}
|
||
if (this.submitTimeWaitForCount === 6) {
|
||
this.submitTimeWaitForCount = 0;
|
||
await this.fnSubmitPlayTime("0", event);
|
||
}
|
||
}, 1000);
|
||
},
|
||
async fnSubmitPlayTime(IS_END, RESOURCETIME) {
|
||
if(!this.videoData.VIDEOCOURSEWARE_ID) {
|
||
return
|
||
}
|
||
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") {
|
||
this.$refs.video && this.$refs.video.fn('pause')
|
||
this.$refs.video && this.$refs.video.fn('fullscreen.exit')
|
||
uni.showModal({
|
||
title: "提示",
|
||
content: "当前班级内所有课程均已学完,是否直接参加考试?",
|
||
confirmButtonText: "是",
|
||
cancelButtonText: "否",
|
||
success: (res) => {
|
||
if (res.confirm) {
|
||
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) {
|
||
this.$refs.video && this.$refs.video.fn('play')
|
||
}
|
||
},
|
||
});
|
||
}
|
||
},
|
||
fnEnded() {
|
||
if (this.changeVideoPlayTime - this.serverVideoPlayTime >= 10) {
|
||
uni.navigateBack();
|
||
return;
|
||
}
|
||
this.changeVideoPlayTime = 0;
|
||
this.fnSubmitPlayTime("1", 0);
|
||
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) {
|
||
this.fnClearInterval();
|
||
this.$refs.video && this.$refs.video.fn('fullscreen.exit')
|
||
this.videoSrc = ''
|
||
this.videoPoster = ''
|
||
this.randomTime = 0;
|
||
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,
|
||
})
|
||
const resData = await this.post('/app/user/getUserFace', {
|
||
USERNAME: loginUser.NAME,
|
||
USER_ID: loginUser.USER_ID,
|
||
});
|
||
if (resData.pd.PORTRAIT) {
|
||
uni.navigateTo({
|
||
url: '/pages/application/onlinexxks/face/index?params=' + params
|
||
})
|
||
} else {
|
||
uni.showModal({
|
||
title: "温馨提示",
|
||
content: "您当前还未进行人脸认证,请先进行认证",
|
||
confirmText: "前往人脸认证",
|
||
cancelText: "确定",
|
||
success: (res) => {
|
||
if(res.confirm) {
|
||
uni.navigateTo({
|
||
url: `/pages/my/set/set`,
|
||
});
|
||
}
|
||
}
|
||
});
|
||
}
|
||
},
|
||
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: 250px;
|
||
}
|
||
|
||
.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>
|