Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
L
learn-online-pc
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
learn-online-pc
Commits
5632ae6b
提交
5632ae6b
authored
6月 16, 2020
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
差异文件
merge...
上级
64bdc527
14316cb5
显示空白字符变更
内嵌
并排
正在显示
40 个修改的文件
包含
2046 行增加
和
903 行删除
+2046
-903
CourseAction.js
client/src/action/CourseAction.js
+28
-13
PlayerAction.js
client/src/action/PlayerAction.js
+19
-16
player_api.js
client/src/api/player_api.js
+1
-1
Before.js
client/src/components/beforeEnter/Before.js
+1
-1
editor.vue
client/src/components/editor.vue
+2
-2
index.html
client/src/index.html
+0
-1
api.js
client/src/modules/viewer/api.js
+39
-5
chapter.vue
client/src/modules/viewer/components/aside/chapter.vue
+28
-30
index.vue
client/src/modules/viewer/components/aside/index.vue
+9
-2
lecture.vue
client/src/modules/viewer/components/aside/lecture.vue
+2
-2
editor.vue
client/src/modules/viewer/components/common/editor.vue
+2
-3
fileList.vue
client/src/modules/viewer/components/common/fileList.vue
+0
-70
upload.vue
client/src/modules/viewer/components/common/upload.vue
+30
-4
layout.vue
client/src/modules/viewer/components/layout.vue
+4
-1
chapterLive.vue
client/src/modules/viewer/components/live/chapterLive.vue
+7
-4
chapterPlayer.vue
...nt/src/modules/viewer/components/player/chapterPlayer.vue
+150
-7
pptPlayer.vue
client/src/modules/viewer/components/player/pptPlayer.vue
+18
-11
videoPlayer.vue
client/src/modules/viewer/components/player/videoPlayer.vue
+21
-14
chapterRead.vue
client/src/modules/viewer/components/read/chapterRead.vue
+1
-1
courseRead.vue
client/src/modules/viewer/components/read/courseRead.vue
+1
-1
fileList.vue
client/src/modules/viewer/components/read/fileList.vue
+9
-2
chapterExam.vue
client/src/modules/viewer/components/work/chapterExam.vue
+173
-126
chapterTest.vue
client/src/modules/viewer/components/work/chapterTest.vue
+279
-0
chapterWork.vue
client/src/modules/viewer/components/work/chapterWork.vue
+111
-18
courseExam.vue
client/src/modules/viewer/components/work/courseExam.vue
+48
-76
courseWork.vue
client/src/modules/viewer/components/work/courseWork.vue
+19
-12
examItem.vue
client/src/modules/viewer/components/work/examItem.vue
+65
-10
index.vue
client/src/modules/viewer/components/work/index.vue
+3
-3
index.vue
client/src/modules/viewer/index.vue
+11
-3
hall.vue
client/src/pages/affairsHall/hall.vue
+3
-1
course.vue
client/src/pages/learn/course.vue
+1
-1
courseDetail.vue
client/src/pages/learn/courseDetail.vue
+39
-112
list.vue
client/src/pages/mobileLive/list.vue
+7
-11
chapterExam.vue
client/src/pages/player/chapterExam/chapterExam.vue
+1
-1
chapterExam2.vue
client/src/pages/player/chapterExam/chapterExam2.vue
+776
-0
exam.vue
client/src/pages/player/exam/exam.vue
+118
-327
index.vue
client/src/pages/player/index.vue
+6
-1
live.vue
client/src/pages/player/live/live.vue
+2
-2
sideChapterList.vue
client/src/pages/player/rightSide/sideChapterList.vue
+3
-5
routes.js
client/src/router/routes.js
+9
-3
没有找到文件。
client/src/action/CourseAction.js
浏览文件 @
5632ae6b
...
...
@@ -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
})
}
/* 课程考核 考核标准文案读取 */
...
...
client/src/action/PlayerAction.js
浏览文件 @
5632ae6b
...
...
@@ -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,19 +290,17 @@ export default class PlayerAction extends BaseACTION {
exam
.
id
=
_res
.
id
exam
.
title
=
_res
.
title
exam
.
score
=
{}
exam
.
radioList
=
_res
.
examination
.
radioList
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
}
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
...
...
@@ -331,11 +328,13 @@ export default class PlayerAction extends BaseACTION {
}
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,24 +344,26 @@ 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
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
=
_res
.
sheet
.
checkboxList
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
=
[]
// 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
=
_res
.
sheet
.
shortAnswerList
exam
.
shortAnswerList
=
exam
.
shortAnswerList
||
[]
for
(
let
i
=
0
;
i
<
exam
.
shortAnswerList
.
length
;
i
++
)
{
tmp
=
exam
.
shortAnswerList
[
i
]
tmp
.
user_answer
=
Base64
.
decode
(
tmp
.
user_answer
.
replace
(
/ /gi
,
'+'
))
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'
,
...
...
@@ -387,6 +388,8 @@ export default class PlayerAction extends BaseACTION {
}
return
exam
})
return
exam
})
}
/* 获取考试状态 */
...
...
client/src/api/player_api.js
浏览文件 @
5632ae6b
...
...
@@ -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
...
...
client/src/components/beforeEnter/Before.js
浏览文件 @
5632ae6b
...
...
@@ -14,7 +14,7 @@ export default class Before {
'studentHelp'
,
'teacherHelp'
]
this
.
isMobile
=
/android|iphone|ip
ad|ip
od/i
.
test
(
UA
)
this
.
isMobile
=
/android|iphone|ipod/i
.
test
(
UA
)
}
async
update
(
to
,
from
,
next
)
{
...
...
client/src/components/editor.vue
浏览文件 @
5632ae6b
...
...
@@ -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
(
...
...
client/src/index.html
浏览文件 @
5632ae6b
...
...
@@ -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>
...
...
client/src/modules/viewer/api.js
浏览文件 @
5632ae6b
...
...
@@ -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
getChapter
Exam
(
semesterId
,
courseId
,
resourseId
)
{
export
function
getChapter
Homework
(
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
sbumitChapter
Exam
(
params
)
{
export
function
sbumitChapter
Homework
(
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
)
}
client/src/modules/viewer/components/aside/chapter.vue
浏览文件 @
5632ae6b
<
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
}
...
...
client/src/modules/viewer/components/aside/index.vue
浏览文件 @
5632ae6b
...
...
@@ -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
:
()
=>
[]
},
// 讲义
...
...
client/src/modules/viewer/components/aside/lecture.vue
浏览文件 @
5632ae6b
<
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
{
...
...
client/src/modules/viewer/components/common/editor.vue
浏览文件 @
5632ae6b
...
...
@@ -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
(
...
...
client/src/modules/viewer/components/common/fileList.vue
deleted
100644 → 0
浏览文件 @
64bdc527
<
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
>
client/src/modules/viewer/components/common/upload.vue
浏览文件 @
5632ae6b
<
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
:
''
)
}
}
}
...
...
client/src/modules/viewer/components/layout.vue
浏览文件 @
5632ae6b
...
...
@@ -11,8 +11,9 @@
<
script
>
// components
import
ChapterPlayer
from
'./player/
C
hapterPlayer.vue'
// 章节视频
import
ChapterPlayer
from
'./player/
c
hapterPlayer.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'
// 课程考试
...
...
client/src/modules/viewer/components/live/chapterLive.vue
浏览文件 @
5632ae6b
...
...
@@ -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
}
`
}
}
...
...
client/src/modules/viewer/components/player/chapterPlayer.vue
浏览文件 @
5632ae6b
...
...
@@ -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 = fal
se"
@
close=
"
onPPTClo
se"
@
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
;
...
...
client/src/modules/viewer/components/player/pptPlayer.vue
浏览文件 @
5632ae6b
...
...
@@ -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
.
current
Sync
=
false
this
.
is
Sync
=
false
},
next
(
e
)
{
this
.
currentIndex
=
this
.
getIndex
(
this
.
currentIndex
+
1
)
this
.
current
Sync
=
false
this
.
is
Sync
=
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
()
{
...
...
client/src/modules/viewer/components/player/videoPlayer.vue
浏览文件 @
5632ae6b
...
...
@@ -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
:
tru
e
,
autoplay
:
fals
e
,
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
)
})
}
)
}
...
...
client/src/modules/viewer/components/read/chapterRead.vue
浏览文件 @
5632ae6b
...
...
@@ -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
{
...
...
client/src/modules/viewer/components/read/courseRead.vue
浏览文件 @
5632ae6b
...
...
@@ -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
{
...
...
client/src/modules/viewer/components/read/fileList.vue
浏览文件 @
5632ae6b
...
...
@@ -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
{
...
...
client/src/modules/viewer/components/work/chapterExam.vue
浏览文件 @
5632ae6b
<
template
>
<container
:title=
"
chapter.nam
e"
v-loading=
"loading"
>
<template
v-slot:header-aside
v-if=
"is
Submited"
>
正确率:
{{
detail
.
score
}}
%
</
template
>
<container
:title=
"
detail.paper_titl
e"
v-loading=
"loading"
>
<template
v-slot:header-aside
v-if=
"is
ExamComplete"
>
分数:
{{
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 unorderedQuestion
s"
v-for=
"(item, index) in item
s"
: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
()
},
// 组装问题数据
genQue
n
stions
(
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
>
client/src/modules/viewer/components/work/chapterTest.vue
0 → 100644
浏览文件 @
5632ae6b
<
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
>
client/src/modules/viewer/components/work/chapterWork.vue
浏览文件 @
5632ae6b
<
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>
<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"
>
<h4>
作业被驳回,点击“重新编辑”按钮重新编辑内容再次提交
</h4>
<div
class=
"paper-check-item"
>
<b>
驳回时间:
</b>
{{
detail
.
checker_time
}}
</div>
<div
class=
"paper-check-item"
>
<b>
驳回说明:
</b>
<div
class=
"edit_html"
v-html=
"detail.check_comments"
></div>
</div>
</div>
</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
.
check_date
}}
</p>
<p>
批改时间:
{{
detail
.
checker_time
}}
</p>
<div
class=
"paper-check-item"
>
<b>
评分:
</b>
{{
detail
.
score
}}
{{
detail
.
score
}}
</div>
<div
class=
"paper-check-item"
>
<b>
评语:
</b>
...
...
@@ -30,18 +53,28 @@
</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>
<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
class=
"buttons"
>
<el-tooltip
content=
"在获老师批改之前,可以多次提交,将以最后一次提交为准"
placement=
"right"
>
<el-button
type=
"primary"
@
click=
"onSubmit"
:disabled=
"isRevised"
>
{{submitText}}
</el-button>
<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
.
getChapter
Exam
(
this
.
sid
,
this
.
cid
,
this
.
resourceId
)
.
getChapter
Homework
(
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
.
sbumitChapter
Exam
(
params
)
.
sbumitChapter
Homework
(
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
>
client/src/modules/viewer/components/work/courseExam.vue
浏览文件 @
5632ae6b
<
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 question
s"
v-for=
"(item, index) in item
s"
: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
.
genQue
n
stions
(
this
.
detail
.
examination
)
?
this
.
genQuestions
(
this
.
detail
.
examination
)
:
[]
callback
&&
callback
()
})
...
...
@@ -172,10 +147,11 @@ export default {
})
},
// 组装问题数据
genQue
nstions
(
data
)
{
if
(
!
data
)
{
return
genQue
stions
(
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
.
genQue
n
stions
(
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
;
...
...
client/src/modules/viewer/components/work/courseWork.vue
浏览文件 @
5632ae6b
...
...
@@ -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
=>
{
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
)
})
.
catch
(()
=>
{
this
.
messageInstance
&&
this
.
messageInstance
.
close
()
this
.
messageInstance
=
this
.
$message
.
error
(
'还有题目未做,不能提交'
)
})
},
// 请求提交接口
handleSubmitRequest
(
params
)
{
...
...
@@ -231,5 +235,8 @@ p {
}
.paper-check-item
{
display
:
flex
;
b
{
white-space
:
nowrap
;
}
}
</
style
>
client/src/modules/viewer/components/work/examItem.vue
浏览文件 @
5632ae6b
...
...
@@ -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>
<p
v-if=
"data.check_comment"
>
<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
:
3
0px
;
padding-left
:
2
0px
;
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
:
#c9c9c9
7a
;
border
:
1px
solid
#c9c9c9
7a
;
padding
:
10px
;
::v-deep
*
{
margin
:
0
;
padding-left
:
20px
;
padding
:
0
;
max-width
:
100%
;
}
}
}
</
style
>
client/src/modules/viewer/components/work/index.vue
浏览文件 @
5632ae6b
...
...
@@ -12,11 +12,11 @@
<
script
>
// componets
import
ChapterWork
from
'./chapterWork.vue'
import
Chapter
Exam
from
'./chapterExam
.vue'
import
Chapter
Test
from
'./chapterTest
.vue'
export
default
{
name
:
'ViewerWork'
,
components
:
{
ChapterWork
,
Chapter
Exam
},
components
:
{
ChapterWork
,
Chapter
Test
},
props
:
{
// 当前选中的
chapter
:
{
...
...
@@ -36,7 +36,7 @@ export default {
computed
:
{
currentCompoent
()
{
const
componentNames
=
{
1
:
'Chapter
Exam'
,
// 考试
1
:
'Chapter
Test'
,
// 课后测验
2
:
'ChapterWork'
// 作业
}
const
homework
=
this
.
chapter
.
homework
...
...
client/src/modules/viewer/index.vue
浏览文件 @
5632ae6b
...
...
@@ -9,7 +9,7 @@
<h1
class=
"course-viewer-main-hd__title"
>
{{
detail
.
course_name
}}
</h1>
<!-- 直播的时候显示帮助按钮 -->
<template
v-if=
"isLive"
>
<router-link
to=
"/app/
account/feedbackC
reate"
target=
"_blank"
>
<router-link
to=
"/app/
feedback/feedback-c
reate"
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
},
...
...
client/src/pages/affairsHall/hall.vue
浏览文件 @
5632ae6b
...
...
@@ -283,7 +283,9 @@ export default {
},
// 新增
golearningAdd
(
url
)
{
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
()
}
}
...
...
client/src/pages/learn/course.vue
浏览文件 @
5632ae6b
...
...
@@ -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
}
})
}
}
}
...
...
client/src/pages/learn/courseDetail.vue
浏览文件 @
5632ae6b
...
...
@@ -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.currentChapter
Id
'
>
<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
}
})
}
},
/* 直接跳转打开新页面 */
...
...
@@ -527,8 +488,8 @@ export default {
cAction
.
Player
.
getChapterList
(
cid
,
sid
,
_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
.
live
.
viewer_name
=
window
.
G
.
UserInfo
.
student_info
.
personal_name
||
window
.
G
.
UserInfo
.
nickname
this
.
live
.
url
=
'https://view.csslcloud.net/api/view/index?roomid='
+
this
.
live
.
room_id
+
'&userid='
+
this
.
live
.
user_id
+
'&autoLogin=true&viewername='
+
(
this
.
live
.
viewer_name
||
'匿名'
)
+
'&viewertoken='
+
this
.
live
.
viewer_token
// + '&groupid=xxx'
...
...
@@ -561,82 +522,48 @@ 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
(
status
!==
0
&&
status
!==
1
&&
status
!==
103
)
{
this
.
$message
.
error
(
_course
.
chapters
[
i2
].
live
.
statusStr
)
if
(
type
===
1
)
{
return
}
const
enableRecord
=
_course
.
chapters
[
i2
].
live
.
enable_record
if
(
status
===
103
&&
enableRecord
!==
undefined
&&
enableRecord
!==
null
&&
!
enableRecord
)
{
this
.
$message
.
info
(
'该直播没有回放'
)
// 直播
if
(
type
===
5
)
{
const
live
=
data
.
live
const
status
=
live
.
live_status
if
(
status
===
0
||
(
status
===
2
&&
!
live
.
record_url
))
{
this
.
$message
.
error
(
live
.
statusStr
)
return
}
/* 判别如果为 云课堂记录 id 则直接进入 云课堂 */
if
(
this
.
cloudClassUrls
[
_
id
])
{
if
(
this
.
cloudClassUrls
[
v
id
])
{
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
[
v
id
]
+
'&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
}
`
})
}
}
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
(
'当前暂无点播课程'
)
}
...
...
@@ -676,11 +603,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
)
{
...
...
client/src/pages/mobileLive/list.vue
浏览文件 @
5632ae6b
...
...
@@ -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
...
...
client/src/pages/player/chapterExam/chapterExam.vue
浏览文件 @
5632ae6b
...
...
@@ -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
)
{
...
...
client/src/pages/player/chapterExam/chapterExam2.vue
0 → 100644
浏览文件 @
5632ae6b
<
template
>
<div
class=
"play-paper"
>
<div
class=
"play-paper-body"
>
<div
class=
"play-paper-title"
>
<div>
<h3>
{{
exam
.
title
}}
</h3>
</div>
</div>
<template
v-if=
"status.isStart"
>
<div
class=
"play-paper-content"
>
<template
v-if=
"exam.id"
>
<div
class=
"exam"
>
<!--
<div
style=
'text-align: center;'
>
-->
<!--
<div
class=
'topic'
>
-->
<!--
<div
class=
'tit'
>
{{
exam
.
title
}}
</div>
-->
<template
v-if=
"exam.type === 2"
>
<template
v-if=
"exam.score"
>
<div
style=
"font-size: 18px;"
>
分数:
{{
exam
.
score
.
total
}}
分
</div>
</
template
>
</template>
<
template
v-else-if=
"exam.type === 1"
>
<div
class=
"no-exam"
>
试卷批改中,请耐心等待
</div>
</
template
>
<!-- </div> -->
<!-- </div> -->
<
template
v-if=
"(exam.type !== 1)"
>
<div
style=
"text-align: center;"
v-if=
"exam.paper_deadline"
>
考试截止时间为:
{{
exam
.
paper_deadline
}}
</div>
<template
v-for=
"question in exam.examination"
>
<!-- 单选题 -->
<template
v-if=
"question.radioList.length"
>
<template
v-for=
"(item, index) in question.radioList"
>
<div
v-bind:key=
"item.id"
class=
"q-group"
:data-index=
"index"
>
<div
class=
"q-num"
>
{{
index
+
1
}}
.
</div>
<div
class=
"q-title"
v-html=
"item.content"
></div>
<div
class=
"q-type"
>
(单选题)
</div>
<el-radio-group
class=
"radio-group"
v-model=
"item.user_answer"
>
<template
v-for=
"(item1, index1) in item.options"
>
<el-radio
v-bind:key=
"item1.id"
:label=
"item1.id"
:disabled=
"isSubmited"
:class=
"['radio', ((item.right_answer && !!exam.type) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>
{{
index1
|
getLetter
()
}}
.
{{
item1
.
option
}}
</el-radio>
</
template
>
</el-radio-group>
<
template
v-if=
"item.right_answer && !!exam.type"
>
<div
class=
"result"
>
学生答案:
<div
:class=
"['stu', (item.right_answer === item.user_answer ? 'success' : 'error')]"
>
{{
item
.
user_answer
|
getRadioAnswer
(
item
.
options
)
}}
</div>
正确答案:
{{
item
.
right_answer
|
getRadioAnswer
(
item
.
options
)
}}
</div>
</
template
>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</template>
</template>
<!-- 多选题 -->
<
template
v-if=
"question.checkboxList.length"
>
<template
v-for=
"(item, index) in question.checkboxList"
>
<div
v-bind:key=
"item.id"
class=
"q-group"
:data-index=
"index"
>
<div
class=
"q-num"
>
{{
question
.
radioList
.
length
+
index
+
1
}}
.
</div>
<div
class=
"q-title"
v-html=
"item.content"
></div>
<div
class=
"q-type"
>
(多选题)
</div>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"item.user_answer"
>
<template
v-for=
"(item1, index1) in item.options"
>
<el-checkbox
v-bind:key=
"item1.id"
:label=
"item1.id"
:disabled=
"isSubmited"
:class=
"['checkbox', ((item.right_answer.length && !!exam.type) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>
{{
index1
|
getLetter
()
}}
.
{{
item1
.
option
}}
</el-checkbox>
</
template
>
</el-checkbox-group>
<
template
v-if=
"item.right_answer.length && !!exam.type"
>
<div
class=
"result"
>
学生答案:
<div
:class=
"['stu', ((item.right_answer.length && isCheckboxRight(item.user_answer, item.right_answer)) ? 'success' : 'error')]"
>
{{
item
.
user_answer
|
getCheckboxAnswer
(
item
.
options
)
}}
</div>
正确答案:
{{
item
.
right_answer
|
getCheckboxAnswer
(
item
.
options
)
}}
</div>
</
template
>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</template>
</template>
<!-- 简答题 -->
<
template
v-if=
"question.shortAnswerList.length"
>
<template
v-for=
"(item, index) in question.shortAnswerList"
>
<div
class=
"q-group"
:key=
"index"
>
<div
class=
"q-sa-title"
>
{{
question
.
radioList
.
length
+
question
.
checkboxList
.
length
+
index
+
1
}}
.
简答题
</div>
<div
class=
"edit_html"
v-html=
"item.content || ''"
></div>
<v-editor
v-model=
"item.user_answer"
:disabled=
"isSubmited"
></v-editor>
<div
style=
"height: 10px;"
></div>
<!-- 利用key值自动更新组件 -->
<component
:is=
"item.upload.type"
v-bind:key=
"item.upload.id + new Date().getTime()"
:item=
"item.upload"
:formData=
"item"
:isUpload=
"!exam.type"
></component>
<div
class=
"result"
v-if=
"item.check_comment"
>
评语:
{{
item
.
check_comment
}}
</div>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</
template
>
</template>
</template>
<div
:class=
"['btn', (isSubmited && 'on')]"
@
click=
"submitExam"
:data-submit=
"isSubmited"
@
mousedown=
"_SubmitMouseLeftDown()"
>
{{isSubmited ? "已提交" : "提交"}}
</div>
<div
class=
"care"
>
(注意:考试只有一次提交机会)
</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</template>
</div>
</template>
</div>
</template>
<
template
v-else
>
<div
class=
"exam"
>
<!--
<p>
考试须知:
</p>
-->
<div
style=
"text-align: left;"
v-if=
"exam.paper_deadline"
>
考试截止时间为:
{{
exam
.
paper_deadline
}}
</div>
<template
v-if=
"isExamTime"
>
<div
style=
"width: 25%;"
:class=
"['btn']"
@
click=
"beginExam(true)"
@
mousedown=
"beginExam(true)"
>
开始考试
</div>
</
template
>
</div>
</template>
</div>
</div>
</template>
<
script
>
import
cAction
from
'@action'
import
Base64
from
'Base64'
import
VEditor
from
'@/components/editor.vue'
const
A_Z
=
(
function
()
{
const
result
=
[]
for
(
let
i
=
0
;
i
<
26
;
i
++
)
{
result
.
push
(
String
.
fromCharCode
(
65
+
i
))
}
return
result
})()
var
getLetter
=
val
=>
{
return
A_Z
[
val
]
}
export
default
{
props
:
{
chapters
:
{
type
:
Array
,
default
()
{
return
[]
}
},
sid
:
{
type
:
String
,
require
:
false
},
cid
:
{
type
:
String
,
require
:
false
},
id
:
{
type
:
String
,
require
:
false
}
},
components
:
{
VEditor
},
filters
:
{
getLetter
:
getLetter
,
getRadioAnswer
:
(
val
,
arr
)
=>
{
for
(
let
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
if
(
arr
[
i
].
id
===
val
)
{
return
getLetter
(
i
)
}
}
},
getCheckboxAnswer
:
(
val
,
arr
)
=>
{
let
str
=
''
for
(
let
i
=
0
;
i
<
val
.
length
;
i
++
)
{
const
tmpId
=
val
[
i
]
for
(
let
j
=
0
;
j
<
arr
.
length
;
j
++
)
{
if
(
arr
[
j
].
id
===
tmpId
)
{
str
+=
getLetter
(
j
)
+
','
break
}
}
}
return
str
.
substr
(
0
,
str
.
length
-
1
)
}
},
data
()
{
return
{
_time
:
null
,
// 定时器,自动化提交
exam
:
{},
status
:
{
isStart
:
false
,
startTime
:
''
,
terminateTime
:
''
,
serverTime
:
''
,
examinationStatus
:
''
,
type
:
0
,
isPublished
:
0
}
}
},
watch
:
{
id
:
{
handler
()
{
this
.
init
()
}
}
},
computed
:
{
// 当前章节
activeChatper
()
{
let
found
=
null
for
(
const
item
of
this
.
chapters
)
{
found
=
item
.
children
.
find
(
subItem
=>
subItem
.
id
===
this
.
id
)
if
(
found
)
{
break
}
}
return
found
||
{}
},
// 是否是考试时间
isExamTime
()
{
if
(
!
this
.
exam
.
paper_deadline
)
{
return
true
}
// 大于开始时间,小于结束时间
const
endTime
=
+
new
Date
(
this
.
exam
.
paper_deadline
)
const
currentTime
=
new
Date
().
getTime
()
return
currentTime
<
endTime
},
// 考试完成
isExamComplete
()
{
// 考试完成,批改完成并且公布成绩
return
this
.
exam
.
is_published
===
1
&&
this
.
exam
.
type
===
2
},
// 是否提交
isSubmited
()
{
return
this
.
exam
.
type
===
1
||
this
.
exam
.
type
===
2
}
},
mounted
()
{
this
.
init
()
this
.
$emit
(
'changeSideBar'
,
''
)
setTimeout
(()
=>
{
if
(
window
.
document
.
getElementById
(
'switch-btn'
))
{
window
.
document
.
getElementById
(
'switch-btn'
).
style
.
display
=
'none'
}
},
500
)
},
destroyed
()
{
if
(
this
.
_time
)
{
clearInterval
(
this
.
_time
)
this
.
_time
=
null
}
if
(
window
.
document
.
getElementById
(
'switch-btn'
))
{
window
.
document
.
getElementById
(
'switch-btn'
).
style
.
display
=
'block'
}
},
methods
:
{
isCheckboxRight
:
(
val
,
arr
)
=>
{
let
flag
=
true
for
(
let
i
=
0
;
i
<
arr
.
length
;
i
++
)
{
const
tmpId
=
arr
[
i
]
let
j
=
0
for
(;
j
<
val
.
length
;
j
++
)
{
if
(
val
[
j
]
===
tmpId
)
{
break
}
}
if
(
j
===
val
.
length
)
{
flag
=
false
break
}
}
return
flag
},
isCheckboxChecked
:
(
val
,
arr
)
=>
{
let
i
=
0
for
(;
i
<
arr
.
length
;
i
++
)
{
if
(
arr
[
i
].
id
===
val
||
arr
[
i
]
===
val
)
{
return
true
}
}
return
false
},
init
()
{
const
data
=
this
.
activeChatper
.
paper
const
exam
=
{}
exam
.
id
=
data
.
id
exam
.
title
=
data
.
paper_title
exam
.
score
=
{}
exam
.
type
=
3
exam
.
examination
=
data
.
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
})
exam
.
paper_deadline
=
data
.
paper_deadline
this
.
exam
=
JSON
.
parse
(
JSON
.
stringify
(
exam
))
// this.loadExamStatus()
// this.loadExamInfo()
this
.
loadAjax
()
if
(
this
.
_time
)
{
clearInterval
(
this
.
_time
)
this
.
_time
=
null
}
this
.
_time
=
setInterval
(()
=>
{
// this.loadExamStatus()
if
(
!
this
.
isSubmited
&&
this
.
status
.
isStart
)
{
// console.log(11, '暂存')
this
.
submitExam
({
submitType
:
true
})
// 暂存, submitType: true 暂存;其他或不填为提交
}
/* 到时间 自动提交 */
if
(
!
this
.
isSubmited
&&
this
.
status
.
isStart
&&
new
Date
(
this
.
status
.
terminateTime
).
getTime
()
-
new
Date
(
this
.
status
.
serverTime
).
getTime
()
<=
5000
)
{
this
.
submitExam
({
submitType
:
false
,
currentTarget
:
{
dataset
:
{}
}
})
}
},
10000
)
},
/* 定时调用 - 考试状态 */
loadExamStatus
()
{
cAction
.
Player
.
getExamStatus
(
this
.
cid
,
this
.
sid
,
this
.
id
)
.
then
(
_data
=>
{
if
(
_data
.
status
&&
_data
.
status
===
200
)
{
this
.
status
.
startTime
=
this
.
setTime
(
_data
.
start_time
)
this
.
status
.
terminateTime
=
_data
.
terminate_time
this
.
status
.
serverTime
=
this
.
setTime
(
_data
.
server_time
)
this
.
status
.
examinationStatus
=
_data
.
examination_status
}
else
{
this
.
$message
.
error
(
'数据异常,请联系管理员'
)
}
})
.
catch
(
e
=>
{
this
.
$message
.
error
(
e
.
message
)
})
.
finally
(()
=>
{})
},
/* 时间格式化(解决safari时间兼容问题); */
setTime
(
time
)
{
return
time
.
replace
(
/-/g
,
'/'
)
},
/* 开始考试 */
beginExam
(
flag
)
{
this
.
status
.
isStart
=
true
// this.loadAjax(flag)
},
/* 加载题库基本数据 */
loadExamInfo
()
{
const
loading
=
this
.
$loading
({
lock
:
true
,
text
:
''
,
spinner
:
''
,
background
:
'rgba(255, 255, 255, 0.9)'
})
cAction
.
Player
.
getExamInfo
(
this
.
cid
,
this
.
sid
)
.
then
(
_data
=>
{
this
.
exam
=
_data
this
.
exam
.
id
=
this
.
id
})
.
catch
(
e
=>
{
this
.
$message
.
error
(
e
.
message
)
})
.
finally
(()
=>
{
loading
.
close
()
/* 正在考试,考试结束 */
this
.
beginExam
()
})
},
/**
* 生命周期函数--监听页面加载
* @param flag 通过该字段 判别是点击进入考试 还是 自动调用
*/
loadAjax
(
flag
)
{
const
loading
=
this
.
$loading
({
lock
:
true
,
text
:
''
,
spinner
:
''
,
background
:
'rgba(255, 255, 255, 0.9)'
})
cAction
.
Player
.
getExamAnswer
(
this
.
cid
,
this
.
sid
,
this
.
id
,
{
paper_type
:
0
})
.
then
(
_data
=>
{
if
(
_data
.
code
===
8001
)
{
console
.
log
(
'没有考试内容,认为是第一次答题,并且在答题期间,所以显示考试开始页面'
)
}
else
{
this
.
status
.
isStart
=
_data
.
type
!==
3
this
.
exam
.
title
=
_data
.
title
this
.
$set
(
this
.
exam
,
'type'
,
_data
.
type
)
this
.
exam
.
score
=
_data
.
score
this
.
exam
.
submitted_time
=
_data
.
submitted_time
this
.
exam
.
isPublished
=
_data
.
isPublished
this
.
status
.
type
=
_data
.
type
this
.
status
.
isPublished
=
_data
.
isPublished
this
.
exam
.
examination
=
this
.
exam
.
examination
.
map
((
exam
,
index
)
=>
{
const
rawExam
=
_data
.
examination
[
index
]
if
(
!
rawExam
)
{
return
exam
}
for
(
let
i
=
0
;
i
<
exam
.
radioList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
radioList
.
length
;
j
++
)
{
if
(
rawExam
.
radioList
[
j
].
id
===
exam
.
radioList
[
i
].
id
)
{
for
(
const
k
in
rawExam
.
radioList
[
j
])
{
exam
.
radioList
[
i
][
k
]
=
rawExam
.
radioList
[
j
][
k
]
}
}
}
}
for
(
let
i
=
0
;
i
<
exam
.
checkboxList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
checkboxList
.
length
;
j
++
)
{
if
(
rawExam
.
checkboxList
[
j
].
id
===
exam
.
checkboxList
[
i
].
id
)
{
for
(
const
k
in
rawExam
.
checkboxList
[
j
])
{
exam
.
checkboxList
[
i
][
k
]
=
rawExam
.
checkboxList
[
j
][
k
]
}
}
}
}
for
(
let
i
=
0
;
i
<
exam
.
shortAnswerList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
shortAnswerList
.
length
;
j
++
)
{
if
(
rawExam
.
shortAnswerList
[
j
].
id
===
exam
.
shortAnswerList
[
i
].
id
)
{
for
(
const
k
in
rawExam
.
shortAnswerList
[
j
])
{
exam
.
shortAnswerList
[
i
][
k
]
=
rawExam
.
shortAnswerList
[
j
][
k
]
}
}
}
}
return
exam
})
}
})
.
catch
(
e
=>
{
this
.
$message
.
error
(
e
.
message
)
})
.
finally
(()
=>
{
// console.log(this.exam.type, this.exam.isPublished)
loading
.
close
()
if
(
this
.
status
.
isStart
&&
this
.
exam
.
type
!==
1
&&
this
.
exam
.
type
!==
2
)
{
/* 滚动到头部 */
document
.
querySelector
(
'.play-paper'
).
scrollTop
=
0
}
})
},
/**
* 提交试题
*/
submitExam
(
e
)
{
if
(
!
e
.
submitType
&&
e
.
currentTarget
.
dataset
.
submit
)
{
this
.
$message
.
error
(
'已做过,不能再提交'
)
return
}
const
body
=
{
answers
:
[],
type
:
!
e
.
submitType
?
1
:
0
,
paper_type
:
0
}
// type: 0 缓存;type: 1 提交
for
(
let
k
=
0
;
k
<
this
.
exam
.
examination
.
length
;
k
++
)
{
const
exam
=
this
.
exam
.
examination
[
k
]
const
radioList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
radioList
.
length
;
i
++
)
{
const
tmp
=
exam
.
radioList
[
i
]
if
(
!
tmp
.
user_answer
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有单选题未做,不能提交'
)
return
}
radioList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
}
const
checkboxList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
checkboxList
.
length
;
i
++
)
{
const
tmp
=
exam
.
checkboxList
[
i
]
if
(
!
tmp
.
user_answer
.
length
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有多选题未做,不能提交'
)
return
}
checkboxList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
}
const
shortAnswerList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
shortAnswerList
.
length
;
i
++
)
{
const
tmp
=
exam
.
shortAnswerList
[
i
]
if
(
!
tmp
.
user_answer
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有简答题未做,不能提交'
)
return
}
shortAnswerList
.
push
({
id
:
tmp
.
id
,
user_answer
:
Base64
.
encode
(
tmp
.
user_answer
),
attachments
:
tmp
.
attachments
})
}
body
.
answers
[
k
]
=
{
radioList
,
checkboxList
,
shortAnswerList
}
}
body
.
answers
=
JSON
.
stringify
(
body
.
answers
)
let
loading
=
null
if
(
!
e
.
submitType
)
{
loading
=
this
.
$loading
({
lock
:
true
,
text
:
''
,
spinner
:
''
,
background
:
'rgba(255, 255, 255, 0.9)'
})
}
cAction
.
Player
.
submitExam
(
this
.
cid
,
this
.
sid
,
this
.
id
,
body
)
.
then
(
_res
=>
{
if
(
e
.
submitType
)
{
// this.$message.success('暂存成功')
console
.
log
(
'暂存成功'
)
return
}
if
(
_res
.
code
===
200
)
{
this
.
$message
.
success
(
'考试答卷提交成功'
)
// this.exam.type = 1
this
.
init
()
console
.
log
(
111
,
'this.loadAjax()'
)
}
else
{
this
.
$message
.
error
(
_res
.
data
.
error
)
}
})
.
catch
(
e
=>
{
this
.
$message
.
error
(
e
.
message
)
})
.
finally
(()
=>
{
if
(
!
e
.
submitType
)
{
loading
.
close
()
}
})
},
_SubmitMouseLeftDown
()
{
const
_fn1
=
this
.
repeatExam
.
bind
(
this
,
false
)
document
.
addEventListener
(
'keydown'
,
_fn1
,
false
)
const
_fn3
=
function
()
{
document
.
removeEventListener
(
'keydown'
,
_fn1
)
document
.
removeEventListener
(
'mouseup'
,
_fn3
)
}
document
.
addEventListener
(
'mouseup'
,
_fn3
,
false
)
},
/**
* 重做
*/
repeatExam
(
e
,
flag
)
{
let
_flag
=
flag
/* 字母 f */
if
(
e
.
keyCode
===
70
)
{
_flag
=
true
}
if
(
!
_flag
)
{
return
}
const
loading
=
this
.
$loading
({
lock
:
true
,
text
:
''
,
spinner
:
''
,
background
:
'rgba(255, 255, 255, 0.9)'
})
cAction
.
chapterAction
.
getExamDetail
(
this
.
sid
,
this
.
cid
,
this
.
id
)
.
then
(
_data
=>
{
this
.
exam
=
{}
})
.
catch
(
e
=>
{
this
.
$message
.
error
(
e
.
message
)
})
.
finally
(()
=>
{
loading
.
close
()
})
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.play
{
.exam
{
padding
:
0
;
}
.exam
.topic
{
display
:
inline-block
;
margin-bottom
:
0
.1rem
;
}
.exam
.topic
.tit
{
margin
:
0
auto
;
padding
:
0
0
.2rem
;
text-align
:
center
;
font-size
:
0
.24rem
;
color
:
#313131
;
background
:
#fff
;
box-sizing
:
border-box
;
-webkit-box-sizing
:
border-box
;
}
.exam
.topic
.cur
{
text-align
:
center
;
font-size
:
0
.18rem
;
color
:
#313131
;
line-height
:
0
.4rem
;
}
/* 循环 所有选择题 */
.exam
.q-group
{
padding
:
0
.1rem
0
.1rem
;
border-bottom
:
1px
solid
#c9c9c9
7a
;
overflow
:
hidden
;
}
.exam
.q-group
.q-num
{
float
:
left
;
margin-right
:
0
.1rem
;
font-size
:
0
.16rem
;
color
:
#676a6c
;
}
.exam
.q-group
.q-title
{
float
:
left
;
width
:
90%
;
font-size
:
0
.16rem
;
color
:
#676a6c
;
text-align
:
justify
;
}
.exam
.q-group
.q-type
{
float
:
right
;
font-size
:
0
.16rem
;
color
:
#676a6c
;
}
.exam
.q-group
.radio-group
{
float
:
left
;
margin-top
:
0
.1rem
;
width
:
100%
;
}
.exam
.q-group
.radio-group
.radio
{
display
:
block
;
font-size
:
0
.18rem
;
color
:
#3f3b3a
;
line-height
:
0
.3rem
;
margin-bottom
:
0
.1rem
;
}
.exam
.q-group
.checkbox-group
{
float
:
left
;
margin-top
:
0
.1rem
;
width
:
100%
;
}
.exam
.q-group
.checkbox-group
.checkbox
{
display
:
block
;
font-size
:
0
.18rem
;
color
:
#3f3b3a
;
line-height
:
0
.3rem
;
margin-bottom
:
0
.1rem
;
}
.exam
.q-group
.radio-group
.radio.error
,
.exam
.q-group
.checkbox-group
.checkbox.error
{
color
:
#d80000
;
}
.exam
.q-group
.radio-group
.radio.success
,
.exam
.q-group
.checkbox-group
.checkbox.success
{
color
:
#090
;
}
.exam
.q-group
.result
{
clear
:
both
;
display
:
flex
;
justify-content
:
flex-end
;
font-size
:
0
.18rem
;
color
:
#3f3b3a
;
margin-right
:
0
;
}
.exam
.q-group
.result
.stu
{
display
:
inline-block
;
}
.exam
.q-group
.result
.stu.error
{
color
:
#d80000
;
}
.exam
.q-group
.result
.stu.success
{
color
:
#090
;
}
.exam
.q-group
:last-child
{
border-bottom
:
none
;
}
.exam
.btn
{
margin
:
0
.2rem
auto
;
width
:
60%
;
height
:
0
.5rem
;
line-height
:
0
.5rem
;
font-size
:
0
.16rem
;
text-align
:
center
;
font-weight
:
300
;
color
:
#fff
;
border-radius
:
0
.1rem
;
background
:
#b49441
;
cursor
:
pointer
;
}
.exam
.btn.on
{
opacity
:
0
.5
;
}
.exam
.care
{
font-size
:
0
.16rem
;
color
:
#d80000
;
text-align
:
center
;
}
.exam
.q-sa-title
{
float
:
left
;
width
:
100%
;
font-size
:
0
.16rem
;
color
:
#676a6c
;
text-align
:
justify
;
}
}
.no-exam
{
font-size
:
0
.24rem
;
line-height
:
19
;
text-align
:
center
;
}
.analysis
{
clear
:
both
;
display
:
flex
;
margin
:
20px
0
;
padding
:
0
20px
;
border
:
1px
solid
#c9c9c9
7a
;
}
</
style
>
client/src/pages/player/exam/exam.vue
浏览文件 @
5632ae6b
...
...
@@ -13,22 +13,7 @@
<template
v-if=
"status.type === 2 && status.isPublished === 1"
>
<template
v-if=
"exam.score.total !== undefined"
>
<div
style=
"font-size: 18px;"
>
得分:单选:
{{
exam
.
score
.
radio
}}
分,多选:
{{
exam
.
score
.
checkbox
}}
分,简答:
{{
exam
.
score
.
shortAnswer
}}
分,总分:
{{
exam
.
score
.
total
}}
分
</div>
</
template
>
<
template
v-if=
"exam.shortAnswerList.length"
>
<template
v-for=
"(item, index) in exam.shortAnswerList"
>
<template
v-if=
"item.check_comment"
>
<div
style=
"font-size: 18px; margin-top: 10px;"
:key=
"index"
>
<div>
简答题
{{
index
+
1
}}
</div>
<div>
老师评语:
<div
style=
"display: inline-block"
v-html=
"item.check_comment || ''"
></div>
</div>
</div>
</
template
>
</template>
<div
style=
"font-size: 18px;"
>
总分:
{{
exam
.
score
.
total
}}
分
</div>
</
template
>
</template>
<
template
v-else-if=
"status.type === 0"
>
...
...
@@ -45,7 +30,7 @@
</div>
</div>
<template
v-if=
"status.isStart"
>
<div
class=
"play-paper-content
play-chapter-exam
"
>
<div
class=
"play-paper-content"
>
<template
v-if=
"exam.id"
>
<div
class=
"exam"
>
<!--
<div
style=
'text-align: center;'
>
-->
...
...
@@ -53,34 +38,20 @@
<!--
<div
class=
'tit'
>
{{
exam
.
title
}}
</div>
-->
<template
v-if=
"exam.type === 2 && exam.isPublished === 1"
>
<template
v-if=
"exam.score.total !== undefined"
>
<div
style=
"font-size: 18px;"
>
得分:单选:
{{
exam
.
score
.
radio
}}
分,多选:
{{
exam
.
score
.
checkbox
}}
分,简答:
{{
exam
.
score
.
shortAnswer
}}
分,总分:
{{
exam
.
score
.
total
}}
分
</div>
</
template
>
<
template
v-if=
"exam.shortAnswerList.length"
>
<template
v-for=
"(item, index) in exam.shortAnswerList"
>
<template
v-if=
"item.check_comment"
>
<div
style=
"font-size: 18px; margin-top: 10px;"
:key=
"index"
>
<div>
简答题
{{
index
+
1
}}
</div>
<div>
老师评语:
<div
style=
"display: inline-block"
v-html=
"item.check_comment || ''"
></div>
</div>
</div>
<div
style=
"font-size: 18px;"
>
总分:
{{
exam
.
score
.
total
}}
分
</div>
</
template
>
</template>
</template>
</template>
<
template
v-else-if=
"exam.type === 1 || exam.type === 2"
>
<
template
v-else-if=
"exam.type === 1"
>
<div
class=
"no-exam"
>
试卷批改中,请耐心等待
</div>
</
template
>
<!-- </div> -->
<!-- </div> -->
<
template
v-if=
"(exam.type !== 1
&& exam.type !== 2
)"
>
<
template
v-if=
"(exam.type !== 1)"
>
<div
style=
"text-align: center;"
>
考试截止时间为:
{{
status
.
terminateTime
}}
</div>
<template
v-for=
"question in exam.examination"
>
<!-- 单选题 -->
<template
v-if=
"exam
.radioList.length"
>
<template
v-for=
"(item, index) in exam
.radioList"
>
<template
v-if=
"question
.radioList.length"
>
<template
v-for=
"(item, index) in question
.radioList"
>
<div
v-bind:key=
"item.id"
class=
"q-group"
:data-index=
"index"
>
<div
class=
"q-num"
>
{{
index
+
1
}}
.
</div>
<div
class=
"q-title"
v-html=
"item.content"
></div>
...
...
@@ -90,8 +61,8 @@
<el-radio
v-bind:key=
"item1.id"
:label=
"item1.id"
:disabled=
"!!item.right_answer && !!exam.type
"
:class=
"['radio', ((item.right_answer && !!exam.type
) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
:disabled=
"isSubmited
"
:class=
"['radio', ((item.right_answer
) ? (item1.id === item.right_answer ? 'success' : 'error') : '')]"
>
{{
index1
|
getLetter
()
}}
.
{{
item1
.
option
}}
</el-radio>
</
template
>
</el-radio-group>
...
...
@@ -104,14 +75,18 @@
正确答案:
{{
item
.
right_answer
|
getRadioAnswer
(
item
.
options
)
}}
</div>
</
template
>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</template>
</template>
<!-- 多选题 -->
<
template
v-if=
"exam
.checkboxList.length"
>
<template
v-for=
"(item, index) in exam
.checkboxList"
>
<
template
v-if=
"question
.checkboxList.length"
>
<template
v-for=
"(item, index) in question
.checkboxList"
>
<div
v-bind:key=
"item.id"
class=
"q-group"
:data-index=
"index"
>
<div
class=
"q-num"
>
{{
exam
.
radioList
.
length
+
index
+
1
}}
.
</div>
<div
class=
"q-num"
>
{{
question
.
radioList
.
length
+
index
+
1
}}
.
</div>
<div
class=
"q-title"
v-html=
"item.content"
></div>
<div
class=
"q-type"
>
(多选题)
</div>
<el-checkbox-group
class=
"checkbox-group"
v-model=
"item.user_answer"
>
...
...
@@ -119,8 +94,8 @@
<el-checkbox
v-bind:key=
"item1.id"
:label=
"item1.id"
:disabled=
"!!item.right_answer.length && !!exam.type
"
:class=
"['checkbox', ((item.right_answer.length && !!exam.type
) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
:disabled=
"isSubmited
"
:class=
"['checkbox', ((item.right_answer.length
) ? (isCheckboxChecked(item1.id, item.right_answer) ? 'success' : 'error') : '')]"
>
{{
index1
|
getLetter
()
}}
.
{{
item1
.
option
}}
</el-checkbox>
</
template
>
</el-checkbox-group>
...
...
@@ -133,18 +108,22 @@
正确答案:
{{
item
.
right_answer
|
getCheckboxAnswer
(
item
.
options
)
}}
</div>
</
template
>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</template>
</template>
<!-- 简答题 -->
<
template
v-if=
"exam
.shortAnswerList.length"
>
<template
v-for=
"(item, index) in exam
.shortAnswerList"
>
<
template
v-if=
"question
.shortAnswerList.length"
>
<template
v-for=
"(item, index) in question
.shortAnswerList"
>
<div
class=
"q-group"
:key=
"index"
>
<div
class=
"q-sa-title"
>
{{
exam
.
radioList
.
length
+
exam
.
checkboxList
.
length
+
index
+
1
}}
.
简答题
</div>
>
{{
question
.
radioList
.
length
+
question
.
checkboxList
.
length
+
index
+
1
}}
.
简答题
</div>
<div
class=
"edit_html"
v-html=
"item.content || ''"
></div>
<textarea
:id=
"('editor-exam' + index)"
v-model=
"item.user_answer"
></textarea
>
<v-editor
v-model=
"item.user_answer"
:disabled=
"isSubmited"
></v-editor
>
<div
style=
"height: 10px;"
></div>
<!-- 利用key值自动更新组件 -->
<component
...
...
@@ -154,16 +133,21 @@
:formData=
"item"
:isUpload=
"!exam.type"
></component>
<div
class=
"result"
v-if=
"item.check_comment"
>
评语:
{{
item
.
check_comment
}}
</div>
<div
class=
"analysis"
v-if=
"item.analysis"
>
<p>
解析:
</p>
<div
v-html=
"item.analysis"
></div>
</div>
</div>
</
template
>
</template>
</template>
<div
:class=
"['btn', (
exam.type
&& 'on')]"
:class=
"['btn', (
isSubmited
&& 'on')]"
@
click=
"submitExam"
:data-submit=
"
!!exam.type
"
:data-submit=
"
isSubmited
"
@
mousedown=
"_SubmitMouseLeftDown()"
>
{{
exam.type
? "已提交" : "提交"}}
</div>
>
{{
isSubmited
? "已提交" : "提交"}}
</div>
<div
class=
"care"
>
(注意:考试只有一次提交机会)
</div>
<!-- <div :class='["btn"]' @click='repeatExam($event, true)' v-if="exam.work_contents">重做</div> -->
</template>
...
...
@@ -197,37 +181,17 @@
import
cAction
from
'@action'
import
Base64
from
'Base64'
import
CKEDITOR
from
'CKEDITOR
'
import
VEditor
from
'@/components/editor.vue
'
var
getLetter
=
val
=>
{
switch
(
val
)
{
case
0
:
return
'A'
case
1
:
return
'B'
case
2
:
return
'C'
case
3
:
return
'D'
case
4
:
return
'E'
case
5
:
return
'F'
case
6
:
return
'G'
case
7
:
return
'H'
case
8
:
return
'I'
case
9
:
return
'J'
case
10
:
return
'K'
case
11
:
return
'L'
case
12
:
return
'M'
const
A_Z
=
(
function
()
{
const
result
=
[]
for
(
let
i
=
0
;
i
<
26
;
i
++
)
{
result
.
push
(
String
.
fromCharCode
(
65
+
i
))
}
return
result
})()
var
getLetter
=
val
=>
{
return
A_Z
[
val
]
}
export
default
{
...
...
@@ -236,6 +200,7 @@ export default {
cid
:
{
type
:
String
,
require
:
false
},
id
:
{
type
:
String
,
require
:
false
}
},
components
:
{
VEditor
},
filters
:
{
getLetter
:
getLetter
,
getRadioAnswer
:
(
val
,
arr
)
=>
{
...
...
@@ -262,131 +227,7 @@ export default {
data
()
{
return
{
_time
:
null
,
// 定时器,自动化提交
exam
:
{
// id: '1',
// title: '标题',
// type: 0, // 0: 暂存,可以继续答题;1: 提交,不能再继续答题(等待批改) 2: 已批改(这时候有分数)
// radioList: [
// {
// id: '6622309081933676544',
// content: '\u5047\u8bbe\u8d27\u5e01\u9700\u6c42\u4e3aL=ky-hr\uff0c\u8d27\u5e01\u4f9b\u7ed9\u589e\u52a010\u4ebf\u7f8e\u5143\u800c\u5176\u5b83\u6761\u4ef6\u4e0d\u53d8\uff0c\u5219\u4f1a\u4f7fLM \u66f2\u7ebf( )',
// options: [
// {
// id: '6622310260604403712',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403713',
// option: '\u53f3\u79fb k\u4e58\u4ee510\u4ebf\u7f8e\u5143'
// },
// {
// id: '6622310260604403714',
// option: '\u53f3\u79fb10\u4ebf\u7f8e\u5143\u9664\u4ee5k'
// },
// {
// id: '6622310260604403715',
// option: '\u53f3\u79fb k\u9664\u4ee510\u4ebf\u7f8e\u5143'
// }
// ],
// user_answer: '6622310260604403714',
// right_answer: '6622310260604403714',
// get_score: 30,
// score: 30
// }
// ],
// checkboxList: [
// {
// id: '6622310510798831616',
// content: '\u51ef\u6069\u65af\u5b8f\u89c2\u7ecf\u6d4e\u7406\u8bba\u7684\u4e3b\u8981\u524d\u63d0\u5305\u62ec\uff08\uff09',
// options: [
// {
// id: '6622310872641437696',
// option: '\u8fb9\u9645\u6d88\u8d39\u503e\u5411\u9012\u51cf'
// },
// {
// id: '6622310872641437697',
// option: '\u8d44\u672c\u8fb9\u9645\u6548\u7387\u9012\u51cf'
// },
// {
// id: '6622310872641437698',
// option: '\u4e0d\u786e\u5b9a\u6027\u4e0e\u6d41\u52a8\u6027\u504f\u597d'
// },
// {
// id: '6622310872641437699',
// option: '\u540d\u4e49\u5de5\u8d44\u521a\u6027'
// }
// ],
// user_answer: [
// '6622310872641437697',
// '6622310872641437698',
// '6622310872641437699'
// ],
// right_answer: [
// '6622310872641437697',
// '6622310872641437698'
// ],
// get_score: 30,
// score: 30
// }
// ],
// shortAnswerList: [
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true,
// headers: {
// 'tenant': 'sofia'
// }
// },
// html: `
//
<
div
style
=
"color: #72818c; font-size: 14px;"
>
//
<
p
style
=
"margin: 0;"
>
支持
doc
,
docx
,
ppt
,
xls
,
txt
,
rar
,
zip
,
pdf
,
jpg
,
pic
,
png
格式的文件,文件小于
30
M
。
<
/p
>
//
<
/div
>
// `
// }
// },
// {
// id: '6622311487476072448',
// content: '\u8bba\u8ff01929\u5e74\u7f8e\u56fd\u4e3a\u4ec0\u4e48\u4f1a\u51fa\u73b0\u5927\u8427\u6761\u3002',
// user_answer: '2018\u5e74\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u5bf9\u4f9b\u7ed9\u6027\u7ed3\u6784\u6027\u6539\u9769\u6709\u4e00\u5b9a\u7684\u63a8\u52a8\u4f5c\u7528\u3002\u623f\u5730\u4ea7\u5e02\u573a\u5f97\u5230\u4e00\u5b9a\u7a0b\u5ea6\u6291\u5236\u3002\u7ecf\u6d4e\u53d1\u5c55\u5e73\u7a33\uff0c\u7ecf\u6d4e\u8fd0\u884c\u5728\u5408\u7406\u533a\u95f4\u3002\u964d\u7a0e\u4e3e\u52a8\u53d6\u5f97\u6210\u6548\uff0c\u5168\u5e74\u56fd\u5185\u589e\u503c\u7a0e\u3001\u4f01\u4e1a\u6240\u5f97\u7a0e\u3001\u4e2a\u4eba\u6240\u5f97\u7a0e\u540c\u6bd4\u5206\u522b\u589e\u957f9.1%\u300110%\u300115.9%\uff0c\u5206\u522b\u62c9\u9ad8\u5168\u56fd\u8d22\u653f\u6536\u5165\u589e\u5e453\u4e2a\u30011.9\u4e2a\u30011.1\u4e2a\u767e\u5206\u70b9\uff0c\u6709\u53d1\u6325\u51fa\u5b8f\u89c2\u8c03\u63a7\u7684\u4f5c\u7528\u3002\u4ece2018\u5e74\u8d27\u5e01\u653f\u7b56\u6574\u4f53\u6765\u770b\uff0c\u8f83\u597d\u5730\u628a\u63e1\u4e86\u652f\u6301\u5b9e\u4f53\u7ecf\u6d4e\u548c\u517c\u987e\u5185\u5916\u90e8\u5747\u8861\u4e4b\u95f4\u7684\u5e73\u8861\uff0c\u53d6\u5f97\u4e86\u79ef\u6781\u6210\u6548\uff0c\u4e5f\u5f88\u597d\u5730\u9632\u8303\u4e86\u91d1\u878d\u98ce\u9669\u30022018\u5e74\u603b\u4f53\u4fdd\u6301\u4e86\u9002\u5b9c\u7684\u8d27\u5e01\u91d1\u878d\u73af\u5883\uff0c\u65e0\u8bba\u662f\u5404\u9879\u8d37\u6b3e\u8fd8\u662f\u666e\u60e0\u53e3\u5f84\u5c0f\u5fae\u8d37\u6b3e\u90fd\u540c\u6bd4\u5927\u5e45\u591a\u589e\uff0cM2\u548c\u793e\u4f1a\u878d\u8d44\u89c4\u6a21\u5b58\u91cf\u540c\u6bd4\u589e\u901f\u4e0e\u540d\u4e49GDP\u589e\u901f\u57fa\u672c\u5339\u914d\uff0c\u6709\u529b\u4fc3\u8fdb\u4e86\u6211\u56fd\u7ecf\u6d4e\u6301\u7eed\u5065\u5eb7\u53d1\u5c55\u3002\u6211\u56fd\u5b9e\u65bd\u79ef\u6781\u7684\u8d22\u653f\u653f\u7b56\u548c\u7a33\u5065\u7684\u8d27\u5e01\u653f\u7b56\uff0c\u4e00\u65b9\u9762\u80fd\u4ee5\u653f\u7b56\u7684\u7a33\u5b9a\u6765\u5e94\u5bf9\u5916\u90e8\u73af\u5883\u7684\u4e0d\u7a33\u5b9a\uff0c\u5728\u9762\u5bf9\u4e16\u754c\u73af\u5883\u53d8\u5316\u65f6\u80fd\u968f\u65f6\u628a\u63e1\u4f4f\u6218\u7565\u4e3b\u52a8\u6027\uff1b\u540c\u65f6\u80fd\u591f\u79ef\u6781\u4fc3\u8fdb\u6211\u56fd\u4f9b\u7ed9\u4fa7\u7ed3\u6784\u6027\u6539\u9769\u8fdb\u7a0b\uff0c\u652f\u6301\u89e3\u51b3\u7ecf\u6d4e\u53d1\u5c55\u4e2d\u7684\u6df1\u5c42\u6b21\u7ed3\u6784\u6027\u95ee\u9898\uff0c\u63d0\u5347\u6211\u56fd\u7ecf\u6d4e\u53d1\u5c55\u8d28\u91cf\u548c\u6548\u7387\u3002\u5b8f\u89c2\u653f\u7b56\u7684\u5b9e\u65bd\u4e5f\u5de9\u56fa\u4e86\u6211\u56fd\u5e73\u7a33\u53d1\u5c55\u7684\u7ecf\u6d4e\u57fa\u7840\uff0c\u4e3a\u5168\u9762\u5efa\u6210\u5c0f\u5eb7\u793e\u4f1a\u63d0\u4f9b\u4e86\u575a\u5f3a\u6709\u529b\u7684\u7ecf\u6d4e\u63aa\u65bd\u4fdd\u969c\u3002',
// check_comment: '\u5361\u5c3c\u66fc\u8ba4\u4e3a\u4eba\u7684\u5927\u8111\u5b58\u5728\u4e24\u4e2a\u7cfb\u7edf\uff0c\u4e24\u4e2a\u7cfb\u7edf\u5206\u522b\u6709\u5feb\u4e0e\u6162\u4e24\u79cd\u4f5c\u51b3\u5b9a\u7684\u65b9\u5f0f\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u662f\u540c\u4e00\u8ba4\u8bc6\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u3002\u4e8c\u8005\u65e2\u76f8\u4e92\u5bf9\u7acb\u53c8\u76f8\u4e92\u7edf\u4e00\u3002\u6211\u4eec\u79f0\u4f5c\u7cfb\u7edf1\u548c\u7cfb\u7edf2\uff0c\u4ed6\u7684\u7406\u8bba\u4e0e\u6211\u4eec\u719f\u77e5\u7684\u611f\u6027\u8ba4\u8bc6\u548c\u7406\u6027\u8ba4\u8bc6\u7684\u8fc7\u7a0b\u7684\u4e24\u4e2a\u9636\u6bb5\u6709\u5f88\u76f8\u4f3c\u7684\u7406\u8bba\u6216\u8005\u8bf4\u662f\u4e0d\u662f\u5c5e\u4e8e\u540c\u4e00\u4e2a\u7406\u8bba\u57fa\u7840\u3002',
// get_score: 30,
// score: 40,
// attachments: [],
// upload: {
// type: 'upload-form',
// label: '附件上传:',
// model: 'attachments',
// action: webConf.apiBaseURL + '/util/upload-file',
// data: {
// special: 'exam'
// },
// attrs: {
// multiple: true
// },
// html: `
//
<
div
style
=
"color: #72818c; font-size: 14px;"
>
//
<
p
style
=
"margin: 0;"
>
支持
doc
,
docx
,
ppt
,
xls
,
txt
,
rar
,
zip
,
pdf
,
jpg
,
pic
,
png
格式的文件,文件小于
30
M
。
<
/p
>
//
<
/div
>
// `
// }
// }
// ]
},
exam
:
{},
status
:
{
isStart
:
false
,
startTime
:
''
,
...
...
@@ -398,6 +239,24 @@ export default {
}
}
},
watch
:
{
id
:
{
handler
()
{
this
.
init
()
}
}
},
computed
:
{
// 考试完成
isExamComplete
()
{
// 考试完成,批改完成并且公布成绩
return
this
.
exam
.
is_published
===
1
&&
this
.
exam
.
type
===
2
},
// 是否提交
isSubmited
()
{
return
this
.
exam
.
type
===
1
||
this
.
exam
.
type
===
2
}
},
mounted
()
{
this
.
init
()
this
.
$emit
(
'changeSideBar'
,
''
)
...
...
@@ -443,76 +302,6 @@ export default {
}
return
false
},
initckeditor
()
{
if
(
!
this
.
exam
.
shortAnswerList
)
{
return
}
/* 删除所有 ckeditor 实例 */
const
instances
=
CKEDITOR
.
instances
for
(
const
name
in
instances
)
{
instances
[
name
].
destroy
()
}
for
(
let
i
=
0
;
i
<
this
.
exam
.
shortAnswerList
.
length
;
i
++
)
{
if
(
!
instances
[
'editor-exam'
+
i
])
{
CKEDITOR
.
replace
(
'editor-exam'
+
i
,
{
height
:
300
,
uiColor
:
'#eeeeee'
,
filebrowserImageUploadUrl
:
'/api/ckeditor/img/upload'
,
// resize_enabled: typeof this.props.resizable === 'boolean' ? this.props.resizable : true,
toolbar
:
[
// { name: 'document', items: [ 'Source', '-', 'Save', 'NewPage', 'Preview' ] },
{
name
:
'styles'
,
items
:
[
'Styles'
,
'Format'
,
'Font'
,
'FontSize'
]
},
{
name
:
'colors'
,
items
:
[
'TextColor'
,
'BGColor'
]
},
{
name
:
'tools'
,
items
:
[
'Maximize'
,
'ShowBlocks'
]
},
// { name: 'clipboard', items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
{
name
:
'editing'
,
items
:
[
'Find'
,
'Replace'
]
},
// { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
'/'
,
{
name
:
'basicstyles'
,
items
:
[
'Bold'
,
'Italic'
,
'Underline'
,
'Strike'
,
'Subscript'
,
'Superscript'
,
'-'
,
'RemoveFormat'
]
},
{
name
:
'paragraph'
,
items
:
[
'NumberedList'
,
'BulletedList'
,
'-'
,
'Outdent'
,
'Indent'
,
'-'
,
'Blockquote'
,
'CreateDiv'
,
'-'
,
'JustifyLeft'
,
'JustifyCenter'
,
'JustifyRight'
,
'JustifyBlock'
,
'-'
,
'BidiLtr'
,
'BidiRtl'
]
},
{
name
:
'links'
,
items
:
[
'Link'
,
'Unlink'
,
'Anchor'
]
},
{
name
:
'insert'
,
items
:
[
'Image'
,
'Table'
,
'HorizontalRule'
]
}
]
})
}
this
.
exam
.
shortAnswerList
[
i
].
ckeditor
=
instances
[
'editor-exam'
+
i
]
}
},
init
()
{
this
.
loadExamStatus
()
this
.
loadExamInfo
()
...
...
@@ -522,13 +311,13 @@ export default {
}
this
.
_time
=
setInterval
(()
=>
{
this
.
loadExamStatus
()
if
(
!
this
.
exam
.
type
&&
this
.
status
.
isStart
)
{
if
(
!
this
.
isSubmited
&&
this
.
status
.
isStart
)
{
// console.log(11, '暂存')
this
.
submitExam
({
submitType
:
true
})
// 暂存, submitType: true 暂存;其他或不填为提交
}
/* 到时间 自动提交 */
if
(
!
this
.
exam
.
type
&&
!
this
.
isSubmited
&&
this
.
status
.
isStart
&&
new
Date
(
this
.
status
.
terminateTime
).
getTime
()
-
new
Date
(
this
.
status
.
serverTime
).
getTime
()
<=
...
...
@@ -536,7 +325,7 @@ export default {
)
{
this
.
submitExam
({
submitType
:
false
,
currentTarget
:
{
dataset
:
{}
}
})
}
},
3
000
)
},
10
000
)
},
/* 定时调用 - 考试状态 */
loadExamStatus
()
{
...
...
@@ -615,44 +404,48 @@ export default {
}
else
{
// this.exam.id = _data.id
this
.
exam
.
title
=
_data
.
title
this
.
exam
.
type
=
_data
.
type
this
.
$set
(
this
.
exam
,
'type'
,
_data
.
type
)
this
.
exam
.
score
=
_data
.
score
this
.
exam
.
submitted_time
=
_data
.
submitted_time
this
.
exam
.
isPublished
=
_data
.
isPublished
this
.
status
.
type
=
_data
.
type
this
.
status
.
isPublished
=
_data
.
isPublished
for
(
let
i
=
0
;
i
<
this
.
exam
.
radioList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
_data
.
radioList
.
length
;
j
++
)
{
if
(
_data
.
radioList
[
j
].
id
===
this
.
exam
.
radioList
[
i
].
id
)
{
for
(
const
k
in
_data
.
radioList
[
j
])
{
this
.
exam
.
radioList
[
i
][
k
]
=
_data
.
radioList
[
j
][
k
]
this
.
exam
.
examination
=
this
.
exam
.
examination
.
map
((
exam
,
index
)
=>
{
const
rawExam
=
_data
.
examination
[
index
]
if
(
!
rawExam
)
{
return
exam
}
for
(
let
i
=
0
;
i
<
exam
.
radioList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
radioList
.
length
;
j
++
)
{
if
(
rawExam
.
radioList
[
j
].
id
===
exam
.
radioList
[
i
].
id
)
{
for
(
const
k
in
rawExam
.
radioList
[
j
])
{
exam
.
radioList
[
i
][
k
]
=
rawExam
.
radioList
[
j
][
k
]
}
}
}
}
for
(
let
i
=
0
;
i
<
this
.
exam
.
checkboxList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
_data
.
checkboxList
.
length
;
j
++
)
{
if
(
_data
.
checkboxList
[
j
].
id
===
this
.
exam
.
checkboxList
[
i
].
id
)
{
for
(
const
k
in
_data
.
checkboxList
[
j
])
{
this
.
exam
.
checkboxList
[
i
][
k
]
=
_data
.
checkboxList
[
j
][
k
]
for
(
let
i
=
0
;
i
<
exam
.
checkboxList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
checkboxList
.
length
;
j
++
)
{
if
(
rawExam
.
checkboxList
[
j
].
id
===
exam
.
checkboxList
[
i
].
id
)
{
for
(
const
k
in
rawExam
.
checkboxList
[
j
])
{
exam
.
checkboxList
[
i
][
k
]
=
rawExam
.
checkboxList
[
j
][
k
]
}
}
}
}
for
(
let
i
=
0
;
i
<
this
.
exam
.
shortAnswerList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
_data
.
shortAnswerList
.
length
;
j
++
)
{
for
(
let
i
=
0
;
i
<
exam
.
shortAnswerList
.
length
;
i
++
)
{
for
(
let
j
=
0
;
j
<
rawExam
.
shortAnswerList
.
length
;
j
++
)
{
if
(
_data
.
shortAnswerList
[
j
].
id
===
this
.
exam
.
shortAnswerList
[
i
].
id
rawExam
.
shortAnswerList
[
j
].
id
===
exam
.
shortAnswerList
[
i
].
id
)
{
for
(
const
k
in
_data
.
shortAnswerList
[
j
])
{
this
.
exam
.
shortAnswerList
[
i
][
k
]
=
_data
.
shortAnswerList
[
j
][
k
]
for
(
const
k
in
rawExam
.
shortAnswerList
[
j
])
{
exam
.
shortAnswerList
[
i
][
k
]
=
rawExam
.
shortAnswerList
[
j
][
k
]
}
}
}
}
return
exam
})
}
})
.
catch
(
e
=>
{
...
...
@@ -668,7 +461,6 @@ export default {
)
{
/* 滚动到头部 */
document
.
querySelector
(
'.play-paper'
).
scrollTop
=
0
this
.
initckeditor
()
}
})
},
...
...
@@ -680,45 +472,42 @@ export default {
this
.
$message
.
error
(
'已做过,不能再提交'
)
return
}
const
body
=
{
answers
:
{},
type
:
!
e
.
submitType
?
1
:
0
}
// type: 0 缓存;type: 1 提交
body
.
answers
.
radioList
=
[]
for
(
let
i
=
0
;
i
<
this
.
exam
.
radioList
.
length
;
i
++
)
{
const
tmp
=
this
.
exam
.
radioList
[
i
]
const
body
=
{
answers
:
[],
type
:
!
e
.
submitType
?
1
:
0
}
// type: 0 缓存;type: 1 提交
for
(
let
k
=
0
;
k
<
this
.
exam
.
examination
.
length
;
k
++
)
{
const
exam
=
this
.
exam
.
examination
[
k
]
const
radioList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
radioList
.
length
;
i
++
)
{
const
tmp
=
exam
.
radioList
[
i
]
if
(
!
tmp
.
user_answer
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有单选题未做,不能提交'
)
return
}
body
.
answers
.
radioList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
radioList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
}
body
.
answers
.
checkboxList
=
[]
for
(
let
i
=
0
;
i
<
this
.
exam
.
checkboxList
.
length
;
i
++
)
{
const
tmp
=
this
.
exam
.
checkboxList
[
i
]
const
checkboxList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
checkboxList
.
length
;
i
++
)
{
const
tmp
=
exam
.
checkboxList
[
i
]
if
(
!
tmp
.
user_answer
.
length
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有多选题未做,不能提交'
)
return
}
body
.
answers
.
checkboxList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
checkboxList
.
push
({
id
:
tmp
.
id
,
user_answer
:
tmp
.
user_answer
})
}
body
.
answers
.
shortAnswerList
=
[]
for
(
let
i
=
0
;
i
<
this
.
exam
.
shortAnswerList
.
length
;
i
++
)
{
const
tmp
=
this
.
exam
.
shortAnswerList
[
i
]
tmp
.
user_answer
=
tmp
.
ckeditor
.
getData
()
const
shortAnswerList
=
[]
for
(
let
i
=
0
;
i
<
exam
.
shortAnswerList
.
length
;
i
++
)
{
const
tmp
=
exam
.
shortAnswerList
[
i
]
if
(
!
tmp
.
user_answer
&&
!
e
.
submitType
)
{
this
.
$message
.
error
(
'还有简答题未做,不能提交'
)
return
}
body
.
answers
.
shortAnswerList
.
push
({
shortAnswerList
.
push
({
id
:
tmp
.
id
,
user_answer
:
Base64
.
encode
(
tmp
.
user_answer
),
attachments
:
tmp
.
attachments
})
}
body
.
answers
[
k
]
=
{
radioList
,
checkboxList
,
shortAnswerList
}
}
body
.
answers
=
JSON
.
stringify
(
body
.
answers
)
let
loading
=
null
if
(
!
e
.
submitType
)
{
...
...
@@ -793,13 +582,6 @@ export default {
loading
.
close
()
})
}
},
watch
:
{
id
:
{
handler
()
{
this
.
init
()
}
}
}
}
</
script
>
...
...
@@ -886,7 +668,9 @@ export default {
color
:
#090
;
}
.exam
.q-group
.result
{
float
:
right
;
clear
:
both
;
display
:
flex
;
justify-content
:
flex-end
;
font-size
:
0
.18rem
;
color
:
#3f3b3a
;
margin-right
:
0
;
...
...
@@ -938,4 +722,11 @@ export default {
line-height
:
19
;
text-align
:
center
;
}
.analysis
{
clear
:
both
;
display
:
flex
;
margin
:
20px
0
;
padding
:
0
20px
;
border
:
1px
solid
#c9c9c9
7a
;
}
</
style
>
client/src/pages/player/index.vue
浏览文件 @
5632ae6b
...
...
@@ -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
...
...
client/src/pages/player/live/live.vue
浏览文件 @
5632ae6b
...
...
@@ -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
(()
=>
{
...
...
client/src/pages/player/rightSide/sideChapterList.vue
浏览文件 @
5632ae6b
...
...
@@ -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
}
...
...
client/src/router/routes.js
浏览文件 @
5632ae6b
//
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
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论