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

update viewer module

上级 d69faf21
......@@ -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) => {
......@@ -173,35 +187,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
})
}
/* 课程考核 考核标准文案读取 */
......
......@@ -35,6 +35,27 @@ 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
......@@ -130,8 +151,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) {
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 {
......
<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,6 +15,17 @@
<i class="el-icon-document"></i>
{{ fileUrl | fileName }}
</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>
......@@ -23,6 +34,7 @@
</div>
</div>
</div>
</div>
</template>
<script>
......@@ -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) {
value: {
immediate: true,
handler(value) {
if (value) {
this.fileList = Array.isArray(value) ? value : [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 : '')
}
}
}
......
......@@ -13,6 +13,7 @@
// components
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' // 课程考试
......
......@@ -7,15 +7,18 @@
:isSkip="isSkip"
: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 +33,8 @@
</template>
<script>
import Cookies from 'js-cookie'
import { throttle } from 'lodash'
// api
import * as api from '../../api'
// components
......@@ -46,11 +51,23 @@ 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,
chatperResources: null,
throttled: null,
throttleWait: 10, // 秒
progress: {
cpt: 0, // 当前播放时间
mpt: 0, // 视频时长
progress: 0, // 进度
pt: 0 // 累计播放时间
},
player: null,
watchedTimePoint: [] // 视频观看的时间点
}
},
watch: {
......@@ -59,6 +76,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,11 +120,29 @@ 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)
}
},
// 当前播放时间更新
onTimeupdate(time) {
const ppts = this.chatperResources.ppts || []
......@@ -108,12 +151,29 @@ export default {
)
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 +185,66 @@ 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 = []
}
},
beforeMount() {
// 获取视频
this.getChapterVideo()
// 获取视频进度
this.getChapterVideoProgress()
}
}
</script>
......@@ -148,6 +260,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>
<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,22 +42,23 @@ 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) {
if (this.isSync) {
this.currentIndex = value
}
}
}
},
computed: {
pptUrl() {
......@@ -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,13 +13,22 @@ 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,
controlBarVisibility: 'always',
components: [
......@@ -30,6 +39,11 @@ export default {
]
},
function(player) {
player.on('ready', function() {
// 跳过片头
_this.isSkip && player.seek(6)
_this.$emit('ready', player)
})
player.on('sourceloaded', function(params) {
const paramData = params.paramData
const desc = paramData.desc
......
......@@ -6,7 +6,7 @@
<i class="el-icon-document"></i>
{{ file.file_name }}
</a>
<span v-if="file.file_size">{{ file.file_size }}</span>
<!-- <span v-if="file.file_size">{{ file.file_size }}K</span> -->
<a :href="file.file_url" :download="file.file_name" target="_blank">
<el-tooltip effect="dark" content="下载">
<i class="el-icon-download"></i>
......
<template>
<container :title="chapter.name" v-loading="loading">
<template v-slot:header-aside v-if="isSubmited">正确率:{{detail.score}}%</template>
<container :title="detail.paper_title" v-loading="loading">
<template v-slot:header-aside v-if="isExamComplete">分数:{{exam.score.total}}</template>
<div class="exam">
<div class="exam-form">
<template v-if="isSubmited && !isExamComplete">
<div class="no-exam">试卷批改中,请耐心等待</div>
</template>
<template v-else>
<!-- 考试期间,未开始考试 -->
<div class="exam-welcome" v-if="!isStartExam">
<div v-if="detail.paper_deadline">考试截止时间:{{detail.paper_deadline}}</div>
<el-button
type="primary"
:disabled="!isExamTime"
@click="onStartExam"
>{{startExamButtonText}}</el-button>
</div>
<!-- 考试试题 -->
<div class="exam-form" v-if="isStartExam">
<el-form :disabled="isSubmited">
<template v-for="items in questions">
<exam-item
v-for="(item, index) in unorderedQuestions"
v-for="(item, index) in items"
:index="index"
:type="item.question_type"
: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>
......@@ -20,13 +36,14 @@
</div>
</el-form>
</div>
</template>
</div>
</container>
</template>
<script>
// libs
import { shuffle } from 'lodash'
import Base64 from 'Base64'
// components
import Container from '../common/container.vue'
import ExamItem from './examItem.vue'
......@@ -44,23 +61,33 @@ export default {
default() {
return {}
}
},
// 课程详情接口返回的数据
data: {
type: Object,
default() {
return {}
}
}
},
data() {
return {
loading: false,
detail: null,
questions: [], // 问题列表
startTime: new Date().getTime(), // 进入时间
messageInstance: null
detail: {},
questions: [],
messageInstance: null,
exam: {},
isStartExam: false, // 是否开始考试
autoSubmitTimer: null // 自动提交定时器
}
},
watch: {
chapter: {
immediate: true,
handler(data) {
this.questions = data.homework
? this.genQuenstions(data.homework.questions)
this.detail = data.paper
this.questions = data.paper
? this.genQuestions(data.paper.examination)
: []
}
}
......@@ -78,19 +105,28 @@ export default {
pid() {
return this.$route.params.id
},
// 资源ID
resourceId() {
return this.chapter.resource_id
// 是否是考试时间
isExamTime() {
if (!this.detail.paper_deadline) {
return true
}
// 大于开始时间,小于结束时间
const endTime = +new Date(this.exam.paper_deadline)
const currentTime = new Date().getTime()
return currentTime < endTime
},
// 打乱顺序的问题列表
unorderedQuestions() {
const ids = this.questions.map(item => item.id)
const sortIds = shuffle(ids)
return sortIds.map(id => this.questions.find(item => item.id === id))
// 考试按钮
startExamButtonText() {
return this.isExamTime ? '开始考试' : '考试结束'
},
// 考试完成
isExamComplete() {
// 考试完成,批改完成并且公布成绩
return this.exam.is_published === 1 && this.exam.type === 2
},
// 是否提交
isSubmited() {
return this.detail ? !!this.detail.work_contents : false
return this.exam.type === 1 || this.exam.type === 2
},
// 提交按钮文本
submitText() {
......@@ -98,88 +134,80 @@ export default {
}
},
methods: {
// 获取测试答题详情
getDetail() {
this.loading = true
api
.getChapterExam(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
})
// 开始考试
onStartExam() {
this.isStartExam = true
// 自动提交答题
this.autoSubmit()
},
// 组装问题数据
genQuenstions(list) {
genQuestions(list) {
if (!list) {
return []
}
return list.map(item => {
let temp = null
if (item.question_type === 1) {
return list.map(data => {
let { radioList, checkboxList, shortAnswerList } = data
// 单选
temp = {
radioList = radioList.map(item => {
const temp = {
type: 1,
formModel: { id: item.id, user_answer: item.user_answer || '' }
}
} else if (item.question_type === 2) {
return Object.assign({}, item, temp)
})
// 多选
temp = {
checkboxList = checkboxList.map(item => {
const temp = {
type: 2,
formModel: { id: item.id, user_answer: item.user_answer || [] }
}
} else if (item.question_type === 3) {
// 简答
temp = {
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)
? Base64.decode(item.user_answer.replace(/ /gi, '+'))
: '',
attachments: item.attachments || ''
attachments: item.attachments || []
}
}
}
return Object.assign(
{},
item,
{
content: item.question_content,
options: item.question_options
? JSON.parse(item.question_options)
: []
return Object.assign({}, item, temp)
})
return [...radioList, ...checkboxList, ...shortAnswerList]
})
},
temp
)
// 获取考试结果
getExamResult() {
api
.getCourseExamResult(this.sid, this.cid, this.pid, { paper_type: 0 })
.then(response => {
// 设置问题列表数据
if (response.code !== 8001) {
this.isStartExam = true
this.exam = response
this.questions = this.genQuestions(response.sheet)
// 自动提交
if (this.isStartExam && !this.isSubmited && !this.isExamComplete) {
this.autoSubmit()
}
}
})
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
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
},
// 提交
......@@ -190,74 +218,80 @@ export default {
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)
}
// 提交的答案数据
const answers = this.handleSubmitData()
// 提交参数
const params = { answers: JSON.stringify(answers), type: 1 }
// 请求接口
this.handleSubmitRequest(params)
},
// 自动提交
autoSubmit() {
// 10秒提交一次
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.autoSubmitTimer = setInterval(() => {
// 提交的答案数据
const answers = this.handleSubmitData()
const params = { answers: JSON.stringify(answers), type: 0 }
// 请求接口
this.handleSubmitRequest(params)
}, 10000)
},
// 处理请求接口答案数据
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 this.questions.map(questions => {
return questions.reduce(
(result, item) => {
// 单选题
if (item.type === 1) {
result.radioList.push(item.formModel)
}
return {
id: option.id,
checked: option.checked,
option: option.option,
selected
// 多选题
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)
})
return {
question_id: item.id,
is_correct: isCorrect ? 1 : 0,
options
result.shortAnswerList.push(formModel)
}
})
return result
},
{ radioList: [], checkboxList: [], shortAnswerList: [] }
)
})
},
// 请求提交接口
handleSubmitRequest(params) {
api.sbumitChapterExam(params).then(response => {
if (response.status) {
this.getDetail()
params.paper_type = 0
api
.submitCourseExam(this.sid, this.cid, this.pid, params)
.then(response => {
if (params.type === 0) {
console.log('暂存成功')
return
}
if (response.code === 200) {
this.$message.success('考试答卷提交成功')
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.getExamResult()
} else {
this.$message.error(response.data.error)
}
})
.catch(error => {
this.$message.error(error.message)
})
}
},
beforeMount() {
this.getDetail()
// 获取考试结果
this.getExamResult()
},
destroyed() {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
}
}
</script>
......@@ -271,4 +305,17 @@ export default {
margin: 40px auto;
}
}
.no-exam {
padding: 100px;
font-size: 30px;
text-align: center;
}
.exam-welcome {
padding: 40px;
line-height: 30px;
text-align: center;
::v-deep .el-button {
margin-top: 30px;
}
}
</style>
<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
.getChapterExam(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.sbumitChapterExam(params).then(response => {
if (response.status) {
this.getDetail()
} else {
this.$message.error(response.data.error)
}
})
}
},
beforeMount() {
this.getDetail()
}
}
</script>
<style lang="scss" scoped>
.exam-buttons {
padding: 40px 0;
text-align: center;
.el-button {
width: 240px;
margin: 40px auto;
}
}
</style>
......@@ -214,12 +214,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
}
......
<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,32 +18,12 @@
@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">
<template v-for="items in questions">
<exam-item
v-for="(item, index) in questions"
v-for="(item, index) in items"
:index="index"
:type="item.type"
:data="item"
......@@ -50,6 +31,7 @@
: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,10 +147,11 @@ export default {
})
},
// 组装问题数据
genQuenstions(data) {
if (!data) {
return
genQuestions(list) {
if (!list) {
return []
}
return list.map(data => {
let { radioList, checkboxList, shortAnswerList } = data
// 单选
radioList = radioList.map(item => {
......@@ -200,14 +176,15 @@ export default {
formModel: {
id: item.id,
user_answer: item.user_answer
? Base64.decode(item.user_answer)
? Base64.decode(item.user_answer.replace(/ /gi, '+'))
: '',
attachments: []
attachments: item.attachments || []
}
}
return Object.assign({}, item, temp)
})
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,13 +221,15 @@ export default {
},
// 提交校验
checkSubmit() {
const quenstions = this.questions
for (let i = 0; i < quenstions.length; i++) {
const value = quenstions[i].formModel.user_answer
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;
......
......@@ -109,7 +109,7 @@ export default {
rules: {
essay_name: [
{ required: true, message: '请输入主题', trigger: 'blur' },
{ max: 5, message: '最多输入 50 个字符', trigger: 'blur' }
{ max: 50, message: '最多输入 50 个字符', trigger: 'blur' }
],
essay_description: [
{ required: true, message: '请输入正文', trigger: 'blur' }
......
......@@ -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,17 +24,18 @@
<!-- 简答题 -->
<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>
<span>评语:</span>
<span>{{data.check_comment}}</span>
</p>
</template>
<template v-else>
<div class="result">
<p>
<span>学生答案:</span>
<span :class="isCorrect ? 'is-success' : 'is-error'">{{submitAnswerText}}</span>
......@@ -40,7 +44,24 @@
<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 {
padding: 10px 0;
p {
font-size: 14px;
margin: 0 0 10px 0;
}
.result {
display: flex;
justify-content: flex-end;
padding: 10px 0;
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-left: 20px;
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
},
......
......@@ -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 } })
}
}
}
......
......@@ -4,11 +4,11 @@
<div class="detail-box">
<div class="box-thd">
<div class="title" @click="noWantThisCourse">{{headerInfo.title}}
<template v-if='headerInfo.isStart && tabs[1].chapterList.currentChapterId'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='cid' :data-sid='sid' :data-type='tabs[1].chapterList.currentVideoProvider' :data-vid='tabs[1].chapterList.currentChapterId'>继续学习</el-button>
<template v-if='headerInfo.isStart && tabs[1].chapterList.currentChapter'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn(tabs[1].chapterList.currentChapter)'>继续学习</el-button>
</template>
<template v-else-if='headerInfo.isStart'>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn' :data-cid='cid' :data-sid='sid' :data-type='firstVideo.video_provider' :data-vid='firstVideo.vid'>开始学习</el-button>
<el-button class="rbtn" type="primary" size="mini" @click='startLearn(firstVideo)'>开始学习</el-button>
</template>
<template v-else>
<el-button class="rbtn" type="primary" size="mini" @click='wantThisCourse'>选课</el-button>
......@@ -43,12 +43,12 @@
<div class='course-list'>
<template v-for="(_item, index) in tabs[1].chapterList.course">
<div v-bind:key="index" :class='["content-group", (!_item.chapters.length ? "no-child" : ""), (_item.isUp ? "up" : "")]'>
<div class='title' @click='clickJumpOrStatus($event)' :data-index='index' :data-cid='_item.cid' :data-sid='_item.sid' :data-status='!!_item.chapters.length'>{{_item.title}}
<div class='title' @click='clickJumpOrStatus(index, _item)'>{{_item.title}}
<i :class="['side', (_item.chapters.length ? '' : 'none'), (_item.isUp ? 'el-icon-arrow-down' : 'el-icon-arrow-up')]"></i>
</div>
<template v-for="(item1, index1) in _item.chapters">
<div v-bind:key="index1" :class='["body", (item1.id === tabs[1].chapterList.currentChapterId && "on")]'>
<div class='name' :data-vid='item1.vid' :data-cid='item1.cid' :data-sid='item1.sid' :data-hasVA='item1.time' :data-type="item1.video_provider" :data-name='item1.name' :data-index='index' :data-count='index1' @click='jumpToOtherVA'>
<div class='name' :data-vid='item1.vid' :data-cid='item1.cid' :data-sid='item1.sid' :data-hasVA='item1.time' :data-type="item1.video_provider" :data-name='item1.name' :data-index='index' :data-count='index1' @click='jumpToOtherVA(item1)'>
{{item1.name}}
<template v-if='item1.type === 5'>
<div class='time'>{{ item1.live.start_time }} {{ item1.live.statusStr }}</div>
......@@ -74,28 +74,6 @@
<div :class='["item-order", (sort[1].isShow ? "on" : "")]' @click='sortFn' :data-index='1' :data-str='sort[1].str'>按投票排序</div>
</div>
<div class='discuss-scroll' bindscrolltolower='loadmore' bindscrolltoupper='updatenew'>
<!-- <template v-for='(item, index) in discussList'>
<div v-bind:key="index" class='item-list' @click='goDiscussDetail' :data-id='item.id' :data-sid='item.sid' :data-index='index'>
<div class='user'>
<template v-if="item.user.url">
<img class='img' :src='item.user.url' />
</template>
<template v-else>
<img class='img' src='@/assets/images/person-default.jpg' />
</template>
<div class='right'>
<div class='name'>{{item.user.name}}</div>
<div class='time'>{{item.user.time}}</div>
</div>
</div>
<div class='title'>{{item.title}}</div>
<div :class='["text"]' v-html="item.text"></div><div :class='["ellipsis", (item.isShow ? "on" : "")]'>....</div>
<div class='result'>{{item.askCnt}} 回答<div style='display: inline-block; width: 20px;'></div>{{item.TouCnt}} 投票</div>
</div>
</template>
<template v-if='!discussList.length'>
<div class='no-data'>暂无相关讨论</div>
</template> -->
<discuss :params="params"></discuss>
</div>
</template>
......@@ -109,7 +87,6 @@
<el-form-item label="标题" prop="title">
<el-input v-model="publish.title" type="text" placeholder="请输入标题"></el-input>
</el-form-item>
<!-- v-model="publish.content" -->
<div style="line-height: 1.5; font-size: 0.16rem; margin-bottom: 0.2rem;">正文内容</div>
<textarea id="editor"></textarea>
<div style="height: 0.2rem;"></div>
......@@ -146,7 +123,7 @@
<div class='tt'>{{item1.title}}</div>
<template v-for='(item2, index) in item1.arr'>
<div v-bind:key="index" class='rd'>
<div class='col3-td1' :data-sid='item1.sid' :data-cid='item1.cid' :data-vid='item2.vid' :data-type='item2.type' :data-duration='item2.duration' @mousedown="jumpVAOrfinishVA($event)">{{item2.name}}</div>
<div class='col3-td1' :data-sid='item1.sid' :data-cid='item1.cid' :data-vid='item2.vid' :data-id='item2.id' :data-type='item2.type' :data-duration='item2.duration' @mousedown="jumpVAOrfinishVA($event)">{{item2.name}}</div>
<div class='col3-td2'>{{item2.time}}</div>
<div class='col3-td3'>{{item2.progress}}</div>
</div>
......@@ -214,8 +191,6 @@
</el-row>
</div>
</div>
<!-- v-model="setArtContent.content" -->
<!-- <textarea id="editor"></textarea> -->
</template>
<script>
......@@ -375,7 +350,6 @@ export default {
this.tabs[0].content = json.tabs0Content
this.tabs[1].chapterList = json.tabs1ChapterList
json.tabs3richTest && (this.tabs[3].richText = json.tabs3richTest)
// 设置开始学习的视频
const courseList = json.tabs1ChapterList.course
for (let i = 0; i < courseList.length; i++) {
......@@ -485,41 +459,28 @@ export default {
/**
* 课程内容 - 列表展开或者跳转
*/
clickJumpOrStatus (e) {
const data = e.currentTarget.dataset
const flag = data.status
clickJumpOrStatus (index, data) {
const flag = !!data.chapters.length
if (flag) {
const index = data.index
const json = this.tabs
const temp = json[1].chapterList.course[index]
temp.isUp = !temp.isUp
} else {
/* 进入详情页,不管是哪个,都存localstorage */
window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
const course = this.tabs[1].chapterList.course[data.index]
const sid = data.sid
const cid = data.cid
if (course.type === 'course_info') {
this.$router.push({ path: `/player/${sid}/${cid}/course-info/course_info` })
} else if (course.type === 'course_work') {
if (!this.headerInfo.survey) {
const { sid, cid } = data
// 课程大作业
if (data.id === 'course_work' && !this.headerInfo.survey) {
this.$message('请先填写教学评估,然后完成大作业。')
return false
return
}
this.$router.push({ path: `/player/${sid}/${cid}/course-work/course_work` })
} else if (course.type === 'teach_evaluation') {
/* 暂时 不增加 手机端 */
// let w = document.documentElement.clientWidth
this.$router.push({ path: `/survey/${sid}/${cid}` })
// if (w > 767) {
// this.$router.push({ path: `/survey/${sid}/${cid}` })
// } else {
// this.$router.push({ path: `/survey-phone/${sid}/${cid}` })
// }
} else if (course.type === 'exam') {
this.$router.push({ path: `/player/${sid}/${cid}/exam/${course.examId}` })
// 教学评估
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: { sid, cid, id: data.id } })
}
},
/* 直接跳转打开新页面 */
......@@ -561,84 +522,52 @@ export default {
/**
* 跳转到对应音视频播放页
*/
jumpToOtherVA (e) {
jumpToOtherVA (data) {
/* 如果未选课,不能查看课程内容 */
if (!this.headerInfo.isStart) {
this.$message.error('先选课,才能学习')
return
}
let _data = e.target.dataset
if (!/name/gi.test(e.target.className)) {
_data = e.target.parentElement.dataset
}
const sid = _data.sid
const cid = _data.cid
const _id = _data.vid
const type = _data.type
if (!_data.hasva) {
const { sid, cid, vid, type } = data
/* 进入详情页,不管是哪个,都存localstorage */
window.localStorage.setItem('headerInfo', JSON.stringify(this.headerInfo))
/* 如果存在 - 课后习题类型(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.tabs[1].chapterList.course[i1]
if (_course && _course.chapters[i2]) {
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].type === 5) {
const status = _course.chapters[i2].live.live_status
if (type === 1) {
return
}
// 直播
if (type === 5) {
const live = data.live
const status = live.live_status
if (status !== 0 && status !== 1 && status !== 103) {
this.$message.error(_course.chapters[i2].live.statusStr)
this.$message.error(live.statusStr)
return
}
const enableRecord = _course.chapters[i2].live.enable_record
const enableRecord = live.enable_record
if (status === 103 && enableRecord !== undefined && enableRecord !== null && !enableRecord) {
this.$message.info('该直播没有回放')
return
}
/* 判别如果为 云课堂记录 id 则直接进入 云课堂 */
if (this.cloudClassUrls[_id]) {
if (this.cloudClassUrls[vid]) {
const viewerName = window.G.UserInfo.student_info.personal_name || window.G.UserInfo.nickname
const url = this.cloudClassUrls[_id] + '&viewername=' + viewerName + '&autoLogin=true'
const url = this.cloudClassUrls[vid] + '&viewername=' + viewerName + '&autoLogin=true'
window.open(url)
return
}
// 新窗口打开
if (this.isOpenNewTabFlag) {
this.openNewTab(sid, cid, _id)
} else {
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].id}` })
}
this.openNewTab(sid, cid, vid)
return
}
this.$message.error('点击频率过快,系统反应不过来,请稍后再试,003')
return
}
this.$router.push({ path: `/player/${sid}/${cid}/chapter-video/${_id}/${type}` })
this.$router.push({ name: 'viewerCourseChapter', params: { sid, cid, id: data.id } })
},
/**
* 开始学习或继续学习 - 跳转到对应音视频播放页
*/
startLearn (e) {
const _data = e.currentTarget.dataset
const sid = _data.sid
const cid = _data.cid
const vid = _data.vid
const type = _data.type
if (vid && type !== '') {
this.$router.push({ path: `/player/${sid}/${cid}/chapter-video/${vid}/${type}` })
startLearn (data) {
if (data.id) {
this.$router.push({ name: 'viewerCourseChapter', params: { sid: this.sid, cid: this.cid, id: data.id } })
} else {
this.$message.error('当前暂无点播课程')
}
......@@ -678,11 +607,11 @@ export default {
const _cid = data.cid
const _vid = data.vid
const _duration = data.duration
const type = data.type
const _id = data.id
/* 字母 o */
if (e.keyCode === 79) {
/* 直接跳转 进入 继续学习 */
this.$router.push({ path: `/player/${_sid}/${_cid}/chapter-video/${_vid}/${type}` })
this.$router.push({ name: 'viewerCourseChapter', params: { sid: _sid, cid: _cid, id: _id } })
}
/* 字母 f */
if (e.keyCode === 70) {
......
// import viewerRoutes from '@/modules/viewer/routes.js'
import viewerRoutes from '@/modules/viewer/routes.js'
export default [
{ path: '/', redirect: '/app/learn/course' },
......@@ -291,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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论