提交 eeb75178 authored 作者: zyx's avatar zyx

update

上级 c2eb9cc1
......@@ -6771,6 +6771,24 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash.assign": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
"dev": true
},
"lodash.mergewith": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz",
"integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==",
"dev": true
},
"loglevel": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz",
......@@ -7422,9 +7440,9 @@
"dev": true
},
"node-sass": {
"version": "4.13.1",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz",
"integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==",
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.5.3.tgz",
"integrity": "sha1-0JydEXlkEjnRuX/8YjH9zsU+FWg=",
"dev": true,
"requires": {
"async-foreach": "0.1.3",
......@@ -7434,7 +7452,9 @@
"get-stdin": "4.0.1",
"glob": "7.1.6",
"in-publish": "2.0.1",
"lodash": "4.17.15",
"lodash.assign": "4.2.0",
"lodash.clonedeep": "4.5.0",
"lodash.mergewith": "4.6.2",
"meow": "3.7.0",
"mkdirp": "0.5.5",
"nan": "2.14.0",
......@@ -7442,8 +7462,7 @@
"npmlog": "4.1.2",
"request": "2.88.2",
"sass-graph": "2.2.4",
"stdout-stream": "1.4.1",
"true-case-path": "1.0.3"
"stdout-stream": "1.4.1"
},
"dependencies": {
"ansi-styles": {
......@@ -10413,15 +10432,6 @@
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
"true-case-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
"integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
"dev": true,
"requires": {
"glob": "7.1.6"
}
},
"tslib": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz",
......
......@@ -52,7 +52,7 @@
"file-loader": "^6.0.0",
"html-webpack-plugin": "^4.0.4",
"mini-css-extract-plugin": "^0.9.0",
"node-sass": "^4.13.1",
"node-sass": "^4.5.3",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.3.1",
"style-loader": "^1.1.3",
......
......@@ -4,8 +4,8 @@ import { Other } from '@api'
export default class OtherAction extends BaseACTION {
/* 获取我的消息信息 */
getMyMsg () {
return msgApi.getMyMsg().then(res => {
let json = res.map(function (_, i) {
return Other.getMyMsg().then(res => {
const json = res.map(function (_, i) {
return {
isRead: false,
id: _.id,
......@@ -17,8 +17,9 @@ export default class OtherAction extends BaseACTION {
return json
})
}
/**
* 文件上传
*/
uploadFile (obj) { return chapterApi.uploadFile(obj).then(res => res) }
uploadFile (obj) { return Other.uploadFile(obj).then(res => res) }
}
import BaseACTION from './base_action'
import { Player } from '@api'
import tools from '@tool'
export default class PlayerAction extends BaseACTION {
/* 获取章节列表信息 */
getChapterList (cid, sid, _id) {
return Player.getChapterList(cid, sid, _id).then(_res => {
let i = 0
let j = 0
let nextVideo = {}
let prevVideo = {}
/* 计算上一章 和 下一章 */
for (; i < _res.chapters.length; i++) {
const _ = _res.chapters[i]
for (j = 0; j < _.children.length; j++) {
if (_.children[j].resource_id === _id) {
// wx.setNavigationBarTitle({ title: _.children[j].name || '音视频' });
if (j - 1 >= 0) {
prevVideo = {
id: _.children[j - 1].resource_id,
time: (_.children[j - 1].video && tools.convertTime.durationToTimeString(_.children[j - 1].video.video_length)) || '',
name: _.children[j - 1].name
}
} else if (i - 1 >= 0) {
const _temp = _res.chapters[i - 1]
prevVideo = {
id: _temp.children[_temp.children.length - 1].resource_id,
time: (_temp.children[_temp.children.length - 1].video && tools.convertTime.durationToTimeString(_temp.children[_temp.children.length - 1].video.video_length)) || '',
name: _temp.children[_temp.children.length - 1].name
}
}
if (j + 1 < _.children.length) {
nextVideo = {
id: _.children[j + 1].resource_id,
time: (_.children[j + 1].video && tools.convertTime.durationToTimeString(_.children[j + 1].video.video_length)) || '',
name: _.children[j + 1].name
}
} else if (i + 1 < _res.chapters.length) {
const _temp = _res.chapters[i + 1]
nextVideo = {
id: _temp.children[0].resource_id,
time: (_temp.children[0].video && tools.convertTime.durationToTimeString(_temp.children[0].video.video_length)) || '',
name: _temp.children[0].name
}
}
}
}
}
let curJson = {}
const json = {
title: _res.course_name,
sid: _res.semester_id,
cid: _res.course_id,
currentChapterId: _id,
survey: _res.survey, // 判别教学评估,是否已经填写
course: _res.chapters.map((_, i) => {
return {
title: _.name,
chapters: _.children.map((__, j) => {
const _homework = (__.type === 3 && __.homework && __.homework.work_type === 1 && __.homework) || ''
if (_homework) {
_homework.course_id = _res.course_id
_homework.chapter_id = __.id
_homework.work_id = __.resource_id
_homework.semester_id = _res.semester_id
}
const _chapterWork = (__.type === 3 && __.homework && __.homework.work_type === 2 && __.homework) || ''
const _chapterRead = (__.type === 4 && __.reading) || ''
const _chapterVideo = (__.type === 2 && __.video) || ''
if (_chapterVideo) {
_chapterVideo.pdf = __.pdf || ''
}
if (_id === __.resource_id) {
curJson = {
id: __.resource_id,
video_provider: (__.video && __.video.video_provider) || '',
time: (__.video && tools.convertTime.durationToTimeString(__.video.video_length)) || '',
name: __.name,
chapterId: __.id, // 需要chapterId 用来 提交 作业或问题 , 这个 chapterId 是 每个章节下 对应课程的 id,不是 章节id
type: __.type,
work_type: (__.homework && __.homework.work_type) || '',
homework: _homework,
chapterRead: _chapterRead,
chapterWork: _chapterWork,
chapterVideo: _chapterVideo,
live: __.live || ''
}
}
if (__.live && __.live.id) {
let str = ''
switch (__.live.live_status) {
case 0: str = '直播未开始'; break
case 1: str = '正在直播'; break
case 2: str = '直播结束'; break
case 101: str = '直播结束,视频剪辑中'; break // 录制开始
case 102: str = '直播结束,视频剪辑中'; break // 录制结束
case 103: str = '观看回放'; break
default: str = '直播未开始'
}
// 5分钟内显示“即将开始”,5~1小时内“N分钟后开始”,1~24小时内“N小时后开始”,1天以上“N天后开始”天就显示年月日
if (__.live.live_status === 0 && __.live.start_time) {
const time = (new Date(__.live.start_time).getTime() - new Date().getTime()) / 1000
if (time <= 5 * 60) {
str = '即将开始'
} else if (time <= 1 * 60 * 60) {
str = parseInt(time / 60) + '分钟后开始'
} else if (time <= 24 * 60 * 60) {
str = parseInt(time / (60 * 60)) + '小时' + parseInt(time / 60 % 60) + '分钟后开始'
} else {
str = parseInt(time / (24 * 60 * 60)) + '天后开始'
}
}
if (__.live.live_status === 103 && __.live.enable_record !== undefined && __.live.enable_record !== null && !__.live.enable_record) {
str = ''
}
__.live.statusStr = str
}
return {
id: __.resource_id,
video_provider: (__.video && __.video.video_provider) || '',
time: (__.video && tools.convertTime.durationToTimeString(__.video.video_length)) || '',
name: __.name,
type: __.type,
work_type: (__.homework && __.homework.work_type) || '',
homework: _homework,
chapterRead: _chapterRead,
chapterWork: _chapterWork,
chapterVideo: _chapterVideo,
live: __.live || ''
}
})
}
}),
nextVideo: nextVideo,
prevVideo: prevVideo
}
json.course.push({
title: '大作业及资料',
chapters: [{
name: '课程大作业',
id: 'course_work'
}, {
name: '课程资料',
id: 'course_info'
}, {
name: '教学评估',
id: 'teach_evaluation'
}]
})
if (_res.course_examination) {
json.course[json.course.length - 1].chapters.push({
name: '课程考试',
type: 'exam',
id: _res.course_examination
})
}
const courseWork = _res.curriculum || {}
courseWork.end_date = _res.end_date
courseWork.essay_date = _res.essay_date
// callback(json) // 可以不使用callback 因为使用then
return {
json: json,
courseInfo: _res.files || [],
courseWork: _res.curriculum || {},
curJson: curJson
}
})
}
/* 获取对应某个章节的详细信息 */
getCurrentChapterDetail (vid) {
return Player.getCurrentChapterDetail(vid).then(_res => {
const json = {
video: {
src: _res.video[0].playurl,
spareSrc: 'http://pd4t7ae3m.bkt.clouddn.com/test.mp4' // 正式环境时,需要将 contentVideo 中 spareSrc 改成 src
},
audio: {
src: _res.audio[0].url,
poster: (_res.ppts && _res.ppts[0] && _res.ppts[0].ppt_url) || ''
},
image: {
imgUrls: (_res.ppts && _res.ppts.map(function (_, i) { return _.ppt_url })) || [],
current: 0,
selectIndex: 0,
timeArr: (_res.ppts && _res.ppts.map(function (_, i) { return _.ppt_point })) || []
},
rData: _res
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取对应某个章节的详细信息 */
getCurrentChapterDetailAliyun (vid) {
return Player.getCurrentChapterDetailAliyun(vid).then(_res => {
window.G && (window.G.m3u8RequestId = _res.request_id)
const json = {
video: {
FD: _res.video.FD,
LD: _res.video.LD,
SD: _res.video.SD
},
audio: {
src: _res.video.SQ,
poster: (_res.ppts && _res.ppts[0] && _res.ppts[0].ppt_url) || ''
},
image: {
imgUrls: (_res.ppts && _res.ppts.map(function (_, i) { return _.ppt_url })) || [],
current: 0,
selectIndex: 0,
timeArr: (_res.ppts && _res.ppts.map(function (_, i) { return _.ppt_point })) || []
},
rData: _res
}
// callback(json) // 可以不使用callback 因为使用then
return json
})
}
/* 获取进度信息 */
getProgress (vid, did, sid) {
return Player.getProgress(vid, did, sid).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return {
id: res.id,
cpt: parseInt(res.cpt),
mpt: parseInt(res.mpt),
pt: parseInt(res.pt),
progress: res.progress,
sign: res.sign,
map: res.map || []
}
})
}
/* 提交进度信息 */
updateProgress (obj) {
return Player.updateProgress(obj).then(res => res)
// return new Promise((resolve, reject) => { resolve({ success: true }) }) // 测试 视频时,不上传 视频数据
}
/* 获取试题信息 */
getExamDetail (sid, cid, eid) {
return Player.getExamDetail(sid, cid, eid).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/* 提交考试信息 */
submitExamDetail (param) {
return Player.submitExamDetail(param).then(res => {
// callback(res) // 可以不使用callback 因为使用then
return res
})
}
/* 获取对应 作业或问题 回答 */
getHomework (sid, cid, id) {
return Player.getHomework(sid, cid, id).then(res => res)
}
/**
* 文件上传
*/
uploadFile (obj) { return Player.uploadFile(obj).then(res => res) }
/* 提交作业 */
updateHomework (obj) { return Player.updateHomework(obj).then(res => res) }
/* 获取大作业 回答 */
getCourseHomework (sid, cid) { return Player.getCourseHomework(sid, cid).then(res => res) }
/* 获取作业截止时间 */
getHomeworkStopTime (sid, cid, chapterId) { return Player.getHomeworkStopTime(sid, cid, chapterId).then(res => res) }
/* 提交大作业 */
updateCourseHomework (sid, cid, obj) { return Player.updateCourseHomework(sid, cid, obj).then(res => res) }
/* 提交课程考核 */
updateSurveyAnswer (obj) { return Player.updateSurveyAnswer(obj).then(res => res) }
/* 获取当前最新直播提醒 */
getNewLiveMsg () { return Player.getNewLiveMsg().then(res => res) }
/* 获取手机直播列表 */
getLiveList () { return Player.getLiveList().then(res => res) }
}
import OtherAction from './OtherAction'
import CourseAction from './CourseAction'
import GradeAction from './GradeAction'
import ReportAction from './ReportAction'
import PlayerAction from './PlayerAction'
const Other = new OtherAction()
const Course = new CourseAction()
const Grade = new GradeAction()
const Report = new ReportAction()
const Player = new PlayerAction()
const cAction = {
Other,
Course,
Grade,
Report
Report,
Player
}
export default cAction
......@@ -2,15 +2,18 @@ import OtherAPI from './other_api'
import CourseAPI from './course_api'
import GradeAPI from './grade_api'
import ReportAPI from './report_api'
import PlayerAPI from './player_api'
const Other = new OtherAPI(webConf)
const Course = new CourseAPI(webConf)
const Grade = new GradeAPI(webConf)
const Report = new ReportAPI(webConf)
const Player = new PlayerAPI(webConf)
export {
Other,
Course,
Grade,
Report
Report,
Player
}
import BaseAPI from './base_api'
export default class PlayerAPI extends BaseAPI {
/**
* 获取章节列表信息
* @param {[string]} cur_course_id -> cid
* @param {[string]} cur_semester_id -> sid
* @param {[string]} cur_video_id -> vid
*/
getChapterList = (cid, sid, vid) => this.get(`/v2/education/courses/${sid}/${cid}`, {})
/**
* 获取对应某个章节的详细信息
* @param {[string]} vid
*/
getCurrentChapterDetail = (vid) => this.post('/v2/education/video-streaming', { vid })
/**
* 获取对应某个章节的详细信息
* @param {[string]} vid
*/
getCurrentChapterDetailAliyun = (vid) => this.post('/v2/education/aliyun-video-streaming', { vid })
/**
* 获取进度信息
* @param {[string]} vid
* @param {[string]} did
* @param {[string]} sid
*/
getProgress = (vid, did, sid) => this.get(`/v2/education/video/${sid}/${vid}/device`, { device_id: did })
/**
* 提交进度信息
* @param {[object]} obj
* d: obj.did,
i: obj.did,
c: obj.cid,
s: obj.sid,
v: obj.vid,
_p: obj.pt, // 累计时间
_m: obj.mpt, // 当前播放最大时间
_c: obj.cpt // 当前播放位置
*/
updateProgress = (obj = {}) => this.get('/v2/analytics/upload-video', obj)
/**
* 获取试题信息
* @param {[string]} eid
* @param {[string]} cid
* @param {[string]} sid
*/
getExamDetail = (sid, cid, eid) => this.get(`/v2/education/homeworks/${sid}/${cid}/${eid}`, {})
/**
* 提交考试信息
* @param {[object]} param
*/
submitExamDetail = (param) => this.post('/v2/education/homeworks', param)
/**
* 获取对应 作业或问题 回答
* @param {[string]} sid
* @param {[string]} cid
* @param {[string]} id resource_id
*/
getHomework = (sid, cid, id) => this.get(`/v2/education/homeworks/${sid}/${cid}/${id}`, {})
/**
* 文件提交
* @param {[object]} obj
*/
uploadFile = (obj = {}) => this.post('/util/upload-file', obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/**
* 提交课程 作业或问题
*/
updateHomework = (obj = {}) => this.post('/v2/education/homeworks', obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/**
* 课程作业截止时间
*/
getHomeworkStopTime = (sid, cid, chapterId) => this.get(`/v2/education/homeworks/${sid}/${cid}/${chapterId}/deadline`, {})
/**
* 获取对应 大作业 回答
* @param {[string]} sid
* @param {[string]} cid
*/
getCourseHomework = (sid, cid) => this.get(`/v2/education/courses/${sid}/${cid}/essay`, {})
/**
* 提交课程 大作业
* @param {[string]} sid
* @param {[string]} cid
*/
updateCourseHomework = (sid, cid, obj = {}) => this.post(`/v2/education/courses/${sid}/${cid}/essay`, obj, { headers: { 'Content-Type': 'multipart/form-data' } })
/**
* 提交 课程考核
* @param {[string]} obj.sid
* @param {[string]} obj.cid
* @param {[string]} obj.raw (base64)
*/
updateSurveyAnswer = (obj) => this.post('/v2/education/survey/answer', obj)
/**
* 手机端 获取实时最新直播接口
*/
getNewLiveMsg = (obj = {}) => this.get('/v2/education/lives/latest', obj)
/**
* 手机端 获取列表接口
*/
getLiveList = (obj = {}) => this.get('/v2/education/lives/courses', obj)
}
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{chapterName}}</h3></div></div>
<div class="play-paper-content play-chapter-exam">
<template v-if="this.exam.id">
<div class='exam'>
<div style='text-align: center;'>
<div class='topic'>
<!-- <div class='tit'>{{exam.title}}</div> -->
<template v-if='exam.score'><div class='cur'>正确率:{{exam.score}}%</div></template>
</div>
</div>
<div style=''>
<template v-for='(item, index) in exam.group'>
<template v-if='item.type == 1'>
<div v-bind:key="index" class='q-group' @click="radioClick" :data-index='index'>
<div class='q-num'>{{index+1}}.</div><div class='q-title' v-html='item.title'></div><div class='q-type'>(单选题)</div>
<el-radio-group class='radio-group' @change='radioChange' v-model="item.sel">
<template v-for='(item1, index1) in item.arr'>
<!-- checked='{{item1.selected}}' -->
<el-radio v-bind:key="index1" :label='index1' :disabled='!!item.cur' :class='["radio", (item.cur && item1.checked && "success"), (item.cur && !item1.checked && item1.selected && "error")]'>{{ index1 == 0 ? "A" : (index1 == 1 ? "B" : (index1 == 2 ? "C" : (index1 == 3 ? "D" : (index1 == 4 ? "E" : (index1 == 5 ? "F" : (index1 == 6 ? "G" : (index1 == 7 ? "H" : (index1 == 8 ? "I" : (index1 == 9 ? "J" : "K"))))))))) }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if='item.cur'><div class='result'>学生答案:<div :class='["stu", (item.is_correct ? "success" : "error")]'>{{item.stuAnswer}}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{item.cur}}</div></template>
</div>
</template>
<template v-if='item.type == 2'>
<div v-bind:key="index" class='q-group' @click="checkboxClick" :data-index='index'>
<div class='q-num'>{{index+1}}.</div><div class='q-title' v-html='item.title'></div><div class='q-type'>(多选题)</div>
<el-checkbox-group class='checkbox-group' @change='checkboxChange' v-model="item.arrSel">
<template v-for='(item1, index1) in item.arr'>
<!-- value='{{index1}}' -->
<el-checkbox v-bind:key="item1.id" :label='index1' :checked='!!item1.selected' :disabled='!!item.cur' :class='["checkbox", (item.cur && item1.checked && "success"), (item.cur && !item1.checked && item1.selected && "error")]'>{{ index1 == 0 ? "A" : (index1 == 1 ? "B" : (index1 == 2 ? "C" : (index1 == 3 ? "D" : (index1 == 4 ? "E" : (index1 == 5 ? "F" : (index1 == 6 ? "G" : (index1 == 7 ? "H" : (index1 == 8 ? "I" : (index1 == 9 ? "J" : "K"))))))))) }}. {{item1.option}}</el-checkbox>
</template>
</el-checkbox-group>
<template v-if='item.cur'><div class='result'>学生答案:<div :class='["stu", (item.is_correct ? "success" : "error")]'>{{item.stuAnswer}}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{item.cur}}</div></template>
</div>
</template>
</template>
</div>
<div :class='["btn", (exam.work_contents && "on")]' @click='submitExam' :data-submit='!!exam.work_contents' @mousedown='_SubmitMouseLeftDown()'>{{exam.work_contents ? "已提交" : "提交"}}</div>
<div class='care'>(注意:测试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import cAction from '@action'
// import CKEDITOR from 'CKEDITOR'
export default {
props: {
chapterId: { type: String, require: false },
chapterName: { type: String, require: false },
chapterExam: { type: Object, require: false },
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
data () {
return {
exam: {
// id: '',
// semester_id: '',
// title: '家族财富传承第一章试题',
// score: '100',
// work_contents: '',
// group: [{
// id: '',
// type: 1, // 判别是 单选还是多选
// title: '<p>一般延续好几代,采取防御战略维持企业的家族企业类型是( )。</p>',
// arr: [
// { id: '', checked: true, option: '闪电型', selected: 0 },
// { id: '', checked: true, option: '不老型', selected: 1 },
// { id: '', checked: false, option: '长存型', selected: 1 },
// { id: '', checked: false, option: '持续创业型', selected: 0 }
// ],
// cur: '1',
// arrSel: [],
// sel: ''
// }]
},
// 提交时,传入的参数值
param: {
course_id: '',
chapter_id: '',
work_id: '',
semester_id: '',
work_contents: '',
duration: 0,
score: 0
},
/* 进入页面记录初试时间 */
startTime: '',
/* 记录所有题目是否都答过 */
recordAll: [],
setTime: null,
/* 当前 radio点击 */
radioCur: '',
/* 当前 checkbox点击 */
checkCur: ''
}
},
mounted () {
this.loadAjax()
},
methods: {
arrRandomSort (arr) {
/* 对json.questions 进行一次乱序排列 */
let tmpJson = null
tmpJson = arr.shift()
arr.push(tmpJson)
tmpJson = arr.shift()
arr.push(tmpJson)
tmpJson = arr.shift()
arr.push(tmpJson)
},
unArrRandomSort (arr) {
/* 取消乱序排列,恢复原顺序 */
let tmpJson = null
tmpJson = arr.pop()
arr.unshift(tmpJson)
tmpJson = arr.pop()
arr.unshift(tmpJson)
tmpJson = arr.pop()
arr.unshift(tmpJson)
},
/**
* 将返回值 - 对照到对应 data上
*/
updateData (json) {
this.param = {
course_id: json.course_id,
chapter_id: json.chapter_id,
work_id: json.work_id,
semester_id: json.semester_id,
work_contents: '',
duration: 0,
score: 0
}
this.startTime = new Date().getTime()
this.arrRandomSort(json.questions)
const _data = {
id: json.id,
semester_id: json.semester_id,
title: json.work_title,
score: json.score || (json.score === 0 ? '0' : ''),
work_contents: json.work_contents || '',
group: json.questions.map(function (_, i) {
let str = ''
let stuAnswer = '' // 学生答案
let stuIsCorrect = 0 // 学生是否答对
let _json = JSON.parse(_.question_options)
_json.forEach(function (__, j) {
if (__.checked) {
switch (j) {
case 0: str += 'A,'; break
case 1: str += 'B,'; break
case 2: str += 'C,'; break
case 3: str += 'D,'; break
case 4: str += 'E,'; break
case 5: str += 'F,'; break
case 6: str += 'G,'; break
case 7: str += 'H,'; break
case 8: str += 'I,'; break
case 9: str += 'J,'; break
}
}
})
let sel = '' // 单选选中
/* 答过题目时 */
if (json.work_contents) {
const a = JSON.parse(json.work_contents)
for (let i = 0; i < a.length; i++) {
if (a[i].question_id === _.id) {
_json = a[i].options
stuIsCorrect = a[i].is_correct
break
}
}
/* 单选选中 */
if (_.question_type === 1) {
for (let i = 0; i < _json.length; i++) {
if (_json[i].selected) {
sel = i
switch (i) {
case 0: stuAnswer = 'A,'; break
case 1: stuAnswer = 'B,'; break
case 2: stuAnswer = 'C,'; break
case 3: stuAnswer = 'D,'; break
case 4: stuAnswer = 'E,'; break
case 5: stuAnswer = 'F,'; break
case 6: stuAnswer = 'G,'; break
case 7: stuAnswer = 'H,'; break
case 8: stuAnswer = 'I,'; break
case 9: stuAnswer = 'J,'; break
}
break
}
}
}
/* 多选选中 */
if (_.question_type === 2) {
for (let i = 0; i < _json.length; i++) {
if (_json[i].selected) {
switch (i) {
case 0: stuAnswer += 'A,'; break
case 1: stuAnswer += 'B,'; break
case 2: stuAnswer += 'C,'; break
case 3: stuAnswer += 'D,'; break
case 4: stuAnswer += 'E,'; break
case 5: stuAnswer += 'F,'; break
case 6: stuAnswer += 'G,'; break
case 7: stuAnswer += 'H,'; break
case 8: stuAnswer += 'I,'; break
case 9: stuAnswer += 'J,'; break
}
}
}
}
}
return {
id: _.id,
type: _.question_type,
title: _.question_content,
arr: _json,
cur: (json.work_contents && str.substr(0, str.length - 1)) || '',
arrSel: [],
sel: sel,
stuAnswer: stuAnswer.substr(0, stuAnswer.length - 1),
is_correct: stuIsCorrect
}
})
}
return _data
},
/**
* 将返回值 - 对照到对应 data上
* 增加这个的目的,由于循环改变data对象时,又由于 v-bind:key使用相同
* 导致,有些选项勾选状态不显示,所有在不改变对象引用的前提下,直接改值
* 所以 产生当前方法
* 直接 把数据值 赋值给 exam对象
*/
updateData1 (json) {
this.exam.id = json.id
this.exam.semester_id = json.semester_id
this.exam.title = json.work_title
this.exam.score = json.score || '0'
this.exam.work_contents = json.work_contents || ''
this.arrRandomSort(json.questions)
for (let i = 0; i < this.exam.group.length; i++) {
let str = ''
let stuAnswer = '' // 学生答案
let stuIsCorrect = 0 // 学生是否答对
const _ = json.questions[i]
let _json = JSON.parse(_.question_options)
_json.forEach(function (__, j) {
if (__.checked) {
switch (j) {
case 0: str += 'A,'; break
case 1: str += 'B,'; break
case 2: str += 'C,'; break
case 3: str += 'D,'; break
case 4: str += 'E,'; break
case 5: str += 'F,'; break
case 6: str += 'G,'; break
case 7: str += 'H,'; break
case 8: str += 'I,'; break
case 9: str += 'J,'; break
}
}
})
let sel = '' // 单选选中
/* 答过题目时 */
if (json.work_contents) {
const a = JSON.parse(json.work_contents)
for (let i = 0; i < a.length; i++) {
if (a[i].question_id === _.id) {
_json = a[i].options
stuIsCorrect = a[i].is_correct
break
}
}
/* 单选选中 */
if (_.question_type === 1) {
for (let i = 0; i < _json.length; i++) {
if (_json[i].selected) {
sel = i
switch (i) {
case 0: stuAnswer = 'A,'; break
case 1: stuAnswer = 'B,'; break
case 2: stuAnswer = 'C,'; break
case 3: stuAnswer = 'D,'; break
case 4: stuAnswer = 'E,'; break
case 5: stuAnswer = 'F,'; break
case 6: stuAnswer = 'G,'; break
case 7: stuAnswer = 'H,'; break
case 8: stuAnswer = 'I,'; break
case 9: stuAnswer = 'J,'; break
}
break
}
}
}
/* 多选选中 */
if (_.question_type === 2) {
for (let i = 0; i < _json.length; i++) {
if (_json[i].selected) {
switch (i) {
case 0: stuAnswer += 'A,'; break
case 1: stuAnswer += 'B,'; break
case 2: stuAnswer += 'C,'; break
case 3: stuAnswer += 'D,'; break
case 4: stuAnswer += 'E,'; break
case 5: stuAnswer += 'F,'; break
case 6: stuAnswer += 'G,'; break
case 7: stuAnswer += 'H,'; break
case 8: stuAnswer += 'I,'; break
case 9: stuAnswer += 'J,'; break
}
}
}
}
}
this.exam.group[i].id = _.id
this.exam.group[i].type = _.question_type
this.exam.group[i].title = _.question_content
this.exam.group[i].cur = (json.work_contents && str.substr(0, str.length - 1)) || ''
this.exam.group[i].arrSel = []
this.exam.group[i].sel = sel
this.exam.group[i].stuAnswer = stuAnswer.substr(0, stuAnswer.length - 1)
this.exam.group[i].is_correct = stuIsCorrect
const _arr = this.exam.group[i].arr
for (let j = 0; j < _arr.length; j++) {
_arr[j].selected = _json[j].selected
_arr[j].id = _arr[j].id + '1'
}
}
},
/**
* 生命周期函数--监听页面加载
*/
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getExamDetail(this.sid, this.cid, this.id).then(_data => {
let json = _data.homework
if (json) {
json.score = _data.score
json.work_contents = _data.work_contents
this.exam = this.updateData(json)
} else {
this.exam = {}
}
}).catch(e => { this.$message.error(e.message) }).finally(() => {
this.setTime = setInterval(() => {
// console.log(this.chapterExam.work_id, this.id)
if (this.chapterExam.work_id && this.chapterExam.work_id === this.id) {
if (!this.exam.id) {
this.exam = this.updateData(this.chapterExam)
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
}
clearInterval(this.setTime)
}
}, 50)
loading.close()
})
},
/**
* 辅助 radio选择
*/
radioClick (e) {
this.radioCur = e.currentTarget.dataset.index
},
/**
* radio选择
* PC端 值会 返回 选择的 值
*/
radioChange (val) {
const i = this.radioCur
const arr = this.exam.group[i].arr
const value = val
for (let j = 0; j < arr.length; j++) {
arr[j].selected = 0
}
arr[value].selected = 1
this.exam.group[i].arr = arr
this.recordAll.push(i)
},
/**
* 辅助 checkbox选择
*/
checkboxClick (e) {
this.checkboxCur = e.currentTarget.dataset.index
},
/**
* checkbox选择
*/
checkboxChange (val) {
const i = this.checkboxCur
const arr = this.exam.group[i].arr
const value = val
for (let j = 0; j < arr.length; j++) {
arr[j].selected = 0
}
for (let j = 0; j < value.length; j++) {
arr[value[j]].selected = 1
}
this.exam.group[i].arr = arr
if (!value.length) {
this.recordAll = this.recordAll.filter(function (_, m) { return _ !== i })
} else {
this.recordAll.push(i)
}
},
/**
* 提交试题
*/
submitExam (e) {
if (e.currentTarget.dataset.submit) {
this.$message.error('已做过,不能再提交')
return
}
// /* 计算答题时间 */
this.param.duration = Math.floor((new Date().getTime() - this.startTime) / 1000)
const group = this.exam.group
const total = group.length
const arr = []
let score = 0
let correct = 0
for (let m = 0; m < total; m++) {
let n = 0
for (; n < this.recordAll.length; n++) {
if (this.recordAll[n] === m + '') { break }
}
if (n === this.recordAll.length) {
// console.log(this.recordAll)
this.$message.error('还有题目未做,不能提交')
return
}
}
/* 重新定义 - 上传字段 并计算分数 */
// is_correct
for (let i = 0; i < group.length; i++) {
correct = 0
if (group[i].type === 1) { // 单选的正确 判断
group[i].arr.forEach(function (_, i) {
if (_.checked && _.selected) { correct = 1; score += 1 }
})
}
if (group[i].type === 2) { // 多选的正确 判断
let flag = true
group[i].arr.forEach(function (_, i) {
if (_.checked !== !!_.selected) { flag = false }
})
if (flag) { correct = 1; score += 1 }
}
arr.push({
question_id: group[i].id,
is_correct: correct,
options: group[i].arr
})
}
this.unArrRandomSort(arr)
this.param.work_contents = JSON.stringify(arr)
this.param.score = (score / total * 100).toFixed(1)
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.submitExamDetail(this.param).then(_res => {
if (_res.status) {
cAction.Player.getExamDetail(this.sid, this.cid, this.id).then(_data => {
const json = _data.homework
if (json) {
json.score = _data.score
json.work_contents = _data.work_contents
this.updateData1(json) // 特殊处理
}
}).catch(e => { this.$message.error(e.message) })
} else {
this.$message.error(_res.data.error)
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
},
_SubmitMouseLeftDown () {
const _fn1 = this.repeatExam.bind(this, false)
document.addEventListener('keydown', _fn1, false)
const _fn3 = function () {
document.removeEventListener('keydown', _fn1)
document.removeEventListener('mouseup', _fn3)
}
document.addEventListener('mouseup', _fn3, false)
},
/**
* 重做
*/
repeatExam (e, flag) {
let _flag = flag
/* 字母 f */
if (e.keyCode === 70) {
_flag = true
}
if (!_flag) { return }
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getExamDetail(this.sid, this.cid, this.id).then(_data => {
this.exam = {}
}).catch(e => { this.$message.error(e.message) }).finally(() => {
this.setTime = setInterval(() => {
// console.log(this.chapterExam.work_id, this.id)
if (this.chapterExam.work_id && this.chapterExam.work_id === this.id) {
if (!this.exam.id) {
this.exam = this.updateData(this.chapterExam)
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
}
clearInterval(this.setTime)
}
}, 50)
loading.close()
})
}
},
watch: {
id: {
handler () {
this.loadAjax()
}
}
}
}
</script>
<style lang="scss" scoped>
.play {
.exam { padding: 0; }
.exam .topic { display: inline-block; margin-bottom: 0.1rem; }
.exam .topic .tit { margin: 0 auto; padding: 0 0.2rem; text-align: center; font-size: 0.24rem; color: #313131; background: #fff; box-sizing: border-box; -webkit-box-sizing: border-box; }
.exam .topic .cur { text-align: center; font-size: 0.18rem; color: #313131; line-height: 0.4rem; }
/* 循环 所有选择题 */
.exam .q-group { padding: 0.1rem 0.1rem; border-bottom: 1px solid #c9c9c97a; overflow: hidden; }
.exam .q-group .q-num { float: left; margin-right: 0.1rem; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .q-title { float: left; width: 90%; font-size: 0.16rem; color: #676a6c; text-align: justify; }
.exam .q-group .q-type { float: right; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .radio-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .radio-group .radio { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .checkbox-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .checkbox-group .checkbox { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .radio-group .radio.error, .exam .q-group .checkbox-group .checkbox.error { color: #d80000; }
.exam .q-group .radio-group .radio.success, .exam .q-group .checkbox-group .checkbox.success { color: #090; }
.exam .q-group .result { float: right; font-size: 0.18rem; color: #3f3b3a; margin-right: 0; }
.exam .q-group .result .stu { display: inline-block; }
.exam .q-group .result .stu.error { color: #d80000; }
.exam .q-group .result .stu.success { color: #090; }
.exam .q-group:last-child { border-bottom: none; }
.exam .btn { margin: 0.2rem auto; width: 60%; height: 0.5rem; line-height: 0.5rem; font-size: 0.16rem; text-align: center; font-weight: 300; color: #fff; border-radius: 0.1rem; background: #b49441; cursor: pointer; }
.exam .btn.on { opacity: 0.5; }
.exam .care { font-size: 0.16rem; color: #d80000; text-align: center; }
}
</style>
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{chapterName}}</h3></div></div>
<div class="play-paper-content">
<ul class="play-read-files">
<li><a :href="chapterRead.reading_attachment" target="_blank">{{chapterRead.reading_content}}</a></li>
</ul>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
chapterRead: { type: Object, require: false },
chapterName: { type: String, require: false }
}
}
</script>
<template>
<div class="play-content-video" ref="container">
<div class="play-center" ref="box">
<template v-if="chapterVideo">
<div :class="['play-video', (state.pptBoxOnly ? 'play-video-hide' : ''), (state.calculatedSize ? '' : 'play-video-init-center')]">
<template v-if="videoType !== '3'">
<e-video
ref="video"
:lastTime="parseFloat((chapterVideo.progress && chapterVideo.progress.pt) || 0)"
:width="videoFlash.videoWidth"
:height="videoFlash.videoHeight"
:username="videoFlash.username"
:videoId="chapterVideo.video_origionalID"
:videoSrt="(chapterVideo.video_subtitle || '')"
@handlePlayTime="onVideoTimeChange"
@handlePlayfinish="onVideoPlayFinish"
:chapterVideo="chapterVideo"
/>
</template>
<template v-else>
<e-video-h5
ref="video"
:lastTime="parseFloat((chapterVideo.progress && chapterVideo.progress.pt) || 0)"
:width="videoFlash.videoWidth"
:height="videoFlash.videoHeight"
:username="videoFlash.username"
:videoId="chapterVideo.id"
:videoSrt="(chapterVideo.video_subtitle || '')"
@handlePlayTime="onVideoTimeChange"
@handlePlayfinish="onVideoPlayFinish"
:chapterVideo="chapterVideo"
:videoType="videoType"
@changeVideoArr="changeVideoArr"
/>
</template>
</div>
<div :class="['play-jiangyi', (state.pptBoxShow ? '' : 'hide')]">
<template v-if="ppts.length">
<e-ppt
ref="ppt"
:ppts="ppts"
:currentIndex="state.pptIndex"
@onVideoSyncTime="setVideoTime"
@onPptOnly="togglePptBoxOnly"
@onClose="togglePptBox"
/>
</template>
</div>
</template>
<template v-else>
<p>课程视频数据不存在</p>
</template>
</div>
<!-- 底部控制 -->
<div class="play-footer">
<div class="fl">
<!-- <Link to={`/courses/${semesterId}/${courseId}/chapters/${prevChapterId || chapterId}`} class={`play-state play-state-prev ${prevChapterId?'': 'play-state-prev-disable'}`}>上一节</Link> -->
<!-- <Link to={`/courses/${semesterId}/${courseId}/chapters/${nextChapterId || chapterId}`} class={`play-state play-state-next ${nextChapterId?'': 'play-state-next-disable'}`}>下一节</Link> -->
</div>
<div class="fr">
<template v-if="chapterVideo.pdf">
<em class="play-state play-state-ppt" ><a :href="chapterVideo.pdf" target="_blank">下载PPT</a></em>
</template>
<!-- <em class={`play-state play-state-check${curProgress == 100 ? '-active' : ''}`} onClick={this.handleOver} data-vid={video && video.id || ''} data-progress={curProgress} data-total={video && video.video_length || 1}>{curProgress == 100 ? '已学完' : '标记为已学完'}</em> -->
<template v-if="ppts.length">
<em :class="['play-state', ('play-state-ppt' + (state.pptBoxShow ? '-active' : ''))]" @click="togglePptBox">同步显示PPT</em>
</template>
<em :class='["play-state", ("play-state-check" + (state.skipBegin ? "-active" : ""))]' @click="toggleSkipBegin">始终跳过片头</em>
</div>
</div>
</div>
</template>
<script>
import eVideo from './video.vue'
import eVideoH5 from './videoH5.vue'
import ePpt from './ppt.vue'
import _ from 'lodash'
const PLAY_SPACE_WIDTH = 15
const PLAY_SPACE_HEIGHT = 10
/* 初始视频 宽、高 */
const VIDEO_DEFAULT_WIDTH = 550
const VIDEO_DEFAULT_HEIGHT = 360
/* 视频 定时 心跳记录器 默认时间 ms */
const HEART_TIME = 10000
export default {
components: { eVideo, eVideoH5, ePpt },
props: {
chapterId: { type: String, require: false },
chapterName: { type: String, require: false },
chapterVideo: { type: Object, require: false },
ppts: { type: Array, require: false, default: [] },
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false },
videoType: { type: String, require: false }
},
data () {
return {
state: {
pptIndex: 0, // ppt 所在当前位置
pptBoxOnly: false, // 仅展示ppt框
pptBoxShow: false, // 展示ppt框
skipBegin: /skip=1/.test(document.cookie), // 跳过片头
calculatedSize: false // 是否以计算过播放位置的尺寸
},
/* video flash 参数传递 */
videoFlash: {
lastTime: null,
speed: 1, // 默认播放速度
isSeek: false, // 是否拖动进度条
isFinish: false, // 是否播放完成
queueFrames: [], // 存放所有视频帧的队列,上传成功清空一次。
mapFrames: [], // 记录视频播放过的 所有帧 hash 表,数组 下标表示 视频对应帧 - 1,数组值 表示 播放过的帧,重复播 + 1
videoWidth: VIDEO_DEFAULT_WIDTH,
videoHeight: VIDEO_DEFAULT_HEIGHT,
username: (window.G.UserInfo && window.G.UserInfo.username) || '',
duration: 10000
},
/* 增加节流函数 对应 执行 */
resizeVideo: null,
/* 视频进度 统计 和 计算 */
hearBeat: null,
_rProgress: {},
/* 检测视频 是否一直在播放,如果停止播放,则停止接口调用 */
isPlaying: false
}
},
mounted () {
const size = this.getCalculateSize()
if (size.video.w) {
this.videoFlash.videoWidth = size.video.w
this.videoFlash.videoHeight = size.video.h
}
/* 如果不存在 改变视频尺寸大小 节流执行函数 定义如下 */
if (!this.resizeVideo) {
this.resizeVideo = _.debounce(this.jdugeSize.bind(this), 200)
}
/* 延迟计算尺寸,因flash初始化需要时间 */
setTimeout(this.jdugeSize, 300)
/* 窗口resize,重置大小 */
window.addEventListener('resize', this.resizeVideo)
/* 增加 心跳 记录 视频学习时间 */
this.createHeartTime(HEART_TIME)
},
destroyed () {
window.removeEventListener('resize', this.resizeVideo)
this.destroyHeartTime()
/* 在定时器 被销毁时,再次执行 定时器,解决:播放过程中,突然退出当前页(非关闭) */
this.hearBeatFunc(HEART_TIME)
},
watch: {
id: {
handler () {
/* 重新点击时,刷新左侧,隐藏pptbox */
/* 这里 理论上 应该 把所有 组件全部初始化,否则会出现部分数据是 上次同一个组件 但不同vid的 视频内容值 */
if (this.id) {
this.state.pptBoxShow = false
this.state.pptBoxOnly = false
this._rProgress = {}
// this.videoFlash.lastTime = 0 // 这里 修改 最后时间不生效
setTimeout(this.jdugeSize, 0)
}
}
}
},
methods: {
// toggle PPT播放框
togglePptBox () {
this.state.pptBoxShow = !this.state.pptBoxShow
this.state.pptBoxOnly = false
setTimeout(this.jdugeSize, 0)
},
// 仅显示ppt播放框
togglePptBoxOnly () {
this.state.pptBoxOnly = !this.state.pptBoxOnly
setTimeout(this.jdugeSize, 0)
},
// 跳过片头
toggleSkipBegin () {
const skip = !this.state.skipBegin
const d = new Date()
d.setMonth(d.getMonth() + 1)
document.cookie = 'skip=' + (+skip) + ';path=/;domain=.ezijing.com;expires=' + d.toGMTString()
if (skip && this.$refs.video) {
this.$refs.video.skipBegin()
}
this.state.skipBegin = skip
},
// 播放时间变更
onVideoTimeChange (data) {
const time = data.time
/* 获取视频 总时长 */
this.videoFlash.duration = data.duration || this.videoFlash.duration
/* 获取 对应 播放 速度 和 是否 拖动 进度条 */
this.videoFlash.speed = parseFloat(data.quality.split(' ')[1] || 1)
this.videoFlash.isSeek = data.isSeek
// 因视频播放完成后也会不断触发playing,因此比对上次时间
if (this.videoFlash.lastTime === time) { return }
this.videoFlash.lastTime = time
this.isPlaying = true
/* 将当前 获取的 每一帧 压缩 队列 */
if (!data.isSeek) {
this.videoFlash.queueFrames.push(time)
// console.log(this.videoFlash.queueFrames)
}
// 判断ppt位置
this.setPptIndexByTime(time)
this.$emit('handlePlayTime', time) // 调用 父级方法,改变 sidebar ppt 对应 位置
},
// 视频播放结束,解决播放结束,但是没有等够 HEART_TIME 时间 就直接 关闭浏览器问题
onVideoPlayFinish (data) {
/* 视频播放完毕触发,然后判断 pt 累加时间 基本等于 视频观看时间时,才认为真正播放完成 */
if (this.chapterVideo) {
if (this._rProgress && this._rProgress.id) {
} else {
this._rProgress = _.assignIn({}, this.chapterVideo.progress)
/* 增加 帧点 视频记录 精确粒度 到 秒 */
const _arr = []
const _len = this.chapterVideo.video_length || 1
for (let i = 0; i < _len; i++) {
_arr.push(0)
}
this.videoFlash.mapFrames = this._rProgress.map.length ? this._rProgress.map : _arr
this._rProgress.map = this.videoFlash.mapFrames
}
if (this._rProgress.pt + ((HEART_TIME / 1000) * this.videoFlash.speed) >= data.time) {
this.isPlaying = true // 这里 再设置一次 true ,只有为 true时 才能上传 进度
this.hearBeatFunc(HEART_TIME)
}
}
},
changeVideoArr (obj) {
this.$emit('changeVideoArr', obj)
},
/**
* 根据时间设置ppt位置 - PPT 样式 控制
*/
setPptIndexByTime (time) {
const ppts = this.ppts || []
let i = 0
for (; i < ppts.length; i++) {
if (time < ppts[i].ppt_point) { break }
}
if (this.state.pptIndex !== i - 1) {
this.state.pptIndex = i - 1
}
},
/**
* 设置视频播放时间点 - video flash 控制
*/
setVideoTime (time) {
if (this.$refs.video) {
this.$refs.video.setTimeTo(time)
}
},
/**
* 判断并设置尺寸位置
*/
jdugeSize () {
const box = this.$refs.box
const size = this.getCalculateSize()
let uh = 0
let uw = 0
if (this.state.pptBoxOnly) { // 仅ppt
uw = size.ppt.w
uh = size.ppt.h
if (this.$refs.ppt) {
this.$refs.ppt.setSize(uw, uh)
}
} else if (this.state.pptBoxShow) { // 同时显示video、ppt
if (this.$refs.ppt) {
this.$refs.ppt.setSize(size.ppt.w, size.ppt.h)
}
if (this.$refs.video) {
this.$refs.video.setSize(size.video.w, size.video.h)
}
uw = size.ppt.w + size.video.w
uh = size.video.h
} else { // 只显示video
uw = size.video.w
uh = size.video.h
if (this.$refs.video) {
this.$refs.video.setSize(uw, uh)
}
}
this.state.calculatedSize = true
box.style.paddingLeft = '' + ((size.space.w - uw) / 2 + PLAY_SPACE_WIDTH) + 'px'
box.style.paddingTop = '' + ((size.space.h - uh) / 2 + PLAY_SPACE_HEIGHT) + 'px'
},
/* 获取各个元素计算后的尺寸 */
getCalculateSize () {
const container = this.$refs.container
const w = container.offsetWidth - PLAY_SPACE_WIDTH * 2
const h = container.offsetHeight - 53 - PLAY_SPACE_HEIGHT * 2 // 减去底部操作条高度
const videoRatio = 550 / 363
const pptRatio = 336 / 236
const r = {
space: { w: w, h: h }, // 可容纳play的容器总大小
video: { w: 0, h: 0 }, // 视频大小
ppt: { w: 0, h: 0 } // ppt容器大小
}
if (this.state.pptBoxOnly) { // 仅ppt
r.ppt.w = w < h * pptRatio ? w : h * pptRatio
r.ppt.h = h < w / pptRatio ? h : w / pptRatio
} else if (this.state.pptBoxShow) { // 同时显示video、ppt
const halfW = w / 2
const vw = halfW < h * videoRatio ? halfW : h * videoRatio
const vh = h < halfW / videoRatio ? h : halfW / videoRatio
const ph = vh
const pw = ph * pptRatio
r.video.w = vw
r.video.h = vh
r.ppt.w = pw
r.ppt.h = ph
} else { // 只显示video
r.video.w = w < h * videoRatio ? w : h * videoRatio
r.video.h = h < w / videoRatio ? h : w / videoRatio
}
return r
},
/* 计数器内部执行方法 */
hearBeatFunc (_time) {
if (this.chapterVideo && this.isPlaying) {
if (this._rProgress && this._rProgress.id) {
} else {
this._rProgress = _.assignIn({}, this.chapterVideo.progress)
/* 增加 帧点 视频记录 精确粒度 到 秒 */
const _arr = []
const _len = this.chapterVideo.video_length || 1
for (let i = 0; i < _len; i++) {
_arr.push(0)
}
this.videoFlash.mapFrames = this._rProgress.map.length ? this._rProgress.map : _arr
this._rProgress.map = this.videoFlash.mapFrames
}
const _rProgress = this._rProgress
const _duration = this.videoFlash.duration
const tempTime = Math.min(this.videoFlash.lastTime, _duration)
const _speed = this.videoFlash.speed
const _isSeek = this.videoFlash.isSeek
const _queue = this.videoFlash.queueFrames
/* 第一次播放时 */
if (_rProgress.cpt === 0 && _rProgress.mpt === 0 && _rProgress.pt === 0) {
if (/skip=1/.test(document.cookie)) {
_rProgress.pt = 7 // 跳过片头设置片头时间
}
}
/* 拖动进度条,不记录时间 */
if (!_isSeek) {
/* 直接给出 增加定时器 循环定时 的每次 执行传输时间 */
_rProgress.pt += (_time / 1000) * _speed
/* cpt 和 mpt 应该没问题 */
_rProgress.cpt = tempTime
_rProgress.mpt = tempTime > _rProgress.mpt ? tempTime : _rProgress.mpt
/* mpt 最大不能超过 总时长 */
_rProgress.mpt = _rProgress.mpt > _duration ? _duration : _rProgress.mpt
if (parseInt(_rProgress.mpt) > (parseInt(_duration) + 10000)) {
this.$message.info('视频播放出错,请刷新页面重新观看。')
}
_rProgress.ps = _queue
_rProgress.vid = this.id
/* 调用接口执行刷新 */
this.$emit('updateProgress', this._rProgress)
}
/* 实时监测 - 是否在播放中 */
this.isPlaying = false
}
},
/* 初始化定时器 */
createHeartTime (time) {
const _time = time || 10000
this.destroyHeartTime()
/* 增加 心跳 记录 视频学习时间 */
this.hearBeat = setInterval(() => {
this.hearBeatFunc(_time)
}, _time)
},
/* 消除定时器 */
destroyHeartTime () {
this.hearBeat && clearInterval(this.hearBeat)
}
}
}
</script>
<template>
<div class="play-ppt" ref="wrap">
<template v-if="ppts.length">
<div class="play-preview" ref="preview">
<template v-if="ppts[state.index] && ppts[state.index].ppt_url">
<img :src="ppts[state.index].ppt_url" class="play-ppt-img" style='vertical-align: middle' />
</template>
</div>
<div class="play-controls cl">
<div style="float: left;">
<template v-if="state.index >= 0">
<a href="#" @click="prev" style='margin: 0 20px 0 0; color: #fff;'><i class="el-icon-arrow-left"></i></a>
</template>
<template v-if="state.index + 1 < ppts.length">
<a href="#" @click="next"><i class="el-icon-arrow-right" style="color: #fff;"></i></a>
</template>
</div>
<div class="play-page">
<span class="play-now">{{state.index + 1}}</span>
/
<span class="play-total">{{ppts.length}}</span>
</div>
<div class="play-amazing">
<i :class="['el-icon-self-xuexiao', (state.sync ? 'active' : '')]" @click="onToggleSync"></i>
<i class="el-icon-self-quanping" @click="() => { this.$emit('onPptOnly') }"></i>
<i class="el-icon-self-shipin" @click="onSetVideoTime"></i>
<i class="el-icon-self-guanbi" @click="() => { this.$emit('onClose') }"></i>
</div>
</div>
</template>
</div>
</template>
<script>
/**
* ppt播放框
* 如果同步显示,则同步props.currentIndex到state.index,否则,仅使用state.index
*/
export default {
props: {
ppts: { type: Array, require: false },
currentIndex: { type: Number, require: false, default: 0 }
},
data () {
return {
state: {
index: this.currentIndex, // ppt展示序号
sync: true // 视频播放时,同步调整ppt展示
}
}
},
watch: {
currentIndex: {
handler () {
if (this.state.sync) {
this.state.index = this.currentIndex
}
}
}
},
methods: {
gotoIndex (index) {
this.state.index = index
},
getIndex (index) {
return Math.min(this.ppts.length - 1, Math.max(0, index))
},
prev (e) {
this.state.index = this.getIndex(this.state.index - 1)
this.state.sync = false
},
next (e) {
this.state.index = this.getIndex(this.state.index + 1)
this.state.sync = false
},
onToggleSync (e) {
this.state.sync = !this.state.sync
this.state.index = this.state.sync ? this.currentIndex : this.state.index
},
onSetVideoTime (e) {
this.$emit('onVideoSyncTime', this.ppts[this.state.index].ppt_point)
},
setSize (w, h) {
this.$refs.wrap.style.width = w + 'px'
this.$refs.wrap.style.height = h + 'px'
this.$refs.preview.style.lineHeight = (h - 44) + 'px'
}
}
}
</script>
<template>
<div id="playerWrap">
<div id="player">
<p>您还没有安装flash播放器,请 <a href="http://www.adobe.com/go/getflash" target="_blank">点击这里安装</a></p>
</div>
</div>
</template>
<style lang="scss" scoped>
#player p { color: #fff; text-align: center; padding: 50px 0; }
#player p a { color: #b01c40; text-decoration: underline; }
</style>
<script>
import swfobject from 'VideoJs'
// 播放器ID
const PLAYER_WRAP_ID = 'playerWrap'
const PLAYER_ID = 'player'
const SKIP_BEGIN_TIME = 7 // 跳过片头设置片头时间
let continueStart = 0 // 继续学习初始值
export default {
props: {
lastTime: { type: Number, require: false },
videoId: { type: String, require: false },
width: { type: Number, require: false },
height: { type: Number, require: false },
username: { type: String, require: false },
videoSrt: { type: String, require: false },
autoPlay: { type: Boolean, require: false, default: true },
chapterVideo: { type: Object, require: false }
},
mounted () {
this.definWindowFun()
// console.log(PLAYER_ID, this.videoId, this.autoPlay, this.videoSrt, this.username, this.width, this.height)
// this.renderPlayer(PLAYER_ID, this.videoId, this.autoPlay, this.videoSrt, this.username, this.width, this.height)
},
watch: {
videoId: {
handler () {
if (this.videoId) {
/* 注意 flash 初始化时,需要页面DOM存在 + videoId存在 */
continueStart = this.lastTime || 0 // 如果传递有上次播放时间,则记录缓存,以便player.start时使用
this.renderPlayer(PLAYER_ID, this.videoId, this.autoPlay, this.videoSrt, this.username, this.width, this.height)
}
}
}
},
methods: {
/* 定义windows下,播放事件和初始化回调 */
definWindowFun () {
const that = this
// 开始播放,如果设置了跳过片头则设置播放时间
window._playerStart = function () {
if (/skip=1/.test(document.cookie)) {
that.getPlayer().callAction('setCurrentTime', Math.max(continueStart, SKIP_BEGIN_TIME)) // 跳到第6秒开始播放
} else if (continueStart) {
that.getPlayer().callAction('setCurrentTime', continueStart)
}
}
// 播放过程中不断触发,传递当前播放到的时间
window._playerIng = function (time) {
$('#' + PLAYER_WRAP_ID).trigger('player.time', { time, duration: that.getPlayer().callAction('getDuration'), quality: that.getPlayer().callAction('getQuality'), isSeek: false })
}
// 拖动播放进度条
window._playerSeek = function () {
$('#' + PLAYER_WRAP_ID).trigger('player.seek', { time: that.getPlayer().callAction('getCurrentTime'), duration: that.getPlayer().callAction('getDuration'), quality: that.getPlayer().callAction('getQuality'), isSeek: true })
}
// 视频播放结束
window._playerFinish = function () {
that.$emit('handlePlayfinish', { time: that.getPlayer().callAction('getDuration') })
}
// 播放控件 - 初始化完成时,注册播放事件
window._playerCallback = function () {
const player = that.getPlayer()
if (player) {
// player.register('onLoadStart', '') // 开始loading加载
player.callAction('register', 'onCanplay', '_playerStart') // 开始播放视频内容
player.callAction('register', 'onPlaying', '_playerIng') // 播放中触发,300ms一次
// player.register('onPause', '') // 暂停
// player.register('onResume', '') // 恢复播放
player.callAction('register', 'onSeekComplete', '_playerSeek') // 拖动进度条
player.callAction('register', 'onEnded', '_playerFinish') // 结束
}
}
},
/* flash swf视频 对象渲染 采用 VideoJs插件渲染 */
renderPlayer (domId, vid, autoPlay, srt, username, width, height) {
autoPlay = typeof autoPlay === 'undefined' ? 1 : autoPlay - 0
// For version detection, set to min. required Flash Player version, or 0 (or 0.0.0), for no version detection.
const swfVersionStr = '11.1.0'
// To use express install, set to playerProductInstall.swf, otherwise the empty string.
const xiSwfUrlStr = 'playerProductInstall.swf'
const flashvars = {
autoStart: autoPlay,
vid: vid,
isShowSpeeder: 1,
videoType: 1, // 0为mp4模式 1为cc模式
callback: '_playerCallback'
}
if (srt) { flashvars.srtUrl = srt }
if (username) { flashvars.username = username }
// flashvars.videoType = 1; // 0为mp4模式 1为cc模式
const params = {
quality: 'high',
bgcolor: '#000000',
allowscriptaccess: 'always',
allowfullscreen: 'true'
}
const attributes = {
id: domId,
name: domId,
align: 'middle',
wmode: 'opaque'
}
// render
swfobject.embedSWF(
// require('player'),
'/static/videoJs/swf/Player1705192.swf',
domId,
parseInt(width),
parseInt(height),
swfVersionStr,
xiSwfUrlStr,
flashvars,
params,
attributes
)
// 绑定事件监听
this.listenPlayerEvents()
},
listenPlayerEvents () {
$('#' + PLAYER_WRAP_ID).off('player.time player.seek').on('player.time player.seek', (e, data) => {
this.$emit('handlePlayTime', data)
})
},
// ========= 提供播放后,其他组件可使用控制播放的方法 ===========
// 获取视频对象
getPlayer () {
return document.getElementById(PLAYER_ID)
},
getTime () {
const player = this.getPlayer()
if (player) {
return player.callAction('getCurrentTime')
} else {
return 0
}
},
// 设置视频跳转时间
setTimeTo (time) {
const player = this.getPlayer()
if (player) {
player.callAction('setCurrentTime', time + 2) // flash实际播放值会大概小个一两秒,因此添加偏移
}
},
// 执行“跳过片头”操作
skipBegin () {
const player = this.getPlayer()
if (player && player.callAction('getCurrentTime') < SKIP_BEGIN_TIME) {
player.callAction('setCurrentTime', SKIP_BEGIN_TIME)
}
},
// 设置视频尺寸
setSize (w, h) {
const player = this.getPlayer()
if (player) {
player.width = w
player.height = h
}
}
}
}
</script>
<template>
<div id="playerWrap">
<!-- H5播放 -->
<div id="player"></div>
</div>
</template>
<script>
import cAction from '@actions'
import Aliplayer from 'Aliplayer'
import AliPlayerComponent from 'AliPlayerComponent'
import Base64 from 'Base64'
// 播放器ID
const PLAYER_WRAP_ID = 'playerWrap'
const PLAYER_ID = 'player'
const SKIP_BEGIN_TIME = 7 // 跳过片头设置片头时间
// videoPlayer
let videoPlayer = null
let objPlayer = null
let continueStart = 0 // 继续学习初始值
export default {
props: {
lastTime: { type: Number, require: false },
videoId: { type: String, require: false },
width: { type: Number, require: false },
height: { type: Number, require: false },
username: { type: String, require: false },
videoSrt: { type: String, require: false },
autoPlay: { type: Boolean, require: false, default: true },
chapterVideo: { type: Object, require: false }
},
data () {
return {
videoArr: {}, // 视频流
timeSetInterval: null
}
},
mounted () {
this.definWindowFun()
this.addWatermark()
},
destroyed () {
if (this.timeSetInterval) {
clearInterval(this.timeSetInterval)
this.timeSetInterval = null
}
},
watch: {
videoId: {
handler (oldId, newId) {
// if (newId === undefined) { return }
if (this.videoId) {
/* 注意 flash 初始化时,需要页面DOM存在 + videoId存在 */
continueStart = this.lastTime || 0 // 如果传递有上次播放时间,则记录缓存,以便player.start时使用
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.chapterAction.getCurrentChapterDetailAliyun(this.videoId).then(json => {
this.videoArr = json.video
const player = this.getPlayer()
if (!objPlayer || !player.innerHTML) {
this.renderPlayer(PLAYER_ID, this.videoId, this.autoPlay, this.videoSrt, this.username, this.width, this.height)
} else {
this.playNextVideo(PLAYER_ID, this.videoId, this.autoPlay, this.videoSrt, this.username, this.width, this.height)
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
}
}
}
},
methods: {
/* 定义windows下,播放事件和初始化回调 */
definWindowFun () {
const that = this
// 开始播放,如果设置了跳过片头则设置播放时间
window._playerStart = function () {
continueStart = videoPlayer.getDuration() < continueStart ? videoPlayer.getDuration() : continueStart
if (/skip=1/.test(document.cookie)) {
videoPlayer.seek(parseInt(Math.max(continueStart, SKIP_BEGIN_TIME))) // 跳到第6秒开始播放
} else if (continueStart) {
videoPlayer.seek(parseInt(continueStart))
}
}
// 播放过程中不断触发,传递当前播放到的时间
window._playerIng = function (time) {
var _quality = videoPlayer.getComponent('QualityComponent').definition
var rate = videoPlayer.getComponent('RateComponent').html.innerText
switch (_quality) {
case 'FD': _quality = '普通'; break
case 'LD': _quality = '清晰'; break
case 'SD': _quality = '高清'; break
}
if (rate) { _quality += (' ' + rate) }
time = videoPlayer.getCurrentTime()
$('#' + PLAYER_WRAP_ID).trigger('player.time', { time, duration: videoPlayer.getDuration(), quality: _quality, isSeek: false })
}
// 开始拖拽
window._playerSeekStart = function (time) {
/* 点击拖拽时,加载缓存 -> canplay 事件 -> timeupdate 事件 */
// continueStart = time
}
// 拖动播放进度条
window._playerSeek = function () {
var _quality = videoPlayer.getComponent('QualityComponent').definition
var rate = videoPlayer.getComponent('RateComponent').html.innerText
switch (_quality) {
case 'FD': _quality = '普通'; break
case 'LD': _quality = '清晰'; break
case 'SD': _quality = '高清'; break
}
if (rate) { _quality += (' ' + rate) }
$('#' + PLAYER_WRAP_ID).trigger('player.seek', { time: videoPlayer.getCurrentTime(), duration: videoPlayer.getDuration(), quality: _quality, isSeek: true })
}
// 视频播放结束
window._playerFinish = function () {
that.$emit('handlePlayfinish', { time: videoPlayer.getDuration() })
}
// 视频播放 - error
window._player403Stop = function (e) {
/* 采集所有视频播放 - 错误信息 */
const socket = window.G.socket
let str = ''
const version = window.G.VERSION // 客户端版本号,每次更新后,更新版本号。可以方便查看是否客户端都是最新版本
if (window.G.UserInfo && window.G.UserInfo.student_info) {
let tmp_info = window.G.UserInfo.student_info // eslint-disable-line
str = tmp_info.personal_name + ':' + tmp_info.telephone + ':' + tmp_info.email + ':' + tmp_info.id + ':' + window.G.UserInfo.auth_key + ':' + (window.G.pwd || '')
}
str += ':' + version
if (socket && socket.readyState === 1) {
const _d = e.paramData || {}
_d.m3u8Url = that.videoArr[videoPlayer.getComponent('QualityComponent').definition || 'LD'] || ''
_d.m3u8RequestId = window.G.m3u8RequestId
_d.UA = window.navigator.userAgent
var arr = JSON.stringify({'action': 'aliVideoErr', info: Base64.encode(str), 'auth': 'aliVideoErr', 'code': Base64.encode(e.paramData.error_code), 'err': Base64.encode(JSON.stringify(_d))}).split('')
// var arr = JSON.stringify({ 'action': 'aliVideoErr', info: Base64.encode(str), 'auth': 'aliVideoErr', 'code': Base64.encode('4006') }).split('')
var strArr = []
for (var i = 0; i < arr.length; i++) {
strArr[i] = arr[i].charCodeAt()
}
var data = new Uint8Array(strArr)
socket.send(data.buffer)
} else {
console.log('not link build success, status: ' + socket.readyState)
}
/* 过期 重新刷新 */
if (e.paramData.error_code === 4006) {
that.$message.error('视频播放错误,请刷新页面重试!')
} else if (e.paramData.error_code === 4016) {
continueStart = videoPlayer.getCurrentTime()
/* 当视频过期时,重新 获取接口 更改播放地址 重新播放 */
const loading = that.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.chapterAction.getCurrentChapterDetailAliyun(that.videoId).then(json => {
that.videoArr = json.video
const player = that.getPlayer()
if (!objPlayer || !player.innerHTML) {
that.renderPlayer(PLAYER_ID, that.videoId, that.autoPlay, that.videoSrt, that.username, that.width, that.height)
} else {
that.playNextVideo(PLAYER_ID, that.videoId, that.autoPlay, that.videoSrt, that.username, that.width, that.height)
}
}).catch(e => { that.$message.error(e.message) }).finally(() => { loading.close() })
} else {
that.$message.error('请将该错误,截图发给管理员。err: ' + e.paramData.error_code)
}
}
// 播放控件 - 初始化完成时,注册播放事件
// window._playerCallback =
},
/* 采用 aliyun 播放器 */
renderPlayer (domId, vid, autoPlay, srt, username, width, height) {
autoPlay = typeof autoPlay === 'undefined' ? 1 : autoPlay - 0
objPlayer = new Aliplayer({
id: domId,
source: JSON.stringify(this.videoArr),
width: '100%',
height: '100%',
autoplay: !!autoPlay,
vodRetry: 0, // 将重试 设置为0,可以防止视频过期
isLive: false,
controlBarVisibility: 'always',
definition: 'FD,LD,SD',
defaultDefinition: 'LD',
components: [{
name: 'QualityComponent',
type: AliPlayerComponent.QualityComponent
}, {
name: 'RateComponent',
type: AliPlayerComponent.RateComponent
}]
}, function (player) {
videoPlayer = player
console.log('The player is created')
/* Register the sourceloaded of the player, query the resolution of the video, invoke the resolution component, and call the setCurrentQuality method to set the resolution. */
player.on('sourceloaded', function (params) {
var paramData = params.paramData
var desc = paramData.desc
var definition = paramData.definition
player.getComponent('QualityComponent').setCurrentQuality(desc, definition)
})
if (videoPlayer) {
// player.register('onLoadStart', '') // 开始loading加载
videoPlayer.on('ready', window._playerStart) // 开始播放视频内容
videoPlayer.on('timeupdate', window._playerIng) // 播放中触发,300ms一次
// player.register('onPause', '') // 暂停
// player.register('onResume', '') // 恢复播放
videoPlayer.on('startSeek', window._playerSeekStart) // 开始拖拽
videoPlayer.on('completeSeek', window._playerSeek) // 拖动进度条
videoPlayer.on('ended', window._playerFinish) // 结束
videoPlayer.on('error', window._player403Stop) // 播放出现未授权情况
}
})
this.setSize(width, height)
// 绑定事件监听
this.listenPlayerEvents()
},
/* 增加水印处理 */
addWatermark () {
if (this.timeSetInterval) {
clearInterval(this.timeSetInterval)
this.timeSetInterval = null
}
/* 60s显示一次,一次显示10s,位置随机 */
let count1 = 0
let count2 = 0
this.timeSetInterval = setInterval(() => {
count1 += 1
count2 += 1
if (count2 >= 10) {
if ($('#' + PLAYER_ID) && $('#' + PLAYER_ID).length) {
const _o = $('#' + PLAYER_ID).find('#coverWatermark')
if (_o && _o.length) {
_o.remove()
}
}
count2 = 0
}
if (count1 >= 60) {
if ($('#' + PLAYER_ID) && $('#' + PLAYER_ID).length) {
$('#' + PLAYER_ID).append([
'<div id="coverWatermark" style="position: absolute; z-index: 99999; top: 60px; left: 0; right: 0; bottom: 60px;">',
' <div style="position: absolute; color: #eee; font-size: 12px; top: ' + (Math.random() * ($('#' + PLAYER_ID).outerHeight() - 120)) + 'px; left: ' + (Math.random() * ($('#' + PLAYER_ID).outerWidth() - 200)) + 'px;">' + (window.G.UserInfo.username || '') + '</div>',
'</div>'
].join(''))
}
count1 = 0
}
}, 1000)
},
listenPlayerEvents () {
$('#' + PLAYER_WRAP_ID).off('player.time player.seek').on('player.time player.seek', (e, data) => {
this.$emit('handlePlayTime', data)
})
},
playNextVideo (domId, vid, autoPlay, srt, username, width, height) {
/* 获取视频源 */
this.changeVideoArr(vid, () => {
videoPlayer._urls[0].FD = this.videoArr.FD
videoPlayer._urls[1].LD = this.videoArr.LD
videoPlayer._urls[2].SD = this.videoArr.SD
var q = videoPlayer.getComponent('QualityComponent').definition || 'LD'
objPlayer.loadByUrl(this.videoArr[q])
})
},
changeVideoArr (vid, callback) {
this.$emit('changeVideoArr', { vid, callback })
},
// ========= 提供播放后,其他组件可使用控制播放的方法 ===========
// 获取视频对象
getPlayer () {
return document.getElementById(PLAYER_ID)
},
getTime () {
if (videoPlayer) {
return videoPlayer.getCurrentTime()
} else {
return 0
}
},
// 设置视频跳转时间
setTimeTo (time) {
if (videoPlayer) {
videoPlayer.seek(parseInt(time + 2)) // flash实际播放值会大概小个一两秒,因此添加偏移,改为H5 不知道是否还存在这个问题
}
},
// 执行“跳过片头”操作
skipBegin () {
if (videoPlayer && videoPlayer.getCurrentTime() < SKIP_BEGIN_TIME) {
videoPlayer.seek(parseInt(SKIP_BEGIN_TIME))
}
},
// 设置视频尺寸
setSize (w, h) {
const player = this.getPlayer()
if (player) {
player.style.width = w + 'px'
player.style.height = h + 'px'
}
}
}
}
</script>
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{chapterName}}</h3></div></div>
<div class="play-paper-content play-chapter-work">
<template v-if="chapterWork.questions && chapterWork.questions.length" >
<ul>
<template v-for="(item, index) in chapterWork.questions">
<li v-bind:key="index">
<div class="work-number">{{index + 1}}.</div>
<div class="work-title">
<div class="edit_html" v-html="item.question_content"></div>
</div>
<textarea id="editor-chapterWork"></textarea>
<div style="height: 20px;"></div>
<!-- <el-upload
ref="upFile"
class="upload-demo"
action=""
:multiple="false"
:limit="1"
:show-file-list="false"
:on-change="handleChange"
:http-request="uploadFile"
:file-list="filesArr">
请上传对应的文件附件:<el-button type="text">点击上传</el-button>
<template v-if="successFileUrl">
{{successFileUrl.replace(/.*\/([^\/]*\.docx)$/gi, '$1')}}
</template>
</el-upload> -->
<template v-if="successFileUrl">
<a :href="successFileUrl">下载已上传文件</a>
</template>
<!-- <div style="height: 20px;"></div> -->
<!-- <p class="help help-file">只支持docx格式的文件,文件小于10M</p> -->
<!-- {answer.file_url && <a style={{display: 'block', marginBottom: '20px', color: 'blue'}} href={answer.file_url} >下载附件</a> } -->
</li>
</template>
</ul>
</template>
<template v-else>
<!-- <p class="no-data">暂无数据</p> -->
</template>
<!-- <p class="text-danger">{this.state.error}</p> -->
<template v-if="this.deadLine">
<p style="color: red">请于截止日期 {{this.deadLine}} 前提交</p>
</template>
<div class="area-btns">
<el-button type="primary" @click="submitWork" :disabled="!!homeData.checker_time || deadLineFlag">{{homeData.checker_time ? '已批改' : '提交'}}</el-button>
<span class="help-info">&emsp;&emsp;在获老师批改之前,可以多次提交,将以最后一次提交为准</span>
<template v-if="homeData.checker_time">
<div class="play-paper-check">
<h4>已获批改 <small>批改于{{homeData.checker_time}}</small></h4>
<div class="play-paper-check-item"><b>评分:</b>{{homeData.score}}</div>
<div class="play-paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="homeData.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="homeData.created_time">
<p class="help">已于 {{homeData.created_time}} 提交,等待批改中</p>
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import cAction from '@action'
import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
export default {
props: {
chapterId: { type: String, require: false },
chapterWork: { type: Object, require: false },
chapterName: { type: String, require: false },
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
data () {
return {
ckeditor: null,
successFileUrl: '',
filesArr: [],
file: {
id: 'WU_FILE_0',
name: '',
type: '',
lastModifiedDate: '',
size: '',
file: ''
},
homeData: {},
/* 设置是否可以初始化 ckeditor */
setTime: null,
isInit: false,
deadLine: '',
deadLineFlag: false
}
},
/* 本组件 仅支持 单个 ckeditor 存在 */
mounted () {
this.loadAjax()
},
updated () {},
destroyed () {
/* 清空 ckeditor 需要调用方法删除 并 在DOM结构中也移除 */
this.ckeditor && this.ckeditor.destroy(true)
this.ckeditor = null
},
methods: {
handleChange (file, filelist) {
this.file.name = file.raw.name
this.file.type = file.raw.type
this.file.lastModifiedDate = file.raw.lastModifiedDate
this.file.size = file.raw.size
this.file.file = file.raw
},
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getHomework(this.sid, this.cid, this.id).then(data => {
this.homeData = data
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => {
this.setTime = setInterval(() => {
if (document.querySelector('#editor-chapterWork')) {
this.initckeditor()
if (this.homeData.work_contents) {
let json = JSON.parse(this.homeData.work_contents)
if (json[0].is_encoded) {
json[0].descreption = Base64.decode(json[0].descreption)
}
this.successFileUrl = json[0].file_url
this.ckeditor.setData(json[0].descreption)
} else {
this.successFileUrl = ''
this.ckeditor.setData('')
}
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
clearInterval(this.setTime)
}
}, 50)
loading.close()
})
setTimeout(() => {
cAction.Player.getHomeworkStopTime(this.sid, this.cid, this.chapterId).then(data => {
this.deadLine = data.dead_line || ''
let deadLine = data.dead_line ? new Date(data.dead_line).getTime() : ''
// deadLine = new Date().getTime() - 100
this.deadLineFlag = ((new Date().getTime() > deadLine) && !!deadLine)
// console.log(this.deadLine)
}).catch(e => { this.$message.error(e.message) }).finally(() => {})
}, 500)
},
submitWork () {
if (!this.ckeditor.getData()) {
this.$message.error('请填写内容')
return
}
/* 只能提交 单个问题 */
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
let str = JSON.stringify([{
question_id: this.chapterWork.questions[0].id,
descreption: Base64.encode(this.ckeditor.getData()),
file_url: this.successFileUrl,
is_encoded: 1
}])
cAction.Player.updateHomework({
semester_id: this.sid,
course_id: this.cid,
chapter_id: this.chapterId,
work_id: this.id,
work_contents: str,
duration: 30 + Math.floor(Math.random() * 1000)
}).then(data => {
if (data.status) {
this.$message({ type: 'success', message: '提交成功,等待批改' })
this.loadAjax()
}
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => { loading.close() })
},
uploadFile () {
if (!/\.(docx)$/gi.test(this.file.name)) {
this.$message.error('文件格式不对,请重新上传')
this.filesArr.pop()
return
}
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.uploadFile(this.file).then(data => {
this.successFileUrl = data.url
this.filesArr.pop()
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => { loading.close() })
},
/* 初始化 ckeditor */
initckeditor () {
!this.ckeditor && (this.ckeditor = CKEDITOR.replace('editor-chapterWork', {
height: 300,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{ name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: [ 'Find', 'Replace' ] },
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl' ] },
{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
{ name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule' ] }
]
}))
}
},
watch: {
id: {
handler () {
this.loadAjax()
}
}
}
}
</script>
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>课程资料</h3></div></div>
<div class="play-paper-content">
<template v-if="courseInfo.length">
<ul class="play-read-files">
<template v-for="(item, index) in courseInfo">
<li v-bind:key="index"><a :href="item.file_url" target="_blank">{{item.file_name}}</a></li>
</template>
</ul>
</template>
<template v-else>
<p class="no-data">暂无课程资料</p>
</template>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
courseInfo: { type: Array, require: false }
}
}
</script>
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>课程大作业</h3></div></div>
<div class="play-paper-content">
<div class="play-paper-step">&#9312; 阅读大作业要求</div>
<div class="edit_html" v-html="courseWork.curriculum_essay || ''"></div>
<p>截止日期:{{courseWork.essay_date || ''}}</p>
<div class="play-paper-step">&#9313; 填写作业主题、正文,上传附件(点击“提交”保存)</div>
<template v-if="courseWork.curriculum_name">
<div style="font-size: 20px;">主题<em style="font-size: 12px;">(最长不超过50个字)</em></div>
<el-input v-model="title" type="text" placeholder="主题" maxlength='100'></el-input>
<div style="font-size: 20px;">正文</div>
<textarea id="editor-courseWork"></textarea>
<div style="height: 20px;"></div>
<el-upload
ref="upFile"
class="upload-demo"
action=""
:multiple="false"
:limit="1"
:show-file-list="false"
:on-change="handleChange"
:http-request="uploadFile"
:file-list="filesArr">
请上传对应的文件附件:<el-button type="text">点击上传</el-button>
<template v-if="successFileUrl">
{{successFileUrl.replace(/.*\/([^\/]*\.docx)$/gi, '$1')}}
</template>
</el-upload>
<template v-if="successFileUrl">
<a :href="successFileUrl">下载已上传文件</a>
</template>
<div style="height: 20px;"></div>
<p class="help help-file">只支持docx格式的文件,文件小于10M</p>
<!-- {answer.file_url && <a style={{display: 'block', marginBottom: '20px', color: 'blue'}} href={answer.file_url} >下载附件</a> } -->
</template>
<template v-else>
<!-- <p class="no-data">暂无数据</p> -->
</template>
<!-- <p class="text-danger">{this.state.error}</p> -->
<div class="area-btns">
<div class="play-paper-step">&#9314; 截止日期前提交</div>
<el-button type="primary" @click="submitWork" :disabled="homeData.check_date">{{homeData.check_date ? '已批改' : '提交'}}</el-button>
<span class="help-info">&emsp;&emsp;在获老师批改之前,可以多次提交,将以最后一次提交为准</span>
<template v-if="homeData.check_date">
<div class="play-paper-check">
<h4>已获批改 <small>批改于{{homeData.check_date}}</small></h4>
<div class="play-paper-check-item"><b>评分:</b>{{homeData.score}}</div>
<div class="play-paper-check-item">
<b>评语:</b>
<!-- <div class="edit_html" dangerouslySetInnerHTML={{__html:work.check_comments}}></div>-->
<div class="edit_html" v-html="homeData.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="homeData.created_time">
<p class="help">已于 {{homeData.created_time}} 提交,等待批改中</p>
<template v-if="homeData.updated_time !== homeData.created_time">
<p class="help">(最后一次提交时间: {{homeData.updated_time}}</p>
</template>
</template>
</div>
</div>
</div>
</div>
</template>
<script>
import cAction from '@action'
import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
export default {
props: {
courseWork: { type: Object, require: false },
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
data () {
return {
ckeditor: null,
successFileUrl: '',
successData: '', // 上传后,解析过来的 base64字符串
title: '',
filesArr: [],
file: {
id: 'WU_FILE_0',
name: '',
type: '',
lastModifiedDate: '',
size: '',
file: '',
special: 'course-work' // 标识 是从 大作业上传的
},
homeData: {},
/* 设置是否可以初始化 ckeditor */
setTime: null,
isInit: false
}
},
/* 本组件 仅支持 单个 ckeditor 存在 */
mounted () {
this.loadAjax()
},
updated () {},
destroyed () {
/* 清空 ckeditor 需要调用方法删除 并 在DOM结构中也移除 */
this.ckeditor && this.ckeditor.destroy(true)
this.ckeditor = null
},
methods: {
handleChange (file, filelist) {
this.file.name = file.raw.name
this.file.type = file.raw.type
this.file.lastModifiedDate = file.raw.lastModifiedDate
this.file.size = file.raw.size
this.file.file = file.raw
},
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getCourseHomework(this.sid, this.cid).then(data => {
this.homeData = data
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => {
this.setTime = setInterval(() => {
if (document.querySelector('#editor-courseWork')) {
this.initckeditor()
if (this.homeData.course_id) {
let json = this.homeData
this.successFileUrl = json.file_url
this.ckeditor.setData(json.essay_description)
this.title = json.essay_name
}
clearInterval(this.setTime)
}
}, 50)
loading.close()
})
},
submitWork () {
if (!this.title) {
this.$message.error('请输入主题')
return
}
// if (!this.successFileUrl) {
// this.$message.error('请上传附件')
// return
// }
if (!this.ckeditor.getData()) {
this.$message.error('请填写内容')
return
}
/* 只能提交 单个问题 */
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
let _strContent = this.ckeditor.getData()
.replace(/<(a|b|p|em|span|strong|table|tbody|thead|th|tr|td|div).*?>/gi, '')
.replace(/<\/.*?>/gi, '')
cAction.Player.updateCourseHomework(this.sid, this.cid, {
essay_name: this.title,
essay_description: this.ckeditor.getData(),
url: this.successFileUrl,
course_id: this.cid,
semester_id: this.sid,
raw: this.successData || Base64.encode(_strContent) // 新增 docx解析字段,必传,论文查重字段,再把内容改成base64 传入
}).then(data => {
if (data.status) {
this.$message({ type: 'success', message: '提交成功,等待批改' })
this.loadAjax()
}
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => { loading.close() })
},
uploadFile () {
if (!/\.(docx)$/gi.test(this.file.name)) {
this.$message.error('文件格式不对,请重新上传')
this.filesArr.pop()
return
}
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.uploadFile(this.file).then(data => {
if (data.error) {
this.$message.error('提示待定!!!!!')
} else {
this.successFileUrl = data.url
this.successData = data.dataStr || '' // 新增base64字符串 解析docx文档
this.filesArr.pop()
}
}).catch(e => { this.filesArr.pop(); this.$message.error(e.message) }).finally(() => { loading.close() })
},
/* 初始化 ckeditor */
initckeditor () {
!this.ckeditor && (this.ckeditor = CKEDITOR.replace('editor-courseWork', {
height: 600,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{ name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: [ 'Find', 'Replace' ] },
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl' ] },
{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
{ name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule' ] }
]
}))
}
}
}
</script>
<template>
<div class="play-paper">
<div class="play-paper-body">
<div class="play-paper-title"><div><h3>{{exam.title}}</h3></div></div>
<div class="play-paper-content play-chapter-exam">
<template v-if="exam.id">
<div class='exam'>
<div style='text-align: center;'>
<div class='topic'>
<!-- <div class='tit'>{{exam.title}}</div> -->
<template v-if='exam.score.total !== undefined'><div class='cur'>单选:{{exam.score.radio}}分,多选:{{exam.score.checkbox}}分,简答:{{exam.score.shortAnswer}}分,总分:{{exam.score.total}}</div></template>
</div>
</div>
<!-- 单选题 -->
<template v-if='exam.radioList.length'>
<template v-for='(item, index) in exam.radioList'>
<div v-bind:key="item.id" class='q-group' :data-index='index'>
<div class='q-num'>{{index+1}}.</div><div class='q-title' v-html='item.content'></div><div class='q-type'>(单选题)</div>
<el-radio-group class='radio-group' v-model="item.user_answer">
<template v-for='(item1, index1) in item.options'>
<el-radio v-bind:key="item1.id" :label='item1.id' :disabled='!!item.right_answer && !!exam.type' :class='["radio", ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? "success" : "error") : "")]'>{{ index1 | getLetter() }}. {{item1.option}}</el-radio>
</template>
</el-radio-group>
<template v-if='item.right_answer && !!exam.type'><div class='result'>学生答案:<div :class='["stu", (item.right_answer === item.user_answer ? "success" : "error")]'>{{ item.user_answer | getRadioAnswer(item.options) }}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getRadioAnswer(item.options) }}</div></template>
</div>
</template>
</template>
<!-- 多选题 -->
<template v-if='exam.checkboxList.length'>
<template v-for='(item, index) in exam.checkboxList'>
<div v-bind:key="item.id" class='q-group' :data-index='index'>
<div class='q-num'>{{exam.radioList.length+index+1}}.</div><div class='q-title' v-html='item.content'></div><div class='q-type'>(多选题)</div>
<el-checkbox-group class='checkbox-group' v-model="item.user_answer">
<template v-for='(item1, index1) in item.options'>
<el-checkbox v-bind:key="item1.id" :label='item1.id' :disabled='!!item.right_answer.length && !!exam.type' :class='["checkbox", ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? "success" : "error") : "")]'>{{ index1 | getLetter() }}. {{item1.option}}</el-checkbox>
</template>
</el-checkbox-group>
<template v-if='item.right_answer.length && !!exam.type'><div class='result'>学生答案:<div :class='["stu", ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? "success" : "error")]'>{{ item.user_answer | getCheckboxAnswer(item.options) }}</div>&nbsp;&nbsp;&nbsp;&nbsp;正确答案:{{ item.right_answer | getCheckboxAnswer(item.options) }}</div></template>
</div>
</template>
</template>
<!-- 简答题 -->
<template v-if='exam.shortAnswerList.length'>
<template v-for='(item, index) in exam.shortAnswerList'>
<div class='q-group'>
<div class='q-sa-title'>{{exam.radioList.length+exam.checkboxList.length+index+1}}.&nbsp;&nbsp;简答题</div>
<div class="edit_html" v-html="item.content || ''"></div>
<textarea :id="('editor-exam' + index)" v-model="item.user_answer"></textarea>
<div style="height: 10px;"></div>
<component :is="item.upload.type" v-bind:key="item.upload.model" :item="item.upload" :formData="item" :isUpload="!exam.type"></component>
</div>
</template>
</template>
<div :class='["btn", (exam.type && "on")]' @click='submitExam' :data-submit='!!exam.type' @mousedown='_SubmitMouseLeftDown()'>{{exam.type ? "已提交" : "提交"}}</div>
<div class='care'>(注意:测试只有一次提交机会)</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</div>
</template>
</div>
</div>
</div>
</template>
<script>
import cAction from '@action'
import Base64 from 'Base64'
import CKEDITOR from 'CKEDITOR'
var getLetter = (val) => {
switch (val) {
case 0: return 'A'
case 1: return 'B'
case 2: return 'C'
case 3: return 'D'
case 4: return 'E'
case 5: return 'F'
case 6: return 'G'
case 7: return 'H'
case 8: return 'I'
case 9: return 'J'
case 10: return 'K'
case 11: return 'L'
case 12: return 'M'
}
}
export default {
props: {
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
filters: {
getLetter: getLetter,
getRadioAnswer: (val, arr) => {
for (let i = 0; i < arr.length; i++) {
if (arr[i].id === val) {
return getLetter(i)
}
}
},
getCheckboxAnswer: (val, arr) => {
let str = ''
for (let i = 0; i < val.length; i++) {
const tmpId = val[i]
for (let j = 0; j < arr.length; j++) {
if (arr[j].id === tmpId) {
str += getLetter(j) + ','
break
}
}
}
return str.substr(0, str.length - 1)
}
},
data () {
return {
_time: null, // 定时器,自动化提交
exam: {
// id: '1',
// title: '标题',
// type: 0, // 0: 暂存,可以继续答题;1: 提交,不能再继续答题
// radioList: [
// {
// id: '6622309081933676544',
// content: '\u5047\u8bbe\u8d27\u5e01\u9700\u6c42\u4e3aL=ky-hr\uff0c\u8d27\u5e01\u4f9b\u7ed9\u589e\u52a010\u4ebf\u7f8e\u5143\u800c\u5176\u5b83\u6761\u4ef6\u4e0d\u53d8\uff0c\u5219\u4f1a\u4f7fLM \u66f2\u7ebf( )',
// options: [
// {
// id: '6622310260604403712',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403713',
// option: '\u53f3\u79fb k\u4e58\u4ee510\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403714',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143\u9664\u4ee5k'
// },
// {
// id: '6622310260604403715',
// option: '\u53f3\u79fb k\u9664\u4ee510\u4ebf\u7f8e\u5143'
// }
// ],
// user_answer: '6622310260604403714',
// right_answer: '6622310260604403714',
// get_score: 30,
// score: 30
// }
// ],
// checkboxList: [
// {
// id: '6622310510798831616',
// content: '\u51ef\u6069\u65af\u5b8f\u89c2\u7ecf\u6d4e\u7406\u8bba\u7684\u4e3b\u8981\u524d\u63d0\u5305\u62ec\uff08\uff09',
// options: [
// {
// id: '6622310872641437696',
// option: '\u8fb9\u9645\u6d88\u8d39\u503e\u5411\u9012\u51cf'
// },
// {
// id: '6622310872641437697',
// option: '\u8d44\u672c\u8fb9\u9645\u6548\u7387\u9012\u51cf'
// },
// {
// id: '6622310872641437698',
// option: '\u4e0d\u786e\u5b9a\u6027\u4e0e\u6d41\u52a8\u6027\u504f\u597d'
// },
// {
// id: '6622310872641437699',
// option: '\u540d\u4e49\u5de5\u8d44\u521a\u6027'
// }
// ],
// user_answer: [
// '6622310872641437697',
// '6622310872641437698',
// '6622310872641437699'
// ],
// right_answer: [
// '6622310872641437697',
// '6622310872641437698'
// ],
// get_score: 30,
// score: 30
// }
// ],
// shortAnswerList: [
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true,
// headers: {
// 'tenant': 'sofia'
// }
// },
// html: `
// <div style="color: #72818c; font-size: 14px;">
// <p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M</p>
// </div>
// `
// }
// },
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true
// },
// html: `
// <div style="color: #72818c; font-size: 14px;">
// <p style="margin: 0;">支持doc,docx,ppt,xls,txt,rar,zip,pdf,jpg,pic,png格式的文件,文件小于30M</p>
// </div>
// `
// }
// }
// ]
}
}
},
mounted () {
this.loadAjax()
if (this._time) {
clearInterval(this._time)
this._time = null
}
this._time = setInterval(() => {
if (!this.exam.type) {
this.submitExam({ submitType: true }) // 暂存, submitType: true 暂存;其他或不填为提交
} else {
clearInterval(this._time)
this._time = null
}
}, 30000)
},
destroyed () {
if (this._time) {
console.log(11)
clearInterval(this._time)
this._time = null
}
},
methods: {
isCheckboxRight: (val, arr) => {
let flag = true
for (let i = 0; i < arr.length; i++) {
const tmpId = arr[i]
let j = 0
for (; j < val.length; j++) {
if (val[j] === tmpId) {
break
}
}
if (j === val.length) { flag = false; break }
}
return flag
},
isCheckboxChecked: (val, arr) => {
let i = 0
for (; i < arr.length; i++) {
if (arr[i].id === val || arr[i] === val) {
return true
}
}
return false
},
initckeditor () {
if (!this.exam.shortAnswerList) { return }
/* 删除所有 ckeditor 实例 */
const instances = CKEDITOR.instances
for (let name in instances) { instances[name].destroy() }
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
if (!instances['editor-exam' + i]) {
CKEDITOR.replace('editor-exam' + i, {
height: 300,
uiColor: '#eeeeee',
filebrowserImageUploadUrl: '/api/ckeditor/img/upload',
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar: [
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{ name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
{ name: 'colors', items: [ 'TextColor', 'BGColor' ] },
{ name: 'tools', items: [ 'Maximize', 'ShowBlocks' ] },
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{ name: 'editing', items: [ 'Find', 'Replace' ] },
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/',
{ name: 'basicstyles', items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
{ name: 'paragraph', items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl' ] },
{ name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
{ name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule' ] }
]
})
}
this.exam.shortAnswerList[i].ckeditor = instances['editor-exam' + i]
}
},
/**
* 生命周期函数--监听页面加载
*/
loadAjax () {
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
if (this.id !== '0') {
cAction.examAction.getExamAnswer(this.cid, this.sid, this.id).then(_data => {
if (_data.code === 8001) {
cAction.examAction.getExamInfo(this.cid, this.sid).then(_data => {
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
return
}
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
} else {
cAction.examAction.getExamInfo(this.cid, this.sid).then(_data => {
this.exam = _data
this.exam.id = this.id
}).catch(e => { this.$message.error(e.message) }).finally(() => {
/* 滚动到头部 */
document.querySelector('.play-paper').scrollTop = 0
loading.close()
this.initckeditor()
})
}
},
/**
* 提交试题
*/
submitExam (e) {
if (!e.submitType && e.currentTarget.dataset.submit) {
this.$message.error('已做过,不能再提交')
return
}
let body = { answers: {}, type: !e.submitType ? 1 : 0 } // type: 0 缓存;type: 1 提交
body.answers.radioList = []
for (let i = 0; i < this.exam.radioList.length; i++) {
let tmp = this.exam.radioList[i]
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有单选题未做,不能提交')
return
}
body.answers.radioList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.checkboxList = []
for (let i = 0; i < this.exam.checkboxList.length; i++) {
let tmp = this.exam.checkboxList[i]
if (!tmp.user_answer.length && !e.submitType) {
this.$message.error('还有多选题未做,不能提交')
return
}
body.answers.checkboxList.push({
id: tmp.id,
user_answer: tmp.user_answer
})
}
body.answers.shortAnswerList = []
for (let i = 0; i < this.exam.shortAnswerList.length; i++) {
let tmp = this.exam.shortAnswerList[i]
tmp.user_answer = tmp.ckeditor.getData()
if (!tmp.user_answer && !e.submitType) {
this.$message.error('还有简答题未做,不能提交')
return
}
body.answers.shortAnswerList.push({
id: tmp.id,
user_answer: Base64.encode(tmp.user_answer, 'utf-8'),
attachments: tmp.attachments
})
}
body.answers = JSON.stringify(body.answers)
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.examAction.submitExam(this.cid, this.sid, this.exam.id, body).then(_res => {
if (e.submitType) {
this.$message.success('暂存成功')
return
}
if (_res.code === 200) {
this.loadAjax()
} else {
this.$message.error(_res.data.error)
}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
},
_SubmitMouseLeftDown () {
let _fn1 = this.repeatExam.bind(this, false)
document.addEventListener('keydown', _fn1, false)
let _fn3 = function () {
document.removeEventListener('keydown', _fn1)
document.removeEventListener('mouseup', _fn3)
}
document.addEventListener('mouseup', _fn3, false)
},
/**
* 重做
*/
repeatExam (e, flag) {
let _flag = flag
/* 字母 f */
if (e.keyCode === 70) {
_flag = true
}
if (!_flag) { return }
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getExamDetail(this.sid, this.cid, this.id).then(_data => {
this.exam = {}
}).catch(e => { this.$message.error(e.message) }).finally(() => {
loading.close()
})
}
},
watch: {
id: {
handler () {
this.loadAjax()
}
}
}
}
</script>
<style lang="scss" scoped>
.play {
.exam { padding: 0; }
.exam .topic { display: inline-block; margin-bottom: 0.1rem; }
.exam .topic .tit { margin: 0 auto; padding: 0 0.2rem; text-align: center; font-size: 0.24rem; color: #313131; background: #fff; box-sizing: border-box; -webkit-box-sizing: border-box; }
.exam .topic .cur { text-align: center; font-size: 0.18rem; color: #313131; line-height: 0.4rem; }
/* 循环 所有选择题 */
.exam .q-group { padding: 0.1rem 0.1rem; border-bottom: 1px solid #c9c9c97a; overflow: hidden; }
.exam .q-group .q-num { float: left; margin-right: 0.1rem; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .q-title { float: left; width: 90%; font-size: 0.16rem; color: #676a6c; text-align: justify; }
.exam .q-group .q-type { float: right; font-size: 0.16rem; color: #676a6c; }
.exam .q-group .radio-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .radio-group .radio { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .checkbox-group { float: left; margin-top: 0.1rem; width: 100%; }
.exam .q-group .checkbox-group .checkbox { display: block; font-size: 0.18rem; color: #3f3b3a; line-height: 0.3rem; margin-bottom: 0.1rem; }
.exam .q-group .radio-group .radio.error, .exam .q-group .checkbox-group .checkbox.error { color: #d80000; }
.exam .q-group .radio-group .radio.success, .exam .q-group .checkbox-group .checkbox.success { color: #090; }
.exam .q-group .result { float: right; font-size: 0.18rem; color: #3f3b3a; margin-right: 0; }
.exam .q-group .result .stu { display: inline-block; }
.exam .q-group .result .stu.error { color: #d80000; }
.exam .q-group .result .stu.success { color: #090; }
.exam .q-group:last-child { border-bottom: none; }
.exam .btn { margin: 0.2rem auto; width: 60%; height: 0.5rem; line-height: 0.5rem; font-size: 0.16rem; text-align: center; font-weight: 300; color: #fff; border-radius: 0.1rem; background: #b49441; cursor: pointer; }
.exam .btn.on { opacity: 0.5; }
.exam .care { font-size: 0.16rem; color: #d80000; text-align: center; }
.exam .q-sa-title {
float: left;
width: 100%;
font-size: 0.16rem;
color: #676a6c;
text-align: justify;
}
}
</style>
.play{overflow:hidden; position: fixed;top:0; z-index: 800; width: 100%; height: 100%; background-color:#3f3f3f;color:#a0a0a0;}
.play .left-content{ position: absolute; right: 350px; top: 0; left: 0; bottom: 0; min-width: 705px;height:100%;}
.play.sidebar-hide .left-content{right:0;}
.play .play-top{ line-height: 56px;}
.play .play-top p{font-size:1.5em;text-align:center;margin:0;}
.play .play-back{position:absolute;top:6px;left:10px;width:40px;height:40px;overflow:hidden;color: #fff;font-size: 24px;line-height: 40px;text-align: center;}
.play .play-content{position:absolute;top:56px;bottom:0;left:0;right:0;}
.play .play-content-video{height:100%;}
.play .play-center .text-error{ font-size:0.875em;line-height:1.5em; }
.play .play-video-hide{visibility:hidden;overflow:hidden;width:0}
.play .play-video-init-center{position:absolute;top:50%;left:50%;margin:-180px 0 0 -275px;}
/* ppt控制 样式 */
.play .play-ppt { position: relative;width: 550px; height: 363.375px;background-color:#000;}
.play .play-ppt-img { width: 100%; height: 100%;}
.play .play-controls {position: absolute;bottom:0;left:0;right:0;height: 44px; line-height: 42px; padding: 0 14px;background-color: #000;}
.play .play-controls .fl i{ color: #8c8c8b}
.play .play-page{ position: absolute; left: 50%; margin-left: -75px; color: #fff; width: 150px; text-align: center; font-size: 0.875em;}
.play .play-page .play-now { color: #d29f29;}
.play .play-amazing { float: right; }
.play .play-amazing i{ color: #fff; margin: 0 10px; cursor: pointer; }
.play .play-amazing i.active,
.play .play-amazing i:hover{ color: #d29f29;}
.play .play-amazing .icon-rotate{ font-size: 1.125em;}
/* 整个视频 底部控制 上一章、下一章 同步、下载、跳过 */
.play .play-footer{ position: absolute; bottom: 0; left: 0;right:0; z-index: 200;padding:18px 20px 15px;}
.play .play-footer a:hover{color:#a0a0a0;}
/* 底部控制 按钮样式 */
.play .play-state{display:inline-block;color:#a0a0a0;padding-left: 25px;font-size:14px;line-height:18px;margin:0 20px;background:url(./play-icons.png) no-repeat 0 0;cursor:pointer}
.play .play-state a { color: #a0a0a0; text-decoration: none; }
.play .play-state-prev{background-position:0 2px;}
.play .play-state-prev-disable{background-position:0 -78px;color:#666;}
.play .play-state-next{background-position:0 -38px;}
.play .play-state-next-disable{background-position:0 -118px;color:#666;}
.play .play-state-check{background-position:0 -160px;}
.play .play-state-check-active{background-position:0 -200px;color: #b19241; }
.play .play-state-ppt{background-position:0 -240px;}
.play .play-state-ppt-active{background-position:0 -280px;color: #b19241; }
.play .play-state-prev-disable:hover,
.play .play-state-next-disable:hover {color:#666!important;}
/* 收起右侧,出现的 按钮样式 */
.play .switch { display: block; width: 36px; height: 125px; position: absolute; right: 0px; top: 50%; text-align: center; margin: -63px 0 0; z-index: 998; cursor: pointer;}
.play .switch a { color: #a0a0a0; text-decoration: none; font-size: 14px; line-height: 2; margin-bottom: 20px; }
.play .switch a i { font-size: 24px; padding: 5px; background: #666; border-radius: 4px; }
/*作业类页*/
.play .play-paper{position:absolute;top:0;bottom:0;left:0;right:0;overflow:auto;background-color:#e5e5e5;}
.play .play-paper-body{min-height:500px;margin:25px;padding:15px 45px 25px;color:#313131;box-shadow:0 0 2px rgba(0,0,0,.05);background-color:#f2f2f2;}
.play .play-paper-title{margin:0 10px;text-align:center;}
.play .play-paper-title div{padding-bottom:3px;display:inline-block;border-bottom:1px solid #707070;}
.play .play-paper-title h3{padding:0 0 5px;margin:0;display:inline-block;font-size:20px;border-bottom:3px solid #707070;}
.play .play-paper-body .help{color:#999;font-size:12px;}
.play .play-paper-body .help-file{margin-top:-10px;}
.play .play-paper-body .area-btns{margin:20px 0;}
.play .play-paper-body .area-btns .btn{padding:6px 25px;}
.play .play-paper-body .area-btns .help{margin-top:10px; font-size: 14px}
.play .play-paper-body .area-btns .help-info{display:inline-block;vertical-align:middle;}
.play .play-paper-body .webuploader-btn .upbtn{color:#b49441;text-decoration:underline;padding:3px 15px;font-size:.9em;display:inline-block;}
.play .play-paper-body .webuploader-btn .loading .fa-spin{font-size:1.2em!important;}
.play .play-paper-body label{font-weight:normal;}
.play .play-paper-body input{vertical-align:middle;}
.play .play-paper-check{margin-top:20px;padding:20px;border:1px solid #dedede;}
.play .play-paper-check h4{font-size:16px;margin:0 0 10px;}
.play .play-paper-check-item{padding-left:3em;}
.play .play-paper-check-item b{margin-left:-3em;margin-top:1px;display:inline-block;vertical-align:top;}
.play .play-paper-check-item .edit_html{display:inline-block;vertical-align:top;}
/*阅读材料*/
.play .play-read-files{padding:30px;margin: 0;}
.play .play-read-files li{font-size:16px;padding:20px 30px;margin-bottom:10px;background-color:#fff;list-style: none;}
.play .play-read-files li a { color: #333; text-decoration: none; }
.play .play-read-files li a:hover { color: #b49441; }
/*章节作业*/
.play .play-chapter-work{padding:25px;}
.play .play-chapter-work .work-number{margin-left:-25px;}
.play .play-chapter-work .work-title{margin-top:-20px;margin-bottom:10px;}
.play .play-chapter-work .area-btns{margin:20px -25px 0;border-top:1px solid #eaeaea;padding-top:20px;}
.play .play-chapter-exam{padding:25px;}
.play .play-chapter-exam li{position:relative;margin-top:15px;border-bottom:1px solid #b49441;}
.play .play-chapter-exam .exam-title{margin: -20px 0 10px 25px;}
.play .play-chapter-exam .wrong{color:#d80000;}
.play .play-chapter-exam .correct{color:#090;}
.play .play-chapter-exam .answer{position:absolute;right:25px;bottom:10px;}
.play .play-chapter-exam .result{float:right;margin-top:-20px;margin-right:-20px;font-size:16px;font-weight:bold;}
/* 统一默认 common样式 */
.play dd,
.play dl,
.play dt,
.play li,
.play ol,
.play ul { margin: 0; padding: 0; list-style: none; }
.play .edit_html p {padding: 0; margin: 0; margin-bottom: 15px;}
.play .play-paper-step{font-weight:bold;font-size:16px;margin:30px -20px 15px;padding-bottom:10px;border-bottom:1px dashed #cecece;}
.play .no-data {
color: #c9c9c9;
font-size: 36px;
padding: 160px 0;
text-align: center;
}
/* 视频播放样式 */
.play .hide { display: none; }
.play .play-video { float: left }
.play .play-jiangyi { float: left }
/* 视频播放 是 flash */
@media (max-width:768px){
.play .left-content {min-width:0;right:0;}
.play .play-back{margin:0;}
.play .play-chapter-work{padding:25px 0;margin-right:-20px;}
.play .play-read-files{padding:30px 0;margin:0 -20px;}
.play .switch{top:80%;}
.play .play-paper-body{ padding: 15px 25px 25px; }
.play .play-paper-step{ margin: 30px 0 15px; }
}
/* OK 样式 */
.exam .q-group .q-title p { padding: 0; margin: 0; }
.exam .el-radio + .el-radio { margin-left: 0; }
.exam .el-radio__input.is-disabled + span.el-radio__label { color: inherit; }
.exam .el-checkbox + .el-checkbox { margin-left: 0; }
.exam .el-checkbox__input.is-disabled + span.el-checkbox__label { color: inherit; }
.exam .el-radio__label, .exam .el-checkbox__label { white-space: normal; }
/* aliyun 播放器 样式修改 */
#player .prism-setting-btn { display: none; }
#player .prism-cc-btn { display: none; }
#player.prism-player .prism-progress { z-index: 99; }
<template>
<div :class="['play', (state.sideBar ? '' : 'sidebar-hide')]">
<div class="left-content">
<div class="play-top cl" :style="(state.sideBar ? {} : { marginRight: 0 })">
<router-link class="router-link-class" :to="{ path: `/app/my-learn/course-detail/${sid}/${cid}` }"><i class="play-back el-icon-arrow-left"></i></router-link>
<p>{{chapterList.title}}</p>
<router-link class="router-link-class" id="sys-help" :to="{ path: `/mobile/help/student` }" target="_blank">
<el-tooltip effect="light" content="帮助" placement="bottom-start">
<i class="el-icon-self-icon-test"></i>
</el-tooltip>
</router-link>
<router-link class="router-link-class" id="sys-callback" :to="{ path: `/app/account/feedbackCreate` }" target="_blank">
<el-tooltip effect="light" content="意见反馈" placement="bottom-start">
<i class="el-icon-self-fankuiyijian"></i>
</el-tooltip>
</router-link>
</div>
<div class="play-content">
<router-view
ref="comTotalChapter"
:chapterName="curChapterName"
:chapterId="chapterId"
:courseInfo="courseInfo"
:courseWork="courseWork"
:chapterRead="chapterRead"
:chapterWork="chapterWork"
:chapterExam="chapterExam"
:chapterVideo="chapterVideo"
:ppts="chapterPpts"
:videoType="videoType"
@changeVideoArr="changeVideoArr"
@handlePlayTime="handlePlayTime"
@updateProgress="updateProgress"
@changeSideBar="changeSideBar"
></router-view>
</div>
</div>
<div class="right-ctrl" :style="{ right: (state.sideBar ? 0 : -388) + 'px' }">
<p class="ctrl-arrow" @click="changeSideBar('')"><span>&gt;</span></p>
<div class="ctrl-pl">
<ul class="pl-tab-hd">
<li :class="[(state.sideBar === SIDEBAR_CHAPTER ? 'on' : '')]"><a :href="('#' + SIDEBAR_CHAPTER)" @click="changeSideBar(SIDEBAR_CHAPTER)">章节</a></li>
<template v-if="state.isChapterVideo">
<li :class="['br-l-line', (state.sideBar === SIDEBAR_PPT ? 'on' : '')]"><a :href="('#' + SIDEBAR_PPT)" @click="changeSideBar(SIDEBAR_PPT)">讲义</a></li>
</template>
</ul>
<div class="pl-tab-bd">
<template v-if="state.sideBar === SIDEBAR_CHAPTER">
<side-chapter-list
:list="chapterList"
:sid="sid"
:cid="cid"
/>
</template>
<template v-if="state.sideBar === SIDEBAR_PPT">
<side-chapter-ppt
ref="sidePpt"
:ppt="pptList"
@handleClickPpt="handleClickSidePpt"
/>
</template>
</div>
</div>
</div>
<template v-if="!state.sideBar">
<div class="switch" id="switch-btn">
<a :href="('#' + SIDEBAR_CHAPTER)" class="switch-chapter" @click="changeSideBar(SIDEBAR_CHAPTER)">
<i class="el-icon-self-wenjian"></i>
<div>章节</div>
</a>
<template v-if="state.isChapterVideo">
<a :href="('#' + SIDEBAR_PPT)" class="switch-handout" @click="changeSideBar(SIDEBAR_PPT)">
<i class="el-icon-self-PPT"></i>
<div>讲义</div>
</a>
</template>
</div>
</template>
</div>
</template>
<script>
import cAction from '@action'
import cTool from '@tools'
import sideChapterList from './rightSide/sideChapterList.vue'
import sideChapterPpt from './rightSide/sideChapterPpt.vue'
export default {
components: { sideChapterList, sideChapterPpt },
props: {
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }, // 章节id
videoType: { type: String, require: false } // 视频播放类型 // 1 是CC加密,2 是非加密,3 是阿里云
},
data () {
return {
SIDEBAR_CHAPTER: 'sidebar_chapter',
SIDEBAR_PPT: 'sidebar_ppt',
state: {
sideBar: 'sidebar_chapter',
isChapterVideo: false
},
chapterList: {
// title: '',
// currentChapterId: '11', // 当前章节id
// course: [{
// title: '第一章:重要概念',
// chapters: [
// { id: '11', time: '28:18', name: '1.1 现值(PV)和终值(FV)' },
// { id: '12', time: '19:09', name: '1.2 净现值(NPV)及实际收益率' }
// ]
// }, {
// title: '第二章:证券估值',
// chapters: [
// { id: '21', time: '27:49', name: '2.1 债券和股票的现金流贴现估值方法' },
// { id: '22', time: '16:04', name: '2.2 债券评级及债券协议' },
// { id: '23', time: '', name: '公司金融第二周测验' },
// { id: '24', time: '', name: '公司金融第二周作业' }
// ]
// }],
// nextVideo: {}, // 下一章 音视频对象
// prevVideo: {} // 上一章音视频对象
},
pptList: {
// imgUrls: [
// 'http://pd4t7ae3m.bkt.clouddn.com/imgs-test1.jpg',
// 'http://pd4t7ae3m.bkt.clouddn.com/imgs-test2.jpg',
// 'http://pd4t7ae3m.bkt.clouddn.com/imgs-test3.jpg'
// ], // 所有图片数组
// current: 0, // 当前跟着音视频走,播放的是第几个图片
// selectIndex: 0,
// timeArr: [2, 10] // 时间数组,在这个时间时,图片需要切换到对应下角标图片
},
/* 课程资料 */
courseInfo: [],
/* 课程大作业 */
courseWork: {},
/* 当前选择 - 章节名字 */
curChapterName: '',
/* 当前选择 - 章节id */
chapterId: '',
/* 章节阅读 */
chapterRead: {},
/* 章节问题、作业提交 */
chapterWork: {},
/* 章节测试 */
chapterExam: {},
/* 章节视频 */
chapterVideo: {},
chapterPpts: []
}
},
beforeRouteUpdate (to, from, next) {
/* 只有 视频时 才有PPT */
if (to.name === 'chapterVideo') {
this.state.isChapterVideo = true
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
const str = to.params.videoType !== '3' ? 'getCurrentChapterDetail' : 'getCurrentChapterDetailAliyun'
cAction.Player[str](to.params.id).then(json => {
this.pptList = json.image
this.chapterPpts = json.rData.ppts
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
} else {
this.state.isChapterVideo = false
}
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
if (to.params.cid === '0' && to.params.sid === '0') {
this.$message({
message: 'URL异常,重新选择课程学习!!!',
type: 'info',
duration: 10000
})
loading.close()
return
}
cAction.Player.getChapterList(to.params.cid, to.params.sid, to.params.id).then(json => {
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
/* 章节选择 */
this.curChapterName = json.curJson.name
this.chapterId = json.curJson.chapterId
/* 如果 是 视频 再调用 进度信息接口 */
if (json.curJson.type === 2) {
cAction.Player.getProgress(this.id, cTool.other.getIdt(), this.sid).then(_d => {
this.chapterVideo = ((json.curJson.type === 2) && json.curJson.chapterVideo) || {}
this.chapterVideo.progress = _d
}).catch(e => { this.$message.error(e.message) })
}
this.chapterRead = ((json.curJson.type === 4) && json.curJson.chapterRead) || {}
this.chapterExam = ((json.curJson.type === 3 && json.curJson.work_type === 1) && json.curJson.homework) || {}
this.chapterWork = ((json.curJson.type === 3 && json.curJson.work_type === 2) && json.curJson.chapterWork) || {}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
next()
},
mounted () {
/* 只有 视频时 才有PPT */
if (this.$route.name === 'chapterVideo') {
this.state.isChapterVideo = true
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
const str = this.videoType !== '3' ? 'getCurrentChapterDetail' : 'getCurrentChapterDetailAliyun'
cAction.Player[str](this.id).then(json => {
this.pptList = json.image
this.chapterPpts = json.rData.ppts
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
}
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
if (this.cid === '0' && this.sid === '0') {
this.$message({
message: 'URL异常,请点击“返回首页”,重新选择课程学习!',
type: 'info',
duration: 10000
})
loading.close()
return
}
cAction.Player.getChapterList(this.cid, this.sid, this.id).then(json => {
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
/* 章节选择 */
this.curChapterName = json.curJson.name
this.chapterId = json.curJson.chapterId
/* 如果 是 视频 再调用 进度信息接口 */
if (json.curJson.type === 2) {
cAction.Player.getProgress(this.id, cTool.other.getIdt(), this.sid).then(_d => {
this.chapterVideo = ((json.curJson.type === 2) && json.curJson.chapterVideo) || {}
this.chapterVideo.progress = _d
}).catch(e => { this.$message.error(e.message) })
}
this.chapterRead = ((json.curJson.type === 4) && json.curJson.chapterRead) || {}
this.chapterExam = ((json.curJson.type === 3 && json.curJson.work_type === 1) && json.curJson.homework) || {}
this.chapterWork = ((json.curJson.type === 3 && json.curJson.work_type === 2) && json.curJson.chapterWork) || {}
}).catch(e => { this.$message.error(e.message) }).finally(() => { loading.close() })
},
methods: {
changeSideBar (str) { this.state.sideBar = str },
// 视频播放时间变更调用此方法
handlePlayTime (time) {
// 侧边ppt对应位置
if (this.$refs.sidePpt) {
this.$refs.sidePpt.setIndexByPoint(time)
}
},
/* 通过 右侧 sider ppt 控制 左侧视频播放位置 */
handleClickSidePpt (index) {
const ppts = this.chapterPpts || []
if (ppts.length > index && this.$refs.comTotalChapter) {
const ppt = ppts[index]
this.$refs.comTotalChapter.setVideoTime(ppt.ppt_point)
}
},
updateProgress (_rProgress) {
if (_rProgress.cpt) {
/* 处理一下 视频帧栈,另存一个数组,防止请求事件过长,或超时,导致数据上送失败 */
let _arr = []
let _intCountPrev = 0
for (let i = 0; i < _rProgress.ps.length; i++) {
_arr[i] = _rProgress.ps[i]
/* 客户端,处理一下,统计整个 视频帧数组 都有哪些帧 播过 */
const _intCount = Math.floor(_rProgress.ps[i]) || 1
if (_intCount !== _intCountPrev) {
_rProgress.map[_intCount - 1] += 1
_intCountPrev = _intCount
}
}
/* 帧点取整 */
const _arrTmp = _arr
_intCountPrev = 0
_arr = []
for (let i = 0; i < _arrTmp.length; i++) {
if (Math.floor(_arrTmp[i]) !== _intCountPrev) {
_arr.push(Math.floor(_arrTmp[i]))
_intCountPrev = Math.floor(_arrTmp[i])
}
}
const _userInfo = window.G.UserInfo
cAction.Player.updateProgress({
sid: (_userInfo && _userInfo.student_info && _userInfo.student_info.id) || '',
uid: (_userInfo && _userInfo.uid) || '',
d: cTool.other.getIdt(),
i: cTool.other.getIdt(),
c: this.cid,
s: this.sid,
v: _rProgress.vid, // this.id, // 这个不能保证退出组件时,刷新视频 从 crouse_work 切换 到 chapter_video
_p: parseInt(_rProgress.pt), // 累计时间
_m: parseInt(_rProgress.mpt), // 当前播放最大时间
_c: parseInt(_rProgress.cpt), // 当前播放位置
ps: _arr.join(',') // 播放时,播放过的 帧
// map: _rProgress.map// 播放时,统计帧
}).then(json => {
if (json.success) {
/* 将已经上传 并 成功的 帧 全部从 栈中删除 */
for (let i = 0; i < _arr.length; i++) {
_rProgress.ps.shift()
}
} else {
/* 兼容 阿里云视频、CC视频 停止播放 */
const $dom = document.getElementById('player')
const flag = !!$dom.callAction
flag ? ($dom.callAction('pause')) : ($dom.children[0].pause())
}
}).catch(e => {
this.$message.error(e.message)
/* 兼容 阿里云视频、CC视频 停止播放 */
const $dom = document.getElementById('player')
const flag = !!$dom.callAction
flag ? ($dom.callAction('pause')) : ($dom.children[0].pause())
}).finally(() => { })
}
},
changeVideoArr (obj) {
// beforeRouteUpdate 已经实现
console.log('已经实现')
obj.callback && obj.callback()
}
}
}
</script>
<style lang="scss" scoped>
@import './index.css';
/* 右侧面板结构 */
.right-ctrl {
position: absolute;
top: 0;
bottom: 0;
right: 0;
z-index: 200;
width: 350px;
background: #212121;
border-left: 19px solid #1b1b1b;
/* 箭头 */
.ctrl-arrow {
position: absolute;
top: 0;
left: -19px;
bottom: 0;
width: 19px;
height: 100%;
font-size: 14px;
color: #969696;
cursor: pointer;
span {
position: absolute;
top: 50%;
left: 0;
width: 19px;
margin-top: -10px;
text-align: center;
color: #fff;
}
}
/* 面板 */
.ctrl-pl {
position: relative;
height: 100%;
/* 头部 - 选择项 */
.pl-tab-hd {
margin: 0;
padding: 15px 0;
line-height: 1.6;
background-color: #232323;
overflow: hidden;
li {
float: left;
position: relative;
width: 50%;
padding: 0;
font-size: 16px;
color: #909090;
text-align: center;
list-style: none;
cursor: pointer;
a {
color: #909090;
text-decoration: none;
}
&.on {
a {
color: #b49441;
}
}
}
.br-l-line {
border-left: 1px solid #3f3f3f;
}
}
/* 内部 - 列表项 */
.pl-tab-bd {
height: 94%;
}
}
}
#sys-help {
display: none;
position: absolute;
top: 6px;
right: 50px;
/* width: 40px; */
height: 40px;
overflow: hidden;
color: #fff;
font-size: 16px;
line-height: 40px;
text-align: center;
text-decoration: none;
i {
font-size: 20px;
}
}
#sys-callback {
display: none;
position: absolute;
top: 6px;
right: 15px;
/* width: 40px; */
height: 40px;
overflow: hidden;
color: #fff;
font-size: 16px;
line-height: 40px;
text-align: center;
text-decoration: none;
i {
font-size: 20px;
}
}
</style>
<template>
<iframe id="myIframe" :src="live.url" frameborder="0" width="100%" height="100%" allow="autoplay;geolocation;microphone;camera;midi;encrypted-media;"></iframe>
</template>
<script>
import cAction from '@action'
export default {
props: {
sid: { type: String, require: false },
cid: { type: String, require: false },
id: { type: String, require: false }
},
data () {
return {
live: {},
ccLive: null
}
},
mounted () {
this.loadAjax()
},
destroyed () {
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'block'
window.document.getElementById('sys-help').style.display = 'none'
window.document.getElementById('sys-callback').style.display = 'none'
}
if (this.ccLive) {
this.ccLive.logout({
success: function (res) {
},
error: function (res) {
}
})
}
},
methods: {
loadAjax () {
/* 只能清除本域名下的cookie,不能清除其他域名下的cookie */
// cAction.loginAction.clearCookie({
// cookies: [{
// name: 'sessionid',
// obj: { path: '/', domain: '.csslcloud.net' }
// }, {
// name: 'TGC',
// obj: { path: '/', domain: '.ezijing.com' }
// }]
// }).then().finally(() => {})
const loading = this.$loading({ lock: true, text: '', spinner: '', background: 'rgba(255, 255, 255, 0.9)' })
cAction.Player.getChapterList(this.cid, this.sid, this.id).then(json => {
this.live = (json.curJson && json.curJson.live) || {}
if (this.live.id) {
if (this.live.record_id && this.live.live_status === 103) {
this.live.url = 'https://view.csslcloud.net/api/view/callback?recordid=' + this.live.record_id + '&roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + this.live.viewer_name + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
} else {
this.$emit('changeSideBar', '')
setTimeout(() => {
if (window.document.getElementById('switch-btn')) {
window.document.getElementById('switch-btn').style.display = 'none'
window.document.getElementById('sys-help').style.display = 'block'
window.document.getElementById('sys-callback').style.display = 'block'
}
}, 1000)
this.live.viewer_name = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
this.live.url = 'https://view.csslcloud.net/api/view/index?roomid=' + this.live.room_id + '&userid=' + this.live.user_id + '&autoLogin=true&viewername=' + this.live.viewer_name + '&viewertoken=' + this.live.viewer_token // + '&groupid=xxx'
}
this.CCLiveInit(this.live)
}
loading.close()
}).catch(e => { this.$message.error(e.message) }).finally(() => { })
},
CCLiveInit (live) {
window.DWLive.init({
userid: live.user_id, // 必须参数
roomid: live.room_id, // 必须参数
// groupid: "groupid", // 可选
viewername: live.viewer_name, // 可选
viewertoken: live.viewer_token // 如果直播间设置为密码验证,必选
// viewercustomua: 'android', // 可选
// language: 'en', // 可选
// viewercustominfo: '{"exportInfos": [ {"key": "城市", "value": "北京"}, {"key": "姓名", "value": "哈哈"}]}', // 可选
// fastMode:true // 可选参数,默认为true
})
this.ccLive = window.DWLive
}
},
watch: {
id: {
handler () {
this.loadAjax()
}
}
}
}
</script>
<style lang="scss" scoped>
</style>
<template>
<div class="tab-pane">
<ul class="chapter-list">
<template v-for="(item, index) in list.course">
<li v-bind:key="index" class="chapter-item">
<h4>{{item.title}}</h4>
<ul class="knot-list">
<template v-for="(_item, _index) in item.chapters">
<li v-bind:key="_index" :class="['knob-item', (_item.id === list.currentChapterId ? 'on' : '')]">
<a :data-vid="_item.id" :data-type="_item.video_provider" :data-hasVA='_item.time' @click='jumpToOtherVA' :data-index='index' :data-count='_index' class="knot-name">
{{_item.name + (_item.type === 5 ? ("(" + _item.live.statusStr + ")") : "") }}
</a>
<template v-if="_item.type !== 5">
<i :class="['el-icon', (_item.time ? 'el-icon-self-iconset0481' : (_item.type === 3 ? 'el-icon-edit-outline' : 'el-icon-self-cc-book'))]"></i>
</template>
</li>
</template>
</ul>
</li>
</template>
</ul>
</div>
</template>
<script>
export default {
props: {
list: { type: Object, require: false },
sid: { type: String, require: false },
cid: { type: String, require: false }
},
methods: {
/**
* 跳转到对应音视频播放页
*/
jumpToOtherVA (e) {
const _data = e.target.dataset
const sid = this.sid
const cid = this.cid
const _id = _data.vid
const type = _data.type
if (!_data.hasva) {
/* 如果存在 - 课后习题类型(chapterExam), type:3、work_type:1 */
/* 如果存在 - 课后问题类型(chapterWork), type:3、work_type:2 */
/* 如果存在 - 课后阅读类型(chapterRead), type:4 */
const i1 = _data.index
const i2 = _data.count
const _course = this.list.course[i1]
if (_course && _course.chapters[i2]) {
if (_course.chapters[i2].id === 'course_info') {
this.$router.push({ path: `/player/${sid}/${cid}/course-info/course_info` })
} else if (_course.chapters[i2].id === 'course_work') {
if (!this.list.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return
}
this.$router.push({ path: `/player/${sid}/${cid}/course-work/course_work` })
} else if (_course.chapters[i2].type === 3) {
if (_course.chapters[i2].work_type === 1) {
this.$router.push({ path: `/player/${sid}/${cid}/chapter-exam/${_id}` })
} else if (_course.chapters[i2].work_type === 2) {
this.$router.push({ path: `/player/${sid}/${cid}/chapter-work/${_id}` })
}
} else if (_course.chapters[i2].type === 4) {
this.$router.push({ path: `/player/${sid}/${cid}/chapter-read/${_id}` })
} else if (_course.chapters[i2].id === 'teach_evaluation') {
// window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
this.$router.push({ path: `/survey/${sid}/${cid}` })
} else if (_course.chapters[i2].type === 'exam') {
this.$router.push({ path: `/player/${sid}/${cid}/exam/${_id}` })
} else if (_course.chapters[i2].type === 5) {
const status = _course.chapters[i2].live.live_status
if (status !== 0 && status !== 1 && status !== 103) {
this.$message.error(_course.chapters[i2].live.statusStr)
return
}
const enableRecord = _course.chapters[i2].live.enable_record
if (status === 103 && enableRecord !== undefined && enableRecord !== null && !enableRecord) {
this.$message.info('该直播没有回放')
return
}
this.$router.push({ path: `/player/${sid}/${cid}/live/${_id}` })
}
return
}
this.$message.error('系统未知错误,002')
return
}
this.$router.push({ path: `/player/${sid}/${cid}/chapter-video/${_id}/${type}` })
}
}
}
</script>
<style lang="scss" scoped>
.tab-pane {
display: block;
height: 100%;
overflow: auto;
/* 章列表样式 */
.chapter-list {
margin: 0;
padding: 0;
line-height: 1.6;
overflow: hidden;
.chapter-item {
h4 {
padding: 10px 22px;
margin: 0;
font-size: 15px;
color: #b0b0b0;
background-color: #2f2f2f;
}
/* 节列表样式 */
.knot-list {
margin: 0;
padding: 0;
line-height: 1.6;
overflow: hidden;
li {
position: relative;
&.on {
background: #3c3c3c;
a {
color: #b49441;
}
}
&:hover {
background: #3c3c3c;
}
&:before {
display: block;
content: "";
position: absolute;
left: 13px;
top: 16px;
z-index: 10;
width: 18px;
height: 18px;
background: #5b5b5b;
border: 2px solid #5b5b5b;
border-radius: 50%;
}
&:after {
display: block;
content: "";
position: absolute;
left: 22px;
top: 0;
z-index: 5;
width: 1px;
height: 100px;
background: #616161;
}
}
.knot-name {
display: block;
padding: 15px 35px 15px 40px;
font-size: 14px;
color: #909090;
text-decoration: none;
cursor: pointer;
}
}
/* 章节后面小图标的样式 */
.el-icon {
position: absolute;
font-size: 16px;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
}
}
}
</style>
<template>
<div class="tab-pane">
<ul class="lecture-list">
<template v-for="(item, index) in ppt.imgUrls">
<li v-bind:key="index" @click="onClickPpt" :data-index="index" :class="[(index === ppt.selectIndex ? 'on' : '')]">
<img :src="item" alt=""/>
</li>
</template>
</ul>
</div>
</template>
<script>
export default {
props: {
ppt: { type: Object, require: false }
},
methods: {
// 根据播放时间同步展示ppt
// @param time 播放时间
setIndexByPoint (time) {
const ppts = this.ppt.imgUrls || []
const len = ppts.length
let i = 0
for (; i < len; i++) {
if (time < this.ppt.timeArr[i]) {
break
}
}
if (this.ppt.selectIndex !== i - 1) {
this.ppt.selectIndex = i - 1
}
},
// 点击ppt跳转对应的播放时间
// 点击某个ppt
onClickPpt (e) {
const toIndex = e.currentTarget.dataset.index - 0
if (this.ppt.selectIndex === toIndex) { return }
this.ppt.selectIndex = toIndex
this.$emit('handleClickPpt', toIndex)
}
}
}
</script>
<style lang="scss" scoped>
.tab-pane {
display: block;
height: 100%;
overflow: auto;
/* 讲义列表样式 */
.lecture-list {
padding: 8px 16px;
li {
padding: 8px 16px;
cursor: pointer;
list-style: none;
&.on {
background: #888;
}
img {
width: 100%;
}
}
}
}
</style>
......@@ -89,6 +89,64 @@ export default [
}
]
},
/* 学习系统 - player */
// {
// path: '/player/:sid/:cid',
// redirect: '/player/0/0/error/404',
// component: () => import('@/pages/player/index.vue'),
// props: true,
// children: [
// { path: 'error/404', component: () => import('@/components/errorPages/404.vue') },
// {
// path: 'chapter-video/:id/:videoType',
// name: 'video',
// component: () => import('@/pages/player/chapterVideo/chapterVideo.vue'),
// props: true
// },
// {
// path: 'chapter-exam/:id',
// name: 'chapterExam',
// component: () => import('@/pages/player/chapterExam/chapterExam.vue'),
// props: true
// },
// {
// path: 'chapter-read/:id',
// name: 'chapterRead',
// component: () => import('@/pages/player/chapterRead/chapterRead.vue'),
// props: true
// },
// {
// path: 'chapter-work/:id',
// name: 'chapterWork',
// component: () => import('@/pages/player/chapterWork/chapterWork.vue'),
// props: true
// },
// {
// path: 'course-info/:id',
// name: 'courseInfo',
// component: () => import('@/pages/player/courseInfo/courseInfo.vue'),
// props: true
// },
// {
// path: 'course-work/:id',
// name: 'courseWork',
// component: () => import('@/pages/player/courseWork/courseWork.vue'),
// props: true
// },
// {
// path: 'exam/:id',
// name: 'exam',
// component: () => import('@/pages/player/exam/exam.vue'),
// props: true
// },
// {
// path: 'live/:id',
// name: 'live',
// component: () => import('@/pages/player/live/live.vue'),
// props: true
// }
// ]
// },
/* 如果所有页面都没找到 - 指向 */
{ path: '*', component: () => import('@/components/errorPages/404.vue') }
]
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论