提交 5632ae6b authored 作者: 王鹏飞's avatar 王鹏飞

merge...

......@@ -104,8 +104,22 @@ export default class CourseAction extends BaseACTION {
unit: item.lecturer_title || ''
})
}
const findChapter = function (id, list) {
for (const item of list) {
if (item.resource_id === id) {
return item
}
if (item.children && item.children.length) {
const found = findChapter(id, item.children)
if (found) {
return found
}
}
}
}
/* 课程内容 */
json.tabs1ChapterList = {
currentChapter: findChapter(cur.latest_play, data.chapters),
currentChapterId: cur.latest_play || '',
currentVideoProvider: cur.latest_play_type || '1',
course: cur.chapters.map((_, i) => {
......@@ -127,9 +141,6 @@ export default class CourseAction extends BaseACTION {
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天后开始”天就显示年月日
......@@ -145,8 +156,8 @@ export default class CourseAction extends BaseACTION {
str = parseInt(time / (24 * 60 * 60)) + '天后开始'
}
}
if (__.live.live_status === 103 && __.live.enable_record !== undefined && __.live.enable_record !== null && !__.live.enable_record) {
str = ''
if (__.live.live_status === 2 && __.live.enable_record && __.live.record_url) {
str = '观看回放'
}
__.live.statusStr = str
}
......@@ -173,35 +184,39 @@ export default class CourseAction extends BaseACTION {
title: '课程大作业',
isUp: true,
chapters: [],
type: 'course_work',
id: 'course_work',
sid: sid,
cid: cid
cid: cid,
type: 99
})
json.tabs1ChapterList.course.push({
title: '课程资料',
isUp: true,
chapters: [],
type: 'course_info',
id: 'course_info',
sid: sid,
cid: cid
cid: cid,
type: 100
})
json.tabs1ChapterList.course.push({
title: '教学评估',
isUp: true,
chapters: [],
type: 'teach_evaluation',
id: 'teach_evaluation',
sid: sid,
cid: cid
cid: cid,
type: 102
})
if (cur.course_examination) {
json.tabs1ChapterList.course.push({
title: '课程考试',
isUp: true,
chapters: [],
type: 'exam',
id: 'course_exam',
sid: sid,
cid: cid,
examId: cur.course_examination
examId: cur.course_examination,
type: 102
})
}
/* 课程考核 考核标准文案读取 */
......
......@@ -93,9 +93,6 @@ export default class PlayerAction extends BaseACTION {
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天后开始”天就显示年月日
......@@ -111,8 +108,8 @@ export default class PlayerAction extends BaseACTION {
str = parseInt(time / (24 * 60 * 60)) + '天后开始'
}
}
if (__.live.live_status === 103 && __.live.enable_record !== undefined && __.live.enable_record !== null && !__.live.enable_record) {
str = ''
if (__.live.live_status === 2 && __.live.enable_record && __.live.record_url) {
str = '观看回放'
}
__.live.statusStr = str
}
......@@ -122,6 +119,7 @@ export default class PlayerAction extends BaseACTION {
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,
......@@ -164,7 +162,8 @@ export default class PlayerAction extends BaseACTION {
json: json,
courseInfo: _res.files || [],
courseWork: _res.curriculum || {},
curJson: curJson
curJson: curJson,
rawResponse: _res
}
})
}
......@@ -291,51 +290,51 @@ export default class PlayerAction extends BaseACTION {
exam.id = _res.id
exam.title = _res.title
exam.score = {}
exam.radioList = _res.examination.radioList
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
exam.checkboxList = _res.examination.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
exam.shortAnswerList = _res.examination.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].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>
`
exam.examination = _res.examination.map(exam => {
for (let i = 0; i < exam.radioList.length; i++) {
exam.radioList[i].user_answer = ''
exam.radioList[i].right_answer = ''
exam.radioList[i].get_score = -1
}
}
for (let i = 0; i < exam.checkboxList.length; i++) {
exam.checkboxList[i].user_answer = []
exam.checkboxList[i].right_answer = []
exam.checkboxList[i].get_score = -1
}
for (let i = 0; i < exam.shortAnswerList.length; i++) {
exam.shortAnswerList[i].user_answer = ''
exam.shortAnswerList[i].get_score = -1
exam.shortAnswerList[i].attachments = []
exam.shortAnswerList[i].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>
`
}
}
return exam
})
return exam
})
}
/* 获取考卷结果 */
getExamAnswer (cid, sid, eid) {
return Player.getExamAnswer(cid, sid, eid).then(_res => {
getExamAnswer (cid, sid, eid, obj) {
return Player.getExamAnswer(cid, sid, eid, obj).then(_res => {
if (_res.code) { return _res }
const exam = {}
let tmp = null
......@@ -345,46 +344,50 @@ export default class PlayerAction extends BaseACTION {
exam.score = _res.score
exam.isPublished = _res.is_published || ''
exam.submitted_time = _res.submitted_time
exam.radioList = _res.sheet.radioList
for (let i = 0; i < exam.radioList.length; i++) {
tmp = exam.radioList[i]
if (!tmp.user_answer) tmp.user_answer = ''
if (!tmp.right_answer) tmp.right_answer = ''
if (!tmp.get_score) tmp.get_score = -1
}
exam.checkboxList = _res.sheet.checkboxList
for (let i = 0; i < exam.checkboxList.length; i++) {
tmp = exam.checkboxList[i]
if (!tmp.user_answer || !tmp.user_answer.length) tmp.user_answer = []
if (!tmp.right_answer || !tmp.right_answer.length) tmp.right_answer = []
if (!tmp.get_score) tmp.get_score = -1
}
exam.shortAnswerList = _res.sheet.shortAnswerList
for (let i = 0; i < exam.shortAnswerList.length; i++) {
tmp = exam.shortAnswerList[i]
tmp.user_answer = Base64.decode(tmp.user_answer.replace(/ /gi, '+'))
if (!tmp.attachments || !tmp.attachments.length) tmp.attachments = []
tmp.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>
`
exam.examination = _res.sheet.map(exam => {
exam.radioList = exam.radioList || []
for (let i = 0; i < exam.radioList.length; i++) {
tmp = exam.radioList[i]
if (!tmp.user_answer) tmp.user_answer = ''
if (!tmp.right_answer) tmp.right_answer = ''
if (!tmp.get_score) tmp.get_score = -1
}
}
exam.checkboxList = exam.checkboxList || []
for (let i = 0; i < exam.checkboxList.length; i++) {
tmp = exam.checkboxList[i]
if (!tmp.user_answer || !tmp.user_answer.length) tmp.user_answer = []
// if (!tmp.right_answer || !tmp.right_answer.length) tmp.right_answer = []
tmp.right_answer = tmp.right_answer || []
if (!tmp.get_score) tmp.get_score = -1
}
exam.shortAnswerList = exam.shortAnswerList || []
for (let i = 0; i < exam.shortAnswerList.length; i++) {
tmp = exam.shortAnswerList[i]
tmp.user_answer = tmp.user_answer ? Base64.decode(tmp.user_answer.replace(/ /gi, '+')) : ''
if (!tmp.attachments || !tmp.attachments.length) tmp.attachments = []
tmp.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>
`
}
}
return exam
})
return exam
})
}
......
......@@ -113,7 +113,7 @@ export default class PlayerAPI extends BaseAPI {
* @param {[string]} semester_id -> sid
* @param {[string]} exam_id -> eid
*/
getExamAnswer = (cid, sid, eid) => this.get(`/v2/education/${sid}/${cid}/examination/${eid}/sheet`, {})
getExamAnswer = (cid, sid, eid, obj = {}) => this.get(`/v2/education/${sid}/${cid}/examination/${eid}/sheet`, obj)
/**
* 获取考试状态
* @param {[string]} course_id -> cid
......
......@@ -14,7 +14,7 @@ export default class Before {
'studentHelp',
'teacherHelp'
]
this.isMobile = /android|iphone|ipad|ipod/i.test(UA)
this.isMobile = /android|iphone|ipod/i.test(UA)
}
async update(to, from, next) {
......
......@@ -85,8 +85,8 @@ export default {
]
}
if (this.readOnly !== null) {
config.readOnly = this.readOnly
if (this.disabled !== null) {
config.readOnly = this.disabled
}
const editor = (this.ckEditor = CKEDITOR.replace(
......
......@@ -24,7 +24,6 @@
<script type="text/javascript" src="https://zws-imgs-pub.ezijing.com/static/build/learn-mba/static/common/runtime.js"></script>
<!-- 直接引入aliyun播放插件 JS -->
<script type="text/javascript" charset="utf-8" src="https://g.alicdn.com/de/prismplayer/2.8.8/aliplayer-min.js"></script>
<script type="text/javascript" charset="utf-8" src="https://player.alicdn.com/aliplayer/presentation/js/aliplayercomponents.min.js"></script>
<!-- 解决iframe嵌套,CC视频在safri中打开免登陆兼容问题 -->
<script src="//view.csslcloud.net/js/_fix_.js"></script>
<script src="//view.csslcloud.net/js/jquery-1.9.0.min.js" type="text/javascript"></script>
......
......@@ -36,21 +36,54 @@ export function getChapterVideoAliyun(vid) {
}
/**
* 获取答题信息
* 获取章节视频播放进度
* @param {string} semesterId 学期ID
* @param {string} resourseId 章节的资源ID
* @param {Object} params
*/
export function getChapterVideoProgress(semesterId, resourseId, params) {
return httpRequest.get(
`/v2/education/video/${semesterId}/${resourseId}/device`,
params
)
}
/**
* 更新章节视频播放进度
* @param {Object} params
*/
export function updateChapterVideoProgress(params) {
return httpRequest.get('/v2/analytics/upload-video', params)
}
/**
* 获取章节作业
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} resourseId 章节的资源ID
*/
export function getChapterExam(semesterId, courseId, resourseId) {
export function getChapterHomework(semesterId, courseId, resourseId) {
return httpRequest.get(
`/v2/education/homeworks/${semesterId}/${courseId}/${resourseId}`
)
}
/**
* 获取提交作业截止时间
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} chapterId 章节ID
*/
export function getChapterHomeworkDeadline(semesterId, courseId, chapterId) {
return httpRequest.get(
`/v2/education/homeworks/${semesterId}/${courseId}/${chapterId}/deadline`
)
}
/**
* 提交考试
*/
export function sbumitChapterExam(params) {
export function sbumitChapterHomework(params) {
return httpRequest.post('/v2/education/homeworks', params, {
headers: { 'Content-Type': 'application/json' }
})
......@@ -130,8 +163,9 @@ export function submitCourseExam(semesterId, courseId, examId, data) {
* @param {string} courseId 课程ID
* @param {string} examId 试题ID
*/
export function getCourseExamResult(semesterId, courseId, examId) {
export function getCourseExamResult(semesterId, courseId, examId, params) {
return httpRequest.get(
`/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`
`/v2/education/${semesterId}/${courseId}/examination/${examId}/sheet`,
params
)
}
<template>
<ul class="chapter-list">
<li class="chapter-item" v-for="item in data" :key="item.id">
<li class="chapter-item" v-for="item in chapters" :key="item.id">
<h4>{{item.name}}</h4>
<ul class="chapter-item-list">
<li
......@@ -9,7 +9,7 @@
@click="onClick(subItem)"
:class="{'is-active': subItem.id === (active ? active.id : '')}"
>
<span class="chapter-item-list__name">{{subItem.name | showName(subItem.type)}}</span>
<span class="chapter-item-list__name">{{subItem.name | showName(subItem)}}</span>
<i class="el-icon" :class="genIconClass(subItem.type)"></i>
</li>
</ul>
......@@ -20,7 +20,14 @@
<script>
export default {
props: {
data: { type: Array, default: () => [] },
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
},
chapters: { type: Array, default: () => [] },
// 当前选中的章节
active: {
type: Object,
......@@ -30,26 +37,13 @@ export default {
}
},
data() {
return {
otherList: [
{
name: '大作业及资料',
children: [
{ name: '课程大作业', id: 'course_work' },
{ name: '课程资料', id: 'course_info' },
{ name: '教学评估', id: 'teach_evaluation' }
]
}
]
}
},
computed: {
list() {
return this.data.concat(this.otherList)
}
return {}
},
filters: {
showName(name, type) {
showName(name, data) {
if (data.type === 5 && data.live) {
return `${name}(${data.live.start_time})`
}
return name
}
},
......@@ -63,16 +57,20 @@ export default {
return map[type] || 'el-icon-self-cc-book'
},
onClick(data) {
if (data.type === 1) {
return
}
// 课程大作业
// if (data.id === 'course_work') {
// this.$router.push({ name: 'viewerCourseWork' })
// return
// }
// 课程资料
// if (data.id === 'course_info') {
// this.$router.push({ name: 'viewerCourseFile' })
// return
// }
if (data.id === 'course_work' && !this.data.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return
}
// 教学评估
if (data.id === 'teach_evaluation') {
const { sid, cid } = this.$route.params
this.$router.push({ name: 'survey', params: { sid, cid } })
return
}
this.$router.push({
name: 'viewerCourseChapter',
params: { id: data.id }
......
......@@ -3,12 +3,12 @@
<el-tabs v-model="activeName">
<el-tab-pane label="章节" name="0">
<div class="tab-pane">
<aside-chapter :data="chapters" :active="active"></aside-chapter>
<aside-chapter :data="data" :chapters="chapters" :active="active"></aside-chapter>
</div>
</el-tab-pane>
<el-tab-pane label="讲义" name="1" v-if="active && active.type === 2">
<div class="tab-pane">
<aside-lecture :data="ppts" :pptIndex="pptIndex" v-on="$listeners"></aside-lecture>
<aside-lecture :ppts="ppts" :pptIndex="pptIndex" v-on="$listeners"></aside-lecture>
</div>
</el-tab-pane>
</el-tabs>
......@@ -21,6 +21,13 @@ import AsideLecture from './lecture.vue'
export default {
props: {
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
},
// 章节
chapters: { type: Array, default: () => [] },
// 讲义
......
<template>
<ul class="lecture-list">
<li
v-for="(item, index) in data"
v-for="(item, index) in ppts"
:key="item.id"
@click="onClick(index)"
:class="{'is-active': index === activeIndex}"
......@@ -16,7 +16,7 @@ export default {
props: {
// 当前选择的PPT
pptIndex: { type: Number, default: 0 },
data: { type: Array, default: () => [] }
ppts: { type: Array, default: () => [] }
},
data() {
return {
......
......@@ -84,9 +84,8 @@ export default {
{ name: 'insert', items: ['Image', 'Table', 'HorizontalRule'] }
]
}
if (this.readOnly !== null) {
config.readOnly = this.readOnly
if (this.disabled !== null) {
config.readOnly = this.disabled
}
const editor = (this.ckEditor = CKEDITOR.replace(
......
<template>
<div>
<ul class="file-list" v-if="files.length">
<li class="file-list-item" v-for="file in files" :key="file.id">
<a :href="file.file_url" target="_blank">
<i class="el-icon-document"></i>
<div v-html="file.file_name"></div>
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</li>
</ul>
<div class="empty" v-else>
<slot name="empty">暂无课程资料</slot>
</div>
</div>
</template>
<script>
export default {
name: 'FilePanel',
props: {
// 标题
title: { type: String, default: '课程资料' },
// 文件列表
files: { type: Array, default: () => [] }
}
}
</script>
<style lang="scss" scoped>
.file-list {
padding: 0;
}
.file-list-item {
display: flex;
font-size: 16px;
padding: 20px 30px;
margin-bottom: 10px;
background-color: #fff;
list-style: none;
border-radius: 32px;
justify-content: space-between;
a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
white-space: nowrap;
&:hover {
color: #b49441;
}
::v-deep * {
margin: 0;
padding: 0;
}
}
}
.empty {
font-size: 18px;
line-height: 80px;
background-color: #fff;
text-align: center;
border-radius: 40px;
}
</style>
<template>
<div class="upload">
<el-upload action :show-file-list="false" :http-request="httpRequest">
<el-upload action :disabled="disabled" :show-file-list="false" :http-request="httpRequest">
<slot></slot>
<el-button type="text" icon="el-icon-upload">点击上传</el-button>
<template v-slot:tip>
......@@ -15,11 +15,23 @@
<i class="el-icon-document"></i>
{{ fileUrl | fileName }}
</a>
<a :href="fileUrl" :download="fileUrl | fileName" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
<div>
<a
href="javascript:;"
@click="handleRemove(index)"
style="margin-right:10px;"
v-if="!disabled"
>
<el-tooltip effect="dark" content="删除">
<i class="el-icon-delete"></i>
</el-tooltip>
</a>
<a :href="fileUrl" :download="fileUrl | fileName" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
</el-tooltip>
</a>
</div>
</div>
</div>
</div>
......@@ -31,7 +43,8 @@ import * as api from '../../api'
export default {
name: 'VUpload',
props: {
value: { type: [String, Array] }
value: { type: [String, Array] },
disabled: { type: Boolean, default: false }
},
data() {
return {
......@@ -39,9 +52,18 @@ export default {
}
},
watch: {
value(value) {
if (value) {
this.fileList = Array.isArray(value) ? value : [value]
value: {
immediate: true,
handler(value) {
if (value) {
if (Array.isArray(value)) {
this.fileList = value.map(item => {
return item.url || item
})
} else {
this.fileList = [value]
}
}
}
}
},
......@@ -63,6 +85,10 @@ export default {
}
}
})
},
handleRemove(index) {
this.fileList.splice(index, 1)
this.$emit('input', Array.isArray(this.value) ? this.fileList : '')
}
}
}
......
......@@ -11,8 +11,9 @@
<script>
// components
import ChapterPlayer from './player/ChapterPlayer.vue' // 章节视频
import ChapterPlayer from './player/chapterPlayer.vue' // 章节视频
import ChapterWork from './work/index.vue' // 章节作业
import ChapterExam from './work/chapterExam.vue' // 章节考试
import ChapterRead from './read/chapterRead.vue' // 章节资料
import ChapterLive from './live/chapterLive.vue' // 章节直播
import CourseWork from './work/courseWork.vue' // 课程大作业
......@@ -25,6 +26,7 @@ export default {
ChapterPlayer,
ChapterWork,
ChapterRead,
ChapterExam,
ChapterLive,
CourseWork,
CourseRead,
......@@ -45,6 +47,7 @@ export default {
3: 'ChapterWork', // 作业
4: 'ChapterRead', // 资料
5: 'ChapterLive', // 直播
9: 'ChapterExam', // 考试
99: 'CourseWork', // 课程大作业
100: 'CourseRead', // 课程资料
101: 'CourseExam' // 课程考试
......
......@@ -39,14 +39,17 @@ export default {
},
iframeUrl() {
const live = this.chapter.live
const liveStatus = live.live_status
live.viewer_name = live.viewer_name || this.nickName
if (liveStatus === 103 && live.enable_record === 1) {
if (
live.live_status === 2 &&
live.enable_record === 1 &&
live.record_url
) {
// enable_record 0:不启用回放 1:开启回放
// 查看回放
return `https://view.csslcloud.net/api/view/callback?recordid=${live.record_id}&roomid=${live.room_id}&userid=${live.user_id}&autoLogin=true&viewername=${live.viewer_name}&viewertoken=${live.viewer_token}`
return live.record_url
} else {
// 直播
live.viewer_name = live.viewer_name || this.nickName
return `https://view.csslcloud.net/api/view/index?roomid=${live.room_id}&userid=${live.user_id}&autoLogin=true&viewername=${live.viewer_name}&viewertoken=${live.viewer_token}`
}
}
......
......@@ -5,17 +5,21 @@
<!-- 视频 -->
<video-player
:isSkip="isSkip"
:skipTime="skipTime"
:video="chatperResources.video"
@timeupdate="onTimeupdate"
@ready="onReady"
ref="videoPlayer"
></video-player>
</div>
<div class="player-column" v-if="pptVisible">
<!-- ppt -->
<ppt-player
:index="pptIndex"
:ppts="chatperResources.ppts"
@close="pptVisible = false"
@close="onPPTClose"
@fullscreen="onPPTFullscreen"
@videoSyncTime="onVideoSyncTime"
></ppt-player>
</div>
</div>
......@@ -30,6 +34,8 @@
</template>
<script>
import Cookies from 'js-cookie'
import { throttle } from 'lodash'
// api
import * as api from '../../api'
// components
......@@ -46,11 +52,26 @@ export default {
pptIndex: { type: Number, default: 0 }
},
data() {
// 是否跳过片头
const isSkip = window.localStorage.getItem('isSkip') === 'true'
return {
videoVisible: true,
pptVisible: false,
isSkip: false,
chatperResources: null
isSkip,
skipTime: 6,
chatperResources: null,
throttled: null,
throttleWait: 5, // 秒
progress: {
cpt: 0, // 当前播放时间
mpt: 0, // 视频时长
progress: 0, // 进度
pt: 0 // 累计播放时间
},
player: null,
watchedTimePoint: [], // 视频观看的时间点
timer: null,
isPlaying: false
}
},
watch: {
......@@ -59,6 +80,14 @@ export default {
}
},
computed: {
// 学期ID
sid() {
return this.$route.params.sid
},
// 课程ID
cid() {
return this.$route.params.cid
},
// 视频资源ID
resourceId() {
return this.chapter.resource_id
......@@ -95,25 +124,63 @@ export default {
// 始终跳过片头
toggleSkip() {
this.isSkip = !this.isSkip
window.localStorage.setItem('isSkip', this.isSkip)
},
// 关闭PPT
onPPTClose() {
this.pptVisible = false
this.videoVisible = true
},
// PPT全屏
onPPTFullscreen(value) {
this.videoVisible = !value
},
// 设置视频时间为当前PPT时间
onVideoSyncTime(time) {
this.player.seek(time)
},
// 播放器ready
onReady(player) {
this.player = player
// 跳转播放进度
if (this.progress.cpt) {
this.player.seek(this.progress.cpt)
}
// 更新视频观看总时长
this.updateWatchTime()
},
// 当前播放时间更新
onTimeupdate(time) {
this.isPlaying = true
const ppts = this.chatperResources.ppts || []
let index = this.chatperResources.ppts.findIndex(
item => item.ppt_point > time
)
index = index !== -1 ? index - 1 : ppts.length - 1
this.$emit('change-ppt', index)
const durations = this.player.getDuration()
// 更新视频时间
this.progress.cpt = parseInt(time)
// 更新视频时长
this.progress.mpt = parseInt(durations)
const hasTimePoint = this.watchedTimePoint.includes(this.progress.cpt)
if (!hasTimePoint) {
this.watchedTimePoint.push(this.progress.cpt)
}
// 更新视频进度,10秒更新一次
if (this.throttled) {
this.throttled(time, durations)
} else {
this.throttled = throttle(
this.updateChapterVideoProgress,
this.throttleWait * 1000
)
}
},
// 更新视频当前播放时间
updateVideoCurrentTime() {
const player = this.$refs.videoPlayer.player
const ppt = this.chatperResources.ppts[this.pptIndex]
ppt && player.seek(ppt.ppt_point) // 增加2秒
ppt && this.player.seek(ppt.ppt_point) // 增加2秒
},
// 获取章节视频详情
getChapterVideo() {
......@@ -125,14 +192,89 @@ export default {
})
} else {
api.getChapterVideo(this.resourceId).then(response => {
this.chatperResources = response
Array.isArray(response.ppts) && this.$emit('pptupdate', response.ppts)
let { video, audio, ppts } = response
video = video.reduce(
(result, item) => {
if (item.quality === '10') {
result.LD = item.playurl
}
if (item.quality === '20') {
result.SD = item.playurl
}
return result
},
{ LD: '', SD: '' }
)
this.chatperResources = { video, audio, ppts }
Array.isArray(ppts) && this.$emit('pptupdate', ppts)
})
}
},
// 获取章节视频进度
getChapterVideoProgress() {
api
.getChapterVideoProgress(this.sid, this.resourceId, {
device_id: Cookies.get('_idt')
})
.then(response => {
this.progress = response
// 跳转播放进度
if (this.player && response.cpt) {
this.player.seek(response.cpt)
}
})
},
// 更新章节视频进度
updateChapterVideoProgress(time, durations) {
// this.progress.pt += this.throttleWait
// 登录用户信息
const user = window.G.UserInfo
const params = {
sid: user.student_info.id,
uid: user.uid,
d: Cookies.get('_idt'),
i: Cookies.get('_idt'),
c: this.cid, // 课程ID
s: this.sid, // 学期ID
v: this.resourceId, // 视频资源ID
_p: this.progress.pt, // 累计时间
_m: this.progress.mpt, // 当前播放最大时间
_c: this.progress.cpt, // 当前播放位置
ps: this.watchedTimePoint.join(',') // 播放时,统计帧
}
api.updateChapterVideoProgress(params)
// 清空已经上传过的观看时间点
this.watchedTimePoint = []
},
// 更新观看总时长
updateWatchTime() {
this.timer && clearInterval(this.timer)
// 增加跳过片头时间
if (this.isSkip && !this.progress.pt) {
this.progress.pt = this.skipTime + this.throttleWait
}
// 默认增加时间
this.progress.pt = this.progress.pt || this.throttleWait
this.timer = setInterval(() => {
// safair 浏览器下有bug
// const status = this.player.getStatus()
if (this.isPlaying) {
// 播放倍速
const speed = this.player._originalPlaybackRate || 1
this.progress.pt = this.progress.pt + 1 * speed
}
this.isPlaying = false
}, 1000)
}
},
beforeMount() {
// 获取视频
this.getChapterVideo()
// 获取视频进度
this.getChapterVideoProgress()
},
destroyed() {
this.timer && clearInterval(this.timer)
}
}
</script>
......@@ -148,6 +290,7 @@ export default {
.player-main {
display: flex;
flex: 1;
overflow: hidden;
}
.player-column {
flex: 1;
......
......@@ -19,10 +19,18 @@
<span>{{ppts.length}}</span>
</div>
<div class="ppt-player-controls__tools">
<i :class="['el-icon-self-xuexiao', (currentSync ? 'active' : '')]" @click="onToggleSync"></i>
<i class="el-icon-self-quanping" @click="fullscreen"></i>
<i class="el-icon-self-shipin" @click="setVideoTime"></i>
<i class="el-icon-self-guanbi" @click="$emit('close')"></i>
<el-tooltip content="PPT同步视频播放">
<i :class="['el-icon-self-xuexiao', (isSync ? 'active' : '')]" @click="onToggleSync"></i>
</el-tooltip>
<el-tooltip content="放大PPT">
<i class="el-icon-self-quanping" @click="fullscreen"></i>
</el-tooltip>
<el-tooltip content="切换视频到当前PPT页">
<i class="el-icon-self-shipin" @click="setVideoTime"></i>
</el-tooltip>
<el-tooltip content="关闭PPT">
<i class="el-icon-self-guanbi" @click="$emit('close')"></i>
</el-tooltip>
</div>
</div>
</template>
......@@ -34,20 +42,21 @@ export default {
name: 'ppt-player',
props: {
ppts: { type: Array },
index: { type: Number, default: 0 },
isSync: { type: Boolean, default: false }
index: { type: Number, default: 0 }
},
data() {
return {
currentIndex: this.index,
currentSync: this.isSync,
isSync: true,
isFullscreen: false
}
},
watch: {
index: {
handler(value) {
this.currentIndex = value
if (this.isSync) {
this.currentIndex = value
}
}
}
},
......@@ -67,20 +76,18 @@ export default {
},
prev() {
this.currentIndex = this.getIndex(this.currentIndex - 1)
this.currentSync = false
this.isSync = false
},
next(e) {
this.currentIndex = this.getIndex(this.currentIndex + 1)
this.currentSync = false
this.isSync = false
},
onToggleSync(e) {
this.currentSync = !this.currentSync
this.currentIndex = this.currentSync
? this.currentIndex
: this.currentIndex
this.isSync = !this.isSync
},
setVideoTime(e) {
this.$emit('onVideoSyncTime', this.ppts[this.currentIndex].ppt_point)
this.isSync = true
this.$emit('videoSyncTime', this.ppts[this.currentIndex].ppt_point)
},
// 全屏
fullscreen() {
......
......@@ -13,34 +13,41 @@ export default {
createPlayer() {
const _this = this
const { FD, LD, SD } = this.video
/*
"OD" : "原画"
"FD" : "流畅"
"LD" : "标清"
"SD" : "高清"
"HD" : "超清"
"2K" : "2K"
"4K" : "4K"
*/
this.player = new Aliplayer(
{
id: 'player',
source: JSON.stringify({ FD, LD, SD }),
width: '100%',
height: '100%',
autoplay: true,
autoplay: false,
isLive: false,
preload: true,
useH5Prism: true,
controlBarVisibility: 'always',
components: [
{
name: 'QualityComponent',
type: AliPlayerComponent.QualityComponent
}
]
defaultDefinition: 'SD',
useHlsPluginForSafari: true
},
function(player) {
player.on('sourceloaded', function(params) {
const paramData = params.paramData
const desc = paramData.desc
const definition = paramData.definition
player
.getComponent('QualityComponent')
.setCurrentQuality(desc, definition)
player.on('ready', function() {
// 跳过片头
_this.isSkip && player.seek(6)
_this.$emit('ready', player)
})
player.on('timeupdate', function(event) {
_this.$emit('timeupdate', player.getCurrentTime())
})
player.on('error', function(event) {
console.log(event)
})
}
)
}
......
......@@ -8,7 +8,7 @@
<script>
// components
import Container from '../common/container.vue'
import FileList from '../common/fileList.vue'
import FileList from './fileList.vue'
// 章节阅读资料
export default {
......
......@@ -8,7 +8,7 @@
<script>
// components
import Container from '../common/container.vue'
import FileList from '../common/fileList.vue'
import FileList from './fileList.vue'
// 课程阅读资料
export default {
......
......@@ -4,9 +4,9 @@
<li class="file-list-item" v-for="file in files" :key="file.id">
<a :href="file.file_url" target="_blank">
<i class="el-icon-document"></i>
{{ file.file_name }}
<div v-html="file.file_name"></div>
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<!-- <span v-if="file.file_size">{{ file.file_size }}</span> -->
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
......@@ -46,11 +46,18 @@ export default {
border-radius: 32px;
justify-content: space-between;
a {
display: flex;
align-items: center;
text-decoration: none;
color: #333;
white-space: nowrap;
&:hover {
color: #b49441;
}
::v-deep * {
margin: 0;
padding: 0;
}
}
}
.empty {
......
<template>
<container :title="chapter.name" v-loading="loading">
<template v-slot:header-aside v-if="isSubmited">正确率:{{detail.score}}%</template>
<div class="exam">
<div class="exam-form">
<el-form :disabled="isSubmited">
<exam-item
v-for="(item, index) in unorderedQuestions"
:index="index"
:type="item.question_type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</div>
</container>
</template>
<script>
// libs
import { shuffle } from 'lodash'
// components
import Container from '../common/container.vue'
import ExamItem from './examItem.vue'
// api
import * as api from '../../api'
// 章节测试题
export default {
name: 'ChapterTest',
components: { Container, ExamItem },
props: {
// 当前选中的章节
chapter: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
loading: false,
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null
}
},
watch: {
chapter: {
immediate: true,
handler(data) {
this.questions = data.homework
? this.genQuenstions(data.homework.questions)
: []
}
}
},
computed: {
// 学期ID
sid() {
return this.$route.params.sid
},
// 课程ID
cid() {
return this.$route.params.cid
},
// 当前页面的ID
pid() {
return this.$route.params.id
},
// 资源ID
resourceId() {
return this.chapter.resource_id
},
// 打乱顺序的问题列表
unorderedQuestions() {
const ids = this.questions.map(item => item.id)
const sortIds = shuffle(ids)
return sortIds.map(id => this.questions.find(item => item.id === id))
},
// 是否提交
isSubmited() {
return this.detail ? !!this.detail.work_contents : false
},
// 提交按钮文本
submitText() {
return this.isSubmited ? '已提交' : '提交'
}
},
methods: {
// 获取测试答题详情
getDetail() {
this.loading = true
api
.getChapterHomework(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
const parseAnswers = JSON.parse(this.detail.work_contents)
// 设置答案
this.questions = this.questions.map(item => {
const found = parseAnswers.find(
answer => answer.question_id === item.id
)
if (found) {
const selectedIds = found.options.reduce((result, item) => {
item.selected && result.push(item.id)
return result
}, [])
item.user_answer =
item.question_type === 2 ? selectedIds : selectedIds[0]
}
return item
})
this.questions = this.genQuenstions(this.questions)
}
})
.finally(() => {
this.loading = false
})
},
// 组装问题数据
genQuenstions(list) {
if (!list) {
return []
}
return list.map(item => {
let temp = null
if (item.question_type === 1) {
// 单选
temp = {
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
} else if (item.question_type === 2) {
// 多选
temp = {
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
} else if (item.question_type === 3) {
// 简答
temp = {
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
: '',
attachments: item.attachments || ''
}
}
}
return Object.assign(
{},
item,
{
content: item.question_content,
options: item.question_options
? JSON.parse(item.question_options)
: []
},
temp
)
})
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
}
}
return true
},
// 提交
onSubmit() {
// 校验
if (!this.checkSubmit()) {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
return
}
// 计算答题时间
const duration = Math.floor(
(new Date().getTime() - this.startTime) / 1000
)
// 答案数据
const data = this.handleSubmitData()
// 计算分数
const score = data.reduce((result, item) => {
item.is_correct && result++
return result
}, 0)
const total = this.questions.length
const params = {
semester_id: this.sid,
course_id: this.cid,
chapter_id: this.pid,
work_id: this.resourceId,
work_contents: JSON.stringify(data),
duration,
score: ((score / total) * 100).toFixed(1)
}
// 请求接口
this.handleSubmitRequest(params)
},
// 提交的答案数据
handleSubmitData() {
const result = this.questions.map(item => {
// 设置提交选中状态
let isCorrect = true
const options = item.options.map(option => {
// 选择的项
const answers = item.formModel.user_answer
// 是否选中该项
const selected = Array.isArray(answers)
? answers.includes(option.id)
: option.id === answers
// 是否选择正确
if (option.checked !== selected && isCorrect) {
isCorrect = false
}
return {
id: option.id,
checked: option.checked,
option: option.option,
selected
}
})
return {
question_id: item.id,
is_correct: isCorrect ? 1 : 0,
options
}
})
return result
},
// 请求提交接口
handleSubmitRequest(params) {
api
.sbumitChapterHomework(params)
.then(response => {
if (response.status) {
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
.catch(error => {
this.$message.error(error.message)
})
}
},
beforeMount() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.exam-buttons {
padding: 40px 0;
text-align: center;
.el-button {
width: 240px;
margin: 40px auto;
}
}
</style>
<template>
<container :title="chapter.name" v-loading="loading">
<div class="exam-form">
<el-form :disabled="isRevised">
<el-form :disabled="disabled || !isWorkTime">
<exam-item
v-for="(item, index) in questions"
:index="index"
:type="item.question_type"
:data="item"
:value="item.formModel"
:disabled="isRevised"
:disabled="disabled || !isWorkTime"
:key="item.id"
></exam-item>
</el-form>
</div>
<div class="work-bottom" v-if="detail">
<div class="info">
<template v-if="isRevised">
<p style="color:red;" v-if="deadline">请于截止日期 {{ deadline }} 前提交</p>
<!-- 驳回状态 -->
<template v-if="detail && detail.status === 1">
<div class="work-bottom">
<div class="info">
<div class="paper-check">
<p>批改时间:{{detail.check_date}}</p>
<h4>作业被驳回,点击“重新编辑”按钮重新编辑内容再次提交</h4>
<div class="paper-check-item">
<b>评分</b>
{{detail.score}}
<b>驳回时间</b>
{{ detail.checker_time }}
</div>
<div class="paper-check-item">
<b>评语</b>
<b>驳回说明</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{detail.created_time}} 提交,等待老师批改中。</p>
<template v-if="detail.updated_time && detail.updated_time !== detail.created_time">
<p class="help">最近提交时间: {{detail.updated_time}}</p>
</div>
</div>
<div class="buttons">
<el-button type="primary" @click="onReEdit" :disabled="!isWorkTime">重新编辑</el-button>
</div>
</template>
<!-- 正常状态 -->
<template v-else>
<div class="work-bottom" v-if="detail">
<div class="info">
<template v-if="isRevised">
<div class="paper-check">
<p>批改时间:{{ detail.checker_time }}</p>
<div class="paper-check-item">
<b>评分:</b>
{{ detail.score }}
</div>
<div class="paper-check-item">
<b>评语:</b>
<div class="edit_html" v-html="detail.check_comments"></div>
</div>
</div>
</template>
</template>
<template v-else-if="detail.created_time">
<p class="help">已于 {{ detail.created_time }} 提交,等待老师批改中。</p>
<template
v-if="
detail.updated_time &&
detail.updated_time !== detail.created_time
"
>
<p class="help">最近提交时间: {{ detail.updated_time }}</p>
</template>
</template>
</div>
</div>
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button type="primary" @click="onSubmit" :disabled="isRevised">{{submitText}}</el-button>
</el-tooltip>
</div>
<div class="buttons">
<el-tooltip content="在获老师批改之前,可以多次提交,将以最后一次提交为准" placement="right">
<el-button
type="primary"
@click="onSubmit"
:disabled="disabled || !isWorkTime"
>{{ submitText }}</el-button>
</el-tooltip>
</div>
</template>
</container>
</template>
......@@ -80,7 +113,9 @@ export default {
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null
messageInstance: null,
deadline: '', // 截止时间
disabled: false
}
},
watch: {
......@@ -112,22 +147,42 @@ export default {
},
// 是否批改
isRevised() {
return this.detail ? !!this.detail.check_date : false
return this.detail ? this.detail.status === 0 : false
},
// 提交按钮文本
submitText() {
return this.isRevised ? '已批改' : '提交'
},
// 是否是提交作业时间
isWorkTime() {
if (!this.deadline) {
return true
}
// 大于开始时间,小于结束时间
const endTime = +new Date(this.deadline)
const currentTime = new Date().getTime()
return currentTime < endTime
}
},
methods: {
// 获取作业截止时间
getDeadline() {
api
.getChapterHomeworkDeadline(this.sid, this.cid, this.pid)
.then(response => {
this.deadline = response.dead_line
})
},
// 获取详情
getDetail() {
this.loading = true
api
.getChapterExam(this.sid, this.cid, this.resourceId)
.getChapterHomework(this.sid, this.cid, this.resourceId)
.then(response => {
this.detail = Array.isArray(response) ? null : response
if (this.detail) {
// -1未处理 0已处理 1驳回
this.disabled = [0, 1].includes(this.detail.status)
const parseAnswers = JSON.parse(this.detail.work_contents)
// 设置答案
this.questions = this.questions.map(item => {
......@@ -205,7 +260,9 @@ export default {
// 校验
if (!this.checkSubmit()) {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
this.messageInstance = this.$message.error(
'答题内容不能为空,请检查并输入内容'
)
return
}
// 计算答题时间
......@@ -214,12 +271,12 @@ export default {
)
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.question_type === 3) {
item.formModel.user_answer = Base64.encode(item.formModel.user_answer)
}
return {
question_id: item.id,
descreption: item.formModel.user_answer,
descreption:
item.question_type === 3
? Base64.encode(item.formModel.user_answer)
: item.formModel.user_answer,
file_url: item.formModel.attachments,
is_encoded: 1
}
......@@ -239,7 +296,7 @@ export default {
// 请求提交接口
handleSubmitRequest(params) {
api
.sbumitChapterExam(params)
.sbumitChapterHomework(params)
.then(response => {
if (response.status) {
this.$message.success('提交成功,等待批改')
......@@ -251,10 +308,46 @@ export default {
.catch(error => {
this.$message.error(error.message)
})
},
// 重新编辑
onReEdit() {
this.disabled = false
this.detail.status = -1
}
},
beforeMount() {
this.getDetail()
this.getDeadline()
}
}
</script>
<style lang="scss" scoped>
.work-bottom {
margin-top: 20px;
.info {
color: #999;
line-height: 28px;
}
}
.buttons {
padding: 20px 0;
::v-deep .el-button {
width: 120px;
}
}
.paper-check {
padding: 10px;
color: #000;
border: 1px solid #dedede;
h4 {
margin: 0 0 10px;
}
}
.paper-check-item {
display: flex;
b {
white-space: nowrap;
}
}
</style>
<template>
<container :title="detail.title" v-loading="loading">
<template v-slot:header-aside v-if="isExamComplete">分数:{{exam.score.total}}</template>
<div class="exam">
<template v-if="status.examination_status === '00'">
<div class="no-exam">暂无考试</div>
......@@ -17,39 +18,20 @@
@click="onStartExam"
>{{startExamButtonText}}</el-button>
</div>
<!-- 考试完成 -->
<div class="exam-finish" v-if="isExamComplete">
<table class="exam-table">
<tr>
<th>单选</th>
<th>多选</th>
<th>简答</th>
</tr>
<tr>
<td>{{exam.score.radio}}</td>
<td>{{exam.score.checkbox}}</td>
<td>{{exam.score.shortAnswer}}</td>
</tr>
<tr>
<td colspan="3">
<div class="exam-total">总分:{{exam.score.total}}</div>
</td>
</tr>
</table>
<el-button type="text" @click="examVisible = !examVisible">查看试卷</el-button>
</div>
<!-- 考试试题 -->
<div class="exam-form" v-if="isStartExam" v-show="examVisible">
<div class="exam-form" v-if="isStartExam">
<el-form :disabled="isSubmited">
<exam-item
v-for="(item, index) in questions"
:index="index"
:type="item.type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
<template v-for="items in questions">
<exam-item
v-for="(item, index) in items"
:index="index"
:type="item.type"
:data="item"
:value="item.formModel"
:disabled="isSubmited"
:key="item.id"
></exam-item>
</template>
<div class="exam-buttons">
<el-tooltip effect="dark" content="提交之后就不能修改了哦" placement="right">
<el-button type="primary" @click="onSubmit">{{submitText}}</el-button>
......@@ -97,18 +79,11 @@ export default {
detail: {},
status: {},
questions: [],
values: [], // 提交的答案
messageInstance: null,
exam: {},
isStartExam: false, // 是否开始考试
autoSubmitTimer: null, // 自动提交定时器
checkStatusTimer: null, // 考试状态定时器
examVisible: true
}
},
watch: {
isExamComplete(value) {
this.examVisible = !value
checkStatusTimer: null // 考试状态定时器
}
},
computed: {
......@@ -163,7 +138,7 @@ export default {
this.detail = Array.isArray(response) ? null : response
// 设置问题列表数据
this.questions = this.detail
? this.genQuenstions(this.detail.examination)
? this.genQuestions(this.detail.examination)
: []
callback && callback()
})
......@@ -172,42 +147,44 @@ export default {
})
},
// 组装问题数据
genQuenstions(data) {
if (!data) {
return
genQuestions(list) {
if (!list) {
return []
}
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
return Object.assign({}, item, temp)
})
// 多选
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
return Object.assign({}, item, temp)
})
// 问答
shortAnswerList = shortAnswerList.map(item => {
const temp = {
type: 3,
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
: '',
attachments: []
return list.map(data => {
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
}
return Object.assign({}, item, temp)
return Object.assign({}, item, temp)
})
// 多选
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
return Object.assign({}, item, temp)
})
// 问答
shortAnswerList = shortAnswerList.map(item => {
const temp = {
type: 3,
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer.replace(/ /gi, '+'))
: '',
attachments: item.attachments || []
}
}
return Object.assign({}, item, temp)
})
return [...radioList, ...checkboxList, ...shortAnswerList]
})
return [...radioList, ...checkboxList, ...shortAnswerList]
},
// 获取考试状态
getExamStatus() {
......@@ -234,7 +211,7 @@ export default {
if (response.code !== 8001) {
this.isStartExam = true
this.exam = response
this.questions = this.genQuenstions(response.sheet)
this.questions = this.genQuestions(response.sheet)
// 自动提交
if (this.isStartExam && !this.isSubmited && !this.isExamComplete) {
this.autoSubmit()
......@@ -244,11 +221,13 @@ export default {
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
for (let i = 0; i < this.questions.length; i++) {
const questions = this.questions[i]
for (let k = 0; k < questions.length; k++) {
const value = questions[k].formModel.user_answer
if (Array.isArray(value) ? !value.length : !value) {
return false
}
}
}
return true
......@@ -262,12 +241,7 @@ export default {
return
}
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.type === 3) {
item.formModel.user_answer = Base64.encode(item.formModel.user_answer)
}
return item.formModel
})
const answers = this.handleSubmitData()
// 提交参数
const params = { answers: JSON.stringify(answers), type: 1 }
// 请求接口
......@@ -279,19 +253,38 @@ export default {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.autoSubmitTimer = setInterval(() => {
// 提交的答案数据
const answers = this.questions.map(item => {
if (item.type === 3) {
item.formModel.user_answer = Base64.encode(
item.formModel.user_answer
)
}
return item.formModel
})
const answers = this.handleSubmitData()
const params = { answers: JSON.stringify(answers), type: 0 }
// 请求接口
this.handleSubmitRequest(params)
}, 10000)
},
// 处理请求接口答案数据
handleSubmitData() {
return this.questions.map(questions => {
return questions.reduce(
(result, item) => {
// 单选题
if (item.type === 1) {
result.radioList.push(item.formModel)
}
// 多选题
if (item.type === 2) {
result.checkboxList.push(item.formModel)
}
// 简答题
if (item.type === 3) {
const formModel = Object.assign({}, item.formModel, {
user_answer: Base64.encode(item.formModel.user_answer)
})
result.shortAnswerList.push(formModel)
}
return result
},
{ radioList: [], checkboxList: [], shortAnswerList: [] }
)
})
},
// 请求提交接口
handleSubmitRequest(params) {
api
......@@ -344,27 +337,6 @@ export default {
font-size: 30px;
text-align: center;
}
.exam-finish {
margin: 40px 0;
}
.exam-table {
width: 100%;
border-collapse: collapse;
th {
background-color: #ccc;
}
td,
th {
padding: 10px;
border: 1px solid #999;
text-align: center;
}
}
.exam-total {
font-size: 18px;
text-align: right;
padding: 0 40px;
}
.exam-welcome {
padding: 40px;
line-height: 30px;
......
......@@ -19,7 +19,7 @@
ref="ruleForm"
>
<el-form-item label="主题" prop="essay_name">
<el-input v-model="ruleForm.essay_name" placeholder="主题"></el-input>
<el-input v-model="ruleForm.essay_name" placeholder="主题" maxlength="50"></el-input>
</el-form-item>
<el-form-item label="正文" prop="essay_description">
<!-- 编辑器 -->
......@@ -108,11 +108,10 @@ export default {
},
rules: {
essay_name: [
{ required: true, message: '请输入主题', trigger: 'blur' },
{ max: 5, message: '最多输入 50 个字符', trigger: 'blur' }
{ required: true, message: '请输入主题', trigger: 'blur' }
],
essay_description: [
{ required: true, message: '请输入正文', trigger: 'blur' }
{ required: true, message: '请输入正文', trigger: 'change' }
],
url: [{ required: true, message: '请上传附件', trigger: 'change' }]
},
......@@ -159,19 +158,24 @@ export default {
},
// 提交
onSubmit() {
this.$refs.ruleForm
.validate()
.then(response => {
const params = Object.assign(this.ruleForm, {
semester_id: this.sid,
course_id: this.cid
})
this.handleSubmitRequest(params)
})
.catch(() => {
this.messageInstance && this.messageInstance.close()
this.messageInstance = this.$message.error('还有题目未做,不能提交')
})
this.messageInstance && this.messageInstance.close()
if (!this.ruleForm.essay_name) {
this.messageInstance = this.$message.error('请输入主题')
return
}
if (!this.ruleForm.essay_description) {
this.messageInstance = this.$message.error('请输入正文')
return
}
if (!this.ruleForm.url) {
this.messageInstance = this.$message.error('请上传附件')
return
}
const params = Object.assign(this.ruleForm, {
semester_id: this.sid,
course_id: this.cid
})
this.handleSubmitRequest(params)
},
// 请求提交接口
handleSubmitRequest(params) {
......@@ -231,5 +235,8 @@ p {
}
.paper-check-item {
display: flex;
b {
white-space: nowrap;
}
}
</style>
......@@ -2,8 +2,11 @@
<div class="q-item">
<div class="q-item-hd">
<div class="q-item-num">{{index + 1}}.</div>
<div class="q-item-title" v-html="data.content">{{data.title}}</div>
<div class="q-item-aside" v-if="typeText">({{typeText}})</div>
<div class="q-item-title" v-html="data.content"></div>
<div class="q-item-aside">
<template v-if="typeText">({{typeText}})</template>
<template v-if="data.hasOwnProperty('score')">({{data.score}}分)</template>
</div>
</div>
<div class="q-item-bd">
<!-- 单选 -->
......@@ -21,26 +24,44 @@
<!-- 简答题 -->
<template v-if="type === 3">
<v-editor v-model="currentValue.user_answer" :disabled="disabled"></v-editor>
<v-upload v-model="currentValue.attachments">请上传对应的文件附件:</v-upload>
<v-upload :disabled="disabled" v-model="currentValue.attachments">请上传对应的文件附件:</v-upload>
</template>
</div>
<div class="q-item-ft" v-if="disabled">
<template v-if="type === 3">
<p>
<span>老师评语:</span>
<p v-if="data.check_comment">
<span>评语:</span>
<span>{{data.check_comment}}</span>
</p>
</template>
<template v-else>
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
</p>
<p>
<span>正确答案:</span>
<span>{{correctAnswerText}}</span>
</p>
<div class="result">
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
</p>
<p>
<span>正确答案:</span>
<span>{{correctAnswerText}}</span>
</p>
</div>
</template>
<p v-if="data.hasOwnProperty('get_score')">
<span>评分:</span>
<span>{{data.get_score}}分</span>
</p>
<div class="analyze" v-if="data.analysis">
<span>解析:</span>
<div class="analyze-main">
<span style="color:blue;cursor:pointer;" @click="showAnalyze = !showAnalyze">查看解析</span>
<div
v-html="data.analysis"
v-if="data.analysis"
v-show="showAnalyze"
class="analyze-content"
></div>
</div>
</div>
</div>
</div>
</template>
......@@ -77,7 +98,8 @@ export default {
},
data() {
return {
currentValue: {}
currentValue: {},
showAnalyze: false
}
},
watch: {
......@@ -116,6 +138,14 @@ export default {
item.selected = Array.isArray(value)
? value.includes(item.id)
: value === item.id
// 处理正确的选中状态
const hasChecked = Object.prototype.hasOwnProperty.call(item, 'checked')
const rightAnswer = this.data.right_answer || ''
if (!hasChecked && rightAnswer) {
item.checked = Array.isArray(rightAnswer)
? rightAnswer.includes(item.id)
: rightAnswer === item.id
}
return item
})
},
......@@ -190,14 +220,16 @@ export default {
}
.q-item-title {
flex: 1;
padding: 0 10px;
::v-deep img {
max-width: 100%;
}
}
.q-item-aside {
padding-left: 20px;
// align-self: flex-end;
}
.q-option-item {
padding-left: 30px;
padding-left: 20px;
margin-bottom: 14px;
}
.is-success {
......@@ -229,13 +261,36 @@ export default {
}
}
.q-item-ft {
display: flex;
justify-content: flex-end;
padding: 10px 0;
p {
font-size: 14px;
margin: 0;
padding-left: 20px;
margin: 0 0 10px 0;
}
.result {
display: flex;
justify-content: flex-end;
p {
padding-left: 20px;
}
}
.analyze {
display: flex;
font-size: 14px;
}
.analyze-main {
flex: 1;
overflow: hidden;
}
.analyze-content {
margin-top: 10px;
background-color: #c9c9c97a;
border: 1px solid #c9c9c97a;
padding: 10px;
::v-deep * {
margin: 0;
padding: 0;
max-width: 100%;
}
}
}
</style>
......@@ -12,11 +12,11 @@
<script>
// componets
import ChapterWork from './chapterWork.vue'
import ChapterExam from './chapterExam.vue'
import ChapterTest from './chapterTest.vue'
export default {
name: 'ViewerWork',
components: { ChapterWork, ChapterExam },
components: { ChapterWork, ChapterTest },
props: {
// 当前选中的
chapter: {
......@@ -36,7 +36,7 @@ export default {
computed: {
currentCompoent() {
const componentNames = {
1: 'ChapterExam', // 考试
1: 'ChapterTest', // 课后测验
2: 'ChapterWork' // 作业
}
const homework = this.chapter.homework
......
......@@ -9,7 +9,7 @@
<h1 class="course-viewer-main-hd__title">{{ detail.course_name }}</h1>
<!-- 直播的时候显示帮助按钮 -->
<template v-if="isLive">
<router-link to="/app/account/feedbackCreate" target="_blank">
<router-link to="/app/feedback/feedback-create" target="_blank">
<el-tooltip effect="light" content="意见反馈">
<i class="el-icon-self-fankuiyijian"></i>
</el-tooltip>
......@@ -35,6 +35,7 @@
</div>
<!-- 侧边栏 -->
<v-aside
:data="detail"
:chapters="chapters"
:active="activeChapter"
:ppts="ppts"
......@@ -92,10 +93,17 @@ export default {
children: [
{ name: '课程大作业', id: 'course_work', type: 99 },
{ name: '课程资料', id: 'course_info', type: 100 },
{ name: '教学评估', id: 'teach_evaluation', type: 102 },
{ name: '课程考试', id: 'course_exam', type: 101 }
{ name: '教学评估', id: 'teach_evaluation', type: 102 }
]
}
// 课程考试
if (this.detail.course_examination) {
customeChapter.children.push({
name: '课程考试',
id: 'course_exam',
type: 101
})
}
chapters.push(customeChapter)
return chapters
},
......
......@@ -283,7 +283,9 @@ export default {
},
// 新增
golearningAdd(url) {
this.$router.push({ path: url, query: { id: this.affairId } })
if (this.affairId) {
this.$router.push({ path: url, query: { id: this.affairId } })
}
},
// 列表接口请求之前
tableListbeforeRequest(params) {
......@@ -291,7 +293,7 @@ export default {
return params
}
},
beforeMount() {
created() {
this.getTapData()
}
}
......
......@@ -193,7 +193,7 @@ export default {
},
/* 直接进直播 */
goLive () {
this.$router.push({ path: `/player/${this.newLiveMsg.semester_id}/${this.newLiveMsg.course_id}/live/${this.newLiveMsg.live.id}` })
this.$router.push({ name: 'viewerCourseChapter', params: { sid: this.newLiveMsg.semester_id, cid: this.newLiveMsg.course_id, id: this.newLiveMsg.chapter_id } })
}
}
}
......
......@@ -33,7 +33,6 @@
<div class="live-item-content__time">{{subitem.start_time}}</div>
<div
class="live-item-content__status"
v-if="!(subitem.live_status === 103 && !subitem.enable_record)"
>{{calcTimeText(subitem.start_time, subitem.live_status)}}</div>
</div>
</div>
......@@ -133,7 +132,7 @@ export default {
start_time: liveTime
} = data
let message = this.calcTimeText(liveTime, liveStatus)
if (liveStatus === 103 && data.enable_record !== 1) {
if (liveStatus === 2 && !data.enable_record) {
message = this.$t('live.noPlayback')
}
if (liveType === 'cloud') {
......@@ -155,7 +154,7 @@ export default {
if (liveStatus === 1) {
// 进行中
this.openNewWindow(data.join_url)
} else if (liveStatus === 103) {
} else if (liveStatus === 2) {
// 查看回放
this.openNewWindow(data.record_url)
} else {
......@@ -173,7 +172,7 @@ export default {
// 进行中
const url = `http://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.username}&viewertoken=${data.password}`
this.openNewWindow(url)
} else if (liveStatus === 103) {
} else if (liveStatus === 2) {
// 查看回放
const replayUrl = data.record_url.replayUrl
const url = replayUrl
......@@ -194,11 +193,11 @@ export default {
// 进行中
const url = `https://view.csslcloud.net/api/view/index?roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.viewer_name}&viewertoken=${data.viewer_token}`
this.openNewWindow(url)
} else if (liveStatus === 103 && data.enable_record === 1) {
} else if (liveStatus === 2 && data.enable_record === 1) {
// enable_record 0:不启用回放 1:开启回放
// 查看回放
const url = `https://view.csslcloud.net/api/view/callback?recordid=${data.record_id}&roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.viewer_name}&viewertoken=${data.viewer_token}`
this.openNewWindow(url)
// const url = `https://view.csslcloud.net/api/view/callback?recordid=${data.record_id}&roomid=${data.room_id}&userid=${data.user_id}&autoLogin=true&viewername=${data.viewer_name}&viewertoken=${data.viewer_token}`
this.openNewWindow(data.record_url)
} else {
this.message && this.message.close()
this.message = this.$message({ type: 'warning', offset: 0, message })
......@@ -222,10 +221,7 @@ export default {
const map = {
0: this.$t('live.notStarted'),
1: this.$t('live.liveStreaming'),
2: this.$t('live.liveEnd'),
101: this.$t('live.liveEndNotVideo'),
102: this.$t('live.liveEndNotVideo'),
103: this.$t('live.watchReplay')
2: this.$t('live.liveEnd')
}
let result = map[liveStatus] || liveTime
......
......@@ -158,7 +158,7 @@ export default {
let str = ''
let stuAnswer = '' // 学生答案
let stuIsCorrect = 0 // 学生是否答对
let _json = JSON.parse(_.question_options)
let _json = _.question_options ? JSON.parse(_.question_options) : []
_json.forEach(function (__, j) {
if (__.checked) {
switch (j) {
......
......@@ -18,6 +18,7 @@
<div class="play-content">
<router-view
ref="comTotalChapter"
:chapters="rawResponse.chapters"
:chapterName="curChapterName"
:chapterId="chapterId"
:courseInfo="courseInfo"
......@@ -33,6 +34,7 @@
@updateProgress="updateProgress"
@changeSideBar="changeSideBar"
:key="id"
v-if="rawResponse.chapters"
></router-view>
</div>
</div>
......@@ -151,7 +153,8 @@ export default {
chapterExam: {},
/* 章节视频 */
chapterVideo: {},
chapterPpts: []
chapterPpts: [],
rawResponse: {} // 接口返回的数据
}
},
beforeRouteUpdate (to, from, next) {
......@@ -178,6 +181,7 @@ export default {
return
}
cAction.Player.getChapterList(to.params.cid, to.params.sid, to.params.id).then(json => {
this.rawResponse = json.rawResponse
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
......@@ -219,6 +223,7 @@ export default {
return
}
cAction.Player.getChapterList(this.cid, this.sid, this.id).then(json => {
this.rawResponse = json.rawResponse
this.chapterList = json.json
this.courseInfo = json.courseInfo
this.courseWork = json.courseWork
......
......@@ -51,8 +51,8 @@ export default {
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'
if (this.live.live_status === 2 && this.live.enable_record && this.live.record_url) {
this.live.url = this.live.record_url
} else {
this.$emit('changeSideBar', '')
setTimeout(() => {
......
......@@ -70,16 +70,14 @@ export default {
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) {
if (status === 2 && enableRecord) {
this.$message.info('该直播没有回放')
return
}
this.$router.push({ path: `/player/${sid}/${cid}/live/${_id}` })
} else if (_course.chapters[i2].type === 9) {
this.$router.push({ path: `/player/${sid}/${cid}/chapter-exam2/${_course.chapters[i2].chapterId}` })
}
return
}
......
// import viewerRoutes from '@/modules/viewer/routes.js'
import viewerRoutes from '@/modules/viewer/routes.js'
export default [
{ path: '/', redirect: '/app/learn/course' },
......@@ -201,6 +201,12 @@ export default [
component: () => import('@/pages/player/chapterExam/chapterExam.vue'),
props: true
},
{
path: 'chapter-exam2/:id',
name: 'chapterExam2',
component: () => import('@/pages/player/chapterExam/chapterExam2.vue'),
props: true
},
{
path: 'chapter-read/:id',
name: 'chapterRead',
......@@ -285,7 +291,7 @@ export default [
// /* survey-phone 内未找到页面时 - 指向 */
// { path: '/survey-phone/*', redirect: '/learn-error/learn-error' },
/* 如果所有页面都没找到 - 指向 */
{ path: '*', component: () => import('@/components/errorPages/404.vue') }
{ path: '*', component: () => import('@/components/errorPages/404.vue') },
// viewer module routes
// ...viewerRoutes
...viewerRoutes
]
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论