Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
X
x-learn
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
x-learn
Commits
49505039
提交
49505039
authored
11月 03, 2020
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
update
上级
f63bcf66
隐藏空白字符变更
内嵌
并排
正在显示
50 个修改的文件
包含
4036 行增加
和
1264 行删除
+4036
-1264
account.js
src/api/account.js
+4
-0
course.js
src/api/course.js
+0
-34
my.js
src/api/my.js
+6
-8
pay.js
src/api/pay.js
+0
-25
CourseList.vue
src/components/CourseList.vue
+1
-1
CourseListItem.vue
src/components/CourseListItem.vue
+5
-2
CourseTag.vue
src/components/CourseTag.vue
+0
-88
aside.vue
src/components/layout/aside.vue
+2
-2
index.vue
src/components/layout/index.vue
+3
-2
api.js
src/modules/viewer/api.js
+175
-0
play-icons.png
src/modules/viewer/assets/play-icons.png
+0
-0
chapter.vue
src/modules/viewer/components/aside/chapter.vue
+168
-0
index.vue
src/modules/viewer/components/aside/index.vue
+86
-0
lecture.vue
src/modules/viewer/components/aside/lecture.vue
+55
-0
container.vue
src/modules/viewer/components/common/container.vue
+24
-0
editor.vue
src/modules/viewer/components/common/editor.vue
+146
-0
upload.vue
src/modules/viewer/components/common/upload.vue
+113
-0
layout.vue
src/modules/viewer/components/layout.vue
+56
-0
chapterLive.vue
src/modules/viewer/components/live/chapterLive.vue
+92
-0
chapterPlayer.vue
src/modules/viewer/components/player/chapterPlayer.vue
+328
-0
pptPlayer.vue
src/modules/viewer/components/player/pptPlayer.vue
+159
-0
videoPlayer.vue
src/modules/viewer/components/player/videoPlayer.vue
+72
-0
chapterRead.vue
src/modules/viewer/components/read/chapterRead.vue
+44
-0
courseRead.vue
src/modules/viewer/components/read/courseRead.vue
+39
-0
fileList.vue
src/modules/viewer/components/read/fileList.vue
+72
-0
chapterExam.vue
src/modules/viewer/components/work/chapterExam.vue
+326
-0
chapterTest.vue
src/modules/viewer/components/work/chapterTest.vue
+271
-0
chapterWork.vue
src/modules/viewer/components/work/chapterWork.vue
+359
-0
courseExam.vue
src/modules/viewer/components/work/courseExam.vue
+398
-0
courseWork.vue
src/modules/viewer/components/work/courseWork.vue
+252
-0
examItem.vue
src/modules/viewer/components/work/examItem.vue
+298
-0
index.vue
src/modules/viewer/components/work/index.vue
+40
-0
index.vue
src/modules/viewer/index.vue
+273
-0
routes.js
src/modules/viewer/routes.js
+13
-0
password.vue
src/pages/account/password.vue
+36
-4
item.vue
src/pages/course/learn/item.vue
+3
-3
api.js
src/pages/course/tag/api.js
+0
-10
courseTagMessage.vue
src/pages/course/tag/components/courseTagMessage.vue
+0
-149
messageCard.vue
src/pages/course/tag/components/messageCard.vue
+0
-104
searchTagMessage.vue
src/pages/course/tag/components/searchTagMessage.vue
+0
-120
tagMessage.vue
src/pages/course/tag/components/tagMessage.vue
+0
-151
index.vue
src/pages/course/tag/index.vue
+36
-170
item.vue
src/pages/course/tag/item.vue
+25
-89
list.vue
src/pages/course/tag/list.vue
+0
-158
index.vue
src/pages/feedback/index.vue
+20
-5
index.vue
src/pages/my/course/index.vue
+10
-1
index.vue
src/pages/my/learn/index.vue
+0
-49
item.vue
src/pages/my/learn/item.vue
+0
-86
routes.js
src/router/routes.js
+19
-2
axios.js
src/utils/axios.js
+7
-1
没有找到文件。
src/api/account.js
浏览文件 @
49505039
...
@@ -8,6 +8,10 @@ export function login(data) {
...
@@ -8,6 +8,10 @@ export function login(data) {
export
function
bindWechat
(
data
)
{
export
function
bindWechat
(
data
)
{
return
httpRequest
.
post
(
'/api/passport/rest/wechat/bind-unionid'
,
data
)
return
httpRequest
.
post
(
'/api/passport/rest/wechat/bind-unionid'
,
data
)
}
}
// 修改密码
export
function
updatePassword
(
data
)
{
return
httpRequest
.
post
(
'/api/usercenter/user/change-pwd-by-cookie'
,
data
)
}
// 重置密码
// 重置密码
export
function
resetPassword
(
data
)
{
export
function
resetPassword
(
data
)
{
return
httpRequest
.
post
(
'/api/usercenter/user/update-pwd'
,
data
)
return
httpRequest
.
post
(
'/api/usercenter/user/update-pwd'
,
data
)
...
...
src/api/course.js
浏览文件 @
49505039
import
httpRequest
from
'@/utils/axios'
import
httpRequest
from
'@/utils/axios'
/**
* 获取免费课程列表
*/
export
function
getFreeCourseList
()
{
return
httpRequest
.
get
(
'/api/zy/v2/education/freecourse'
)
}
/**
/**
* 获取课程列表
* 获取课程列表
*/
*/
...
@@ -42,31 +36,3 @@ export function getCourseTagList(courseId) {
...
@@ -42,31 +36,3 @@ export function getCourseTagList(courseId) {
export
function
getCourseTag
(
tagId
)
{
export
function
getCourseTag
(
tagId
)
{
return
httpRequest
.
get
(
`/api/zy/v2/education/tag/
${
tagId
}
`
)
return
httpRequest
.
get
(
`/api/zy/v2/education/tag/
${
tagId
}
`
)
}
}
/**
* 获取搜索记录
*/
export
function
getSearchTips
()
{
return
httpRequest
.
get
(
'/api/zy/v2/education/search/tips'
)
}
/**
* 知识点搜索
*/
export
function
getSearchTagList
(
data
)
{
return
httpRequest
.
post
(
'/api/zy/v2/education/search/tag'
,
data
)
}
/**
* 视频课程搜索
*/
export
function
getSearchCourseVideoList
(
data
)
{
return
httpRequest
.
post
(
'/api/zy/v2/education/search/chapter1'
,
data
)
}
/**
* 课程搜索
*/
export
function
getSearchCourseList
(
data
)
{
return
httpRequest
.
post
(
'/api/zy/v2/education/search/chapter2'
,
data
)
}
src/api/my.js
浏览文件 @
49505039
...
@@ -46,14 +46,12 @@ export function cacheQuestion(data) {
...
@@ -46,14 +46,12 @@ export function cacheQuestion(data) {
/* 意见反馈 */
/* 意见反馈 */
export
function
submitFeedback
(
data
)
{
export
function
submitFeedback
(
data
)
{
return
httpRequest
.
post
(
'/api/zy/v2/feedback/commit'
,
data
,
{
return
httpRequest
.
post
(
'/api/zy/v2/feedback/commit'
,
data
)
}
/* 获取考试状态 */
export
function
getExamStatus
(
data
)
{
return
httpRequest
.
get
(
'/api/zy/v2/examination/examination-papers-status'
,
data
,
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
}
headers
:
{
'Content-Type'
:
'multipart/form-data'
}
})
})
}
}
// /* 获取考试状态 */
// export function getExamStatus(data) {
// return httpRequest.get('/api/zy/v2/examination/examination-papers-status', data, {
// headers: { 'Content-Type': 'multipart/form-data' }
// })
// }
src/api/pay.js
deleted
100644 → 0
浏览文件 @
f63bcf66
import
httpRequest
from
'@/utils/axios'
/**
* 获取商品详情
*/
export
function
getGoodsDetails
(
id
)
{
return
httpRequest
.
get
(
`/api/zy/v2/mall/product/
${
id
}
`
)
}
/**
* 获取我的购买订单
*/
export
function
getMyOrder
()
{
return
httpRequest
.
get
(
'/api/zy/v2/mall/order/my'
)
}
/* 获取openid */
export
function
getOpenid
(
data
)
{
return
httpRequest
.
get
(
'/usercenter/user/get-user-openid'
,
data
)
}
/* 监听支付状态 */
export
function
getOrderStatus
()
{
return
httpRequest
.
get
(
'/api/zy/v2/mall/order/status'
)
}
src/components/CourseList.vue
浏览文件 @
49505039
<
template
>
<
template
>
<div
class=
"course-list"
v-loading=
"!loaded"
>
<div
class=
"course-list"
element-loading-text=
"加载中..."
v-loading=
"!loaded"
>
<template
v-if=
"list.length"
>
<template
v-if=
"list.length"
>
<course-list-item
v-for=
"item in list"
:data=
"item"
:key=
"item.id"
v-bind=
"$attrs"
v-on=
"$listeners"
/>
<course-list-item
v-for=
"item in list"
:data=
"item"
:key=
"item.id"
v-bind=
"$attrs"
v-on=
"$listeners"
/>
</
template
>
</
template
>
...
...
src/components/CourseListItem.vue
浏览文件 @
49505039
...
@@ -5,9 +5,9 @@
...
@@ -5,9 +5,9 @@
<div
class=
"course-item-content"
>
<div
class=
"course-item-content"
>
<div
class=
"course-item__title"
>
{{
data
.
title
}}
</div>
<div
class=
"course-item__title"
>
{{
data
.
title
}}
</div>
<div
class=
"course-item__tools"
>
<div
class=
"course-item__tools"
>
<div
class=
"course-item__text course-item__text__freevideo"
>
{{
data
.
free_video_num
}}
个免费视频
</div>
<div
class=
"course-item__text course-item__text__video"
>
{{
data
.
video_num
}}
节视频课
</div>
<div
class=
"course-item__text course-item__text__course"
>
含
{{
data
.
course_num
}}
节课
</div>
<div
class=
"course-item__text course-item__text__course"
>
含
{{
data
.
course_num
}}
节课
</div>
<div
class=
"course-item__text course-item__text__video"
>
{{
data
.
video_num
}}
节视频课
</div>
<div
class=
"course-item__text course-item__text__freevideo"
>
{{
data
.
free_video_num
}}
个免费视频
</div>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -105,6 +105,9 @@ export default {
...
@@ -105,6 +105,9 @@ export default {
padding
:
20px
0
;
padding
:
20px
0
;
border-bottom
:
1px
solid
#eee
;
border-bottom
:
1px
solid
#eee
;
cursor
:
pointer
;
cursor
:
pointer
;
&
:hover
{
color
:
#c01540
;
}
.name
{
.name
{
flex
:
1
;
flex
:
1
;
font-size
:
18px
;
font-size
:
18px
;
...
...
src/components/CourseTag.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<el-collapse
v-model=
"activeNames"
>
<el-collapse-item
:title=
"item.name"
:name=
"item.id"
v-for=
"item in data"
:key=
"item.id"
>
<template
#
right-icon
>
<van-icon
name=
"arrow"
/>
<van-icon
name=
"arrow-down"
/>
</
template
>
<ul>
<li
v-for=
"subItem in item.children"
:key=
"subItem.id"
@
click=
"$emit('on-click', subItem)"
>
<div
class=
"name"
>
{{ subItem.name }}
<
template
v-if=
"subItem.free"
>
(免费)
</
template
>
</div>
<div
class=
"progress"
v-if=
"showProgress"
>
{{ progressText(subItem.video_progress, subItem.free) }}
</div>
</li>
</ul>
</el-collapse-item>
</el-collapse>
</template>
<
script
>
export
default
{
props
:
{
courseId
:
{
type
:
String
},
data
:
{
type
:
Array
,
required
:
true
,
default
:
()
=>
[]
},
showProgress
:
{
type
:
Boolean
,
default
:
false
}
},
data
()
{
return
{
activeNames
:
[]
}
},
computed
:
{
isVip
()
{
return
this
.
$store
.
state
.
isVip
}
},
methods
:
{
progressText
(
value
,
isFree
)
{
value
=
parseInt
(
value
)
if
(
value
===
0
)
{
return
isFree
||
this
.
isVip
?
'未开始'
:
'未开通'
}
if
(
value
===
100
)
{
return
'已学完'
}
return
`已学
${
value
}
%`
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
::v-deep
.van-cell
{
padding-left
:
0
;
padding-right
:
0
;
}
::v-deep
.el-collapse-item__content
{
padding
:
0
;
}
::v-deep
.el-collapse-item--border
::after
{
left
:
0
;
right
:
0
;
}
::v-deep
.el-collapse-item__title--expanded
::after
{
display
:
none
;
}
li
{
display
:
flex
;
padding
:
10px
0
;
&
:first-child
{
padding-top
:
0
;
}
&
:last-child
{
padding-bottom
:
20px
;
}
.name
{
flex
:
1
;
font-size
:
13px
;
overflow
:
hidden
;
}
.progress
{
margin-left
:
20px
;
font-size
:
12px
;
color
:
#999
;
}
}
</
style
>
src/components/layout/aside.vue
浏览文件 @
49505039
...
@@ -30,7 +30,7 @@ export default {
...
@@ -30,7 +30,7 @@ export default {
{
title
:
'错题集合'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'错题集合'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'收藏试题'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'收藏试题'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'必考考点'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'必考考点'
,
icon
:
''
,
path
:
'/exam'
},
{
title
:
'考证课程'
,
icon
:
''
,
path
:
'/course
/learn
'
},
{
title
:
'考证课程'
,
icon
:
''
,
path
:
'/course'
},
{
title
:
'意见反馈'
,
icon
:
''
,
path
:
'/feedback'
},
{
title
:
'意见反馈'
,
icon
:
''
,
path
:
'/feedback'
},
{
title
:
'联系客服'
,
icon
:
''
,
path
:
'/contact'
}
{
title
:
'联系客服'
,
icon
:
''
,
path
:
'/contact'
}
]
]
...
@@ -85,7 +85,7 @@ export default {
...
@@ -85,7 +85,7 @@ export default {
img
{
img
{
width
:
100%
;
width
:
100%
;
height
:
100%
;
height
:
100%
;
object-fit
:
co
ntain
;
object-fit
:
co
ver
;
}
}
}
}
.user-tools
{
.user-tools
{
...
...
src/components/layout/index.vue
浏览文件 @
49505039
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
<div
class=
"app-layout"
>
<div
class=
"app-layout"
>
<app-header
/>
<app-header
/>
<div
class=
"app-layout-bd"
>
<div
class=
"app-layout-bd"
>
<app-aside
v-bind=
"$attrs"
/>
<app-aside
v-bind=
"$attrs"
v-if=
"showAside"
/>
<app-main
/>
<app-main
/>
</div>
</div>
</div>
</div>
...
@@ -13,7 +13,8 @@ import AppHeader from './header'
...
@@ -13,7 +13,8 @@ import AppHeader from './header'
import
AppAside
from
'./aside'
import
AppAside
from
'./aside'
import
AppMain
from
'./main'
import
AppMain
from
'./main'
export
default
{
export
default
{
components
:
{
AppHeader
,
AppAside
,
AppMain
}
components
:
{
AppHeader
,
AppAside
,
AppMain
},
props
:
{
showAside
:
{
type
:
Boolean
,
default
:
true
}
}
}
}
</
script
>
</
script
>
...
...
src/modules/viewer/api.js
0 → 100644
浏览文件 @
49505039
import
BaseAPI
from
'@/api/base_api'
const
httpRequest
=
new
BaseAPI
(
webConf
)
/**
* 获取课程详情
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export
function
getCourse
(
semesterId
,
courseId
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/courses/
${
semesterId
}
/
${
courseId
}
`
)
}
/**
* 获取章节资源详情
* @param {string} vid 资源ID
*/
export
function
getChapterVideo
(
vid
)
{
return
httpRequest
.
post
(
'/api/lms/v2/education/video-streaming'
,
{
vid
},
{
headers
:
{
'Content-Type'
:
'application/json'
}
}
)
}
/**
* 获取章节资源详情
* @param {string} vid 章节的资源ID
*/
export
function
getChapterVideoAliyun
(
vid
)
{
return
httpRequest
.
post
(
'/api/lms/v2/education/aliyun-video-streaming'
,
{
vid
},
{
headers
:
{
'Content-Type'
:
'application/json'
}
}
)
}
/**
* 获取章节视频播放进度
* @param {string} semesterId 学期ID
* @param {string} resourseId 章节的资源ID
* @param {Object} params
*/
export
function
getChapterVideoProgress
(
semesterId
,
resourseId
,
params
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/video/
${
semesterId
}
/
${
resourseId
}
/device`
,
params
)
}
/**
* 更新章节视频播放进度
* @param {Object} params
*/
export
function
updateChapterVideoProgress
(
params
)
{
return
httpRequest
.
get
(
'/api/lms/v2/analytics/upload-video'
,
params
)
}
/**
* 获取章节作业
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} resourseId 章节的资源ID
*/
export
function
getChapterHomework
(
semesterId
,
courseId
,
resourseId
)
{
return
httpRequest
.
get
(
`/api/lms/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
(
`/api/lms/v2/education/homeworks/
${
semesterId
}
/
${
courseId
}
/
${
chapterId
}
/deadline`
)
}
/**
* 提交考试
*/
export
function
sbumitChapterHomework
(
params
)
{
return
httpRequest
.
post
(
'/api/lms/v2/education/homeworks'
,
params
,
{
headers
:
{
'Content-Type'
:
'application/json'
}
})
}
/**
* 上传文件
*/
export
function
uploadFile
(
data
)
{
return
httpRequest
.
post
(
'/api/lms/util/upload-file'
,
data
,
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
}
})
}
/**
* 获取课程大作业详情
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export
function
getCourseWork
(
semesterId
,
courseId
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/courses/
${
semesterId
}
/
${
courseId
}
/essay`
)
}
/**
* 提交课程大作业
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export
function
updateCourseWork
(
semesterId
,
courseId
,
data
)
{
return
httpRequest
.
post
(
`/api/lms/v2/education/courses/
${
semesterId
}
/
${
courseId
}
/essay`
,
data
,
{
headers
:
{
'Content-Type'
:
'multipart/form-data'
}
}
)
}
/**
* 获取课程考试试题
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
*/
export
function
getCourseExam
(
semesterId
,
courseId
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/
${
semesterId
}
/
${
courseId
}
/examination`
)
}
/**
* 获取课程考试状态
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} examId 试题ID
*/
export
function
getCourseExamStatus
(
semesterId
,
courseId
,
examId
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/
${
semesterId
}
/
${
courseId
}
/examination/
${
examId
}
/status`
)
}
/**
* 提交课程考试
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} examId 试题ID
*/
export
function
submitCourseExam
(
semesterId
,
courseId
,
examId
,
data
)
{
return
httpRequest
.
post
(
`/api/lms/v2/education/
${
semesterId
}
/
${
courseId
}
/examination/
${
examId
}
/sheet`
,
data
,
{
headers
:
{
'Content-Type'
:
'application/x-www-form-urlencoded'
}
}
)
}
/**
* 获取课程考试结果
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} examId 试题ID
*/
export
function
getCourseExamResult
(
semesterId
,
courseId
,
examId
,
params
)
{
return
httpRequest
.
get
(
`/api/lms/v2/education/
${
semesterId
}
/
${
courseId
}
/examination/
${
examId
}
/sheet`
,
params
)
}
src/modules/viewer/assets/play-icons.png
0 → 100644
浏览文件 @
49505039
2.7 KB
src/modules/viewer/components/aside/chapter.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<ul
class=
"chapter-list"
>
<li
class=
"chapter-item"
v-for=
"item in chapters"
:key=
"item.id"
>
<h4>
{{
item
.
name
}}
</h4>
<ul
class=
"chapter-item-list"
>
<li
v-for=
"subItem in item.children"
:key=
"subItem.id"
@
click=
"onClick(subItem)"
:class=
"
{ 'is-active': subItem.id === (active ? active.id : '') }"
>
<span
class=
"chapter-item-list__name"
>
{{
subItem
.
name
|
showName
(
subItem
)
}}
</span>
<i
class=
"el-icon"
:class=
"genIconClass(subItem.type)"
></i>
</li>
</ul>
</li>
</ul>
</
template
>
<
script
>
export
default
{
props
:
{
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
},
chapters
:
{
type
:
Array
,
default
:
()
=>
[]
},
// 当前选中的章节
active
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
data
()
{
return
{}
},
filters
:
{
showName
(
name
,
data
)
{
if
([
5
,
8
].
includes
(
data
.
type
)
&&
data
.
live
)
{
return
`
${
name
}
(
${
data
.
live
.
start_time
}
)`
}
return
name
}
},
methods
:
{
genIconClass
(
type
)
{
const
map
=
{
2
:
'el-icon-self-iconset0481'
,
3
:
'el-icon-edit-outline'
,
4
:
'el-icon-self-cc-book'
}
return
map
[
type
]
||
'el-icon-self-cc-book'
},
onClick
(
data
)
{
if
(
data
.
type
===
1
)
{
return
}
// zoom直播
if
(
data
.
type
===
8
)
{
const
live
=
data
.
live
const
hasRecordUrl
=
live
.
enable_record
&&
live
.
record_url
if
([
3
,
5
].
includes
(
live
.
live_status
)
&&
!
hasRecordUrl
)
{
this
.
$message
.
error
(
'直播结束'
)
return
}
window
.
open
(
live
.
record_url
||
live
.
join_url
)
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
}
})
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
/* 章列表样式 */
.chapter-list
{
margin
:
0
;
padding
:
0
;
line-height
:
1
.6
;
overflow
:
hidden
;
.chapter-item
{
h4
{
padding
:
10px
22px
;
margin
:
0
;
font-size
:
15px
;
color
:
#b0b0b0
;
background-color
:
#2f2f2f
;
}
/* 节列表样式 */
.chapter-item-list
{
margin
:
0
;
padding
:
0
;
line-height
:
1
.6
;
overflow
:
hidden
;
li
{
position
:
relative
;
&
.is-active
{
background
:
#3c3c3c
;
.chapter-item-list__name
{
color
:
#b49441
;
}
}
&
:hover
{
background
:
#3c3c3c
;
}
&
:before
{
display
:
block
;
content
:
''
;
position
:
absolute
;
left
:
13px
;
top
:
16px
;
z-index
:
10
;
width
:
18px
;
height
:
18px
;
background
:
#5b5b5b
;
border
:
2px
solid
#5b5b5b
;
border-radius
:
50%
;
}
&
:after
{
display
:
block
;
content
:
''
;
position
:
absolute
;
left
:
22px
;
top
:
0
;
z-index
:
5
;
width
:
1px
;
height
:
100px
;
background
:
#616161
;
}
}
.chapter-item-list__name
{
display
:
block
;
padding
:
15px
35px
15px
40px
;
font-size
:
14px
;
color
:
#909090
;
text-decoration
:
none
;
cursor
:
pointer
;
}
}
/* 章节后面小图标的样式 */
.el-icon
{
position
:
absolute
;
font-size
:
16px
;
right
:
10px
;
top
:
50%
;
transform
:
translateY
(
-50%
);
color
:
#a0a0a0
;
}
}
}
</
style
>
src/modules/viewer/components/aside/index.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<aside
class=
"course-viewer-aside"
>
<el-tabs
v-model=
"activeName"
>
<el-tab-pane
label=
"章节"
name=
"0"
>
<div
class=
"tab-pane"
>
<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
:ppts=
"ppts"
:pptIndex=
"pptIndex"
v-on=
"$listeners"
></aside-lecture>
</div>
</el-tab-pane>
</el-tabs>
</aside>
</
template
>
<
script
>
import
AsideChapter
from
'./chapter.vue'
import
AsideLecture
from
'./lecture.vue'
export
default
{
props
:
{
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 章节
chapters
:
{
type
:
Array
,
default
:
()
=>
[]
},
// 讲义
ppts
:
{
type
:
Array
,
default
:
()
=>
[]
},
// 当前选中的章节
active
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 当前选择的PPT
pptIndex
:
{
type
:
Number
,
default
:
0
}
},
components
:
{
AsideChapter
,
AsideLecture
},
data
()
{
return
{
activeName
:
'0'
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.course-viewer-aside
{
width
:
350px
;
min-height
:
100vh
;
background-color
:
#232323
;
}
.tab-pane
{
height
:
calc
(
100vh
-
56px
);
overflow-y
:
auto
;
}
::v-deep
.el-tabs__header
{
margin
:
0
;
}
::v-deep
.el-tabs__nav
{
float
:
none
;
display
:
flex
;
}
::v-deep
.el-tabs__item
{
flex
:
1
;
height
:
56px
;
font-size
:
16px
;
line-height
:
56px
;
color
:
#909090
;
text-align
:
center
;
&
.is-active
{
color
:
#b49441
;
}
}
::v-deep
.el-tabs__active-bar
,
::v-deep
.el-tabs__nav-wrap
::after
{
display
:
none
;
}
</
style
>
src/modules/viewer/components/aside/lecture.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<ul
class=
"lecture-list"
>
<li
v-for=
"(item, index) in ppts"
:key=
"item.id"
@
click=
"onClick(index)"
:class=
"
{'is-active': index === activeIndex}"
>
<img
:src=
"item.ppt_url"
/>
</li>
</ul>
</
template
>
<
script
>
export
default
{
props
:
{
// 当前选择的PPT
pptIndex
:
{
type
:
Number
,
default
:
0
},
ppts
:
{
type
:
Array
,
default
:
()
=>
[]
}
},
data
()
{
return
{
activeIndex
:
this
.
pptIndex
}
},
watch
:
{
pptIndex
(
index
)
{
this
.
activeIndex
=
index
}
},
methods
:
{
// 点击PPT
onClick
(
index
)
{
this
.
activeIndex
=
index
this
.
$emit
(
'change-ppt'
,
index
)
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.lecture-list
{
padding
:
0
16px
;
li
{
padding
:
8px
16px
;
cursor
:
pointer
;
&
.is-active
{
background
:
#888
;
}
img
{
width
:
100%
;
}
}
}
</
style
>
src/modules/viewer/components/common/container.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"course-viewer-content"
>
<div
class=
"course-viewer-content-hd"
>
<slot
name=
"header"
>
<h3
class=
"course-viewer-content-hd__title"
>
<slot
name=
"title"
>
{{
title
}}
</slot>
</h3>
<div
class=
"course-viewer-content-hd__aside"
>
<slot
name=
"header-aside"
></slot>
</div>
</slot>
</div>
<div
class=
"course-viewer-content-bd"
>
<slot></slot>
</div>
</div>
</
template
>
<
script
>
export
default
{
name
:
'Continaer'
,
props
:
{
title
:
String
}
}
</
script
>
src/modules/viewer/components/common/editor.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"editor"
>
<textarea
name=
"editor"
:id=
"textareaElementId"
:disabled=
"disabled"
></textarea>
</div>
</
template
>
<
script
>
import
{
uniqueId
}
from
'lodash'
export
default
{
name
:
'VEditor'
,
props
:
{
value
:
{
type
:
String
},
disabled
:
{
type
:
Boolean
,
default
:
false
}
},
data
()
{
return
{
textareaElementId
:
uniqueId
(
'editor_'
),
ckEditor
:
null
}
},
watch
:
{
value
(
val
)
{
if
(
this
.
ckEditor
&&
this
.
ckEditor
.
getData
()
!==
val
)
{
this
.
ckEditor
.
setData
(
val
)
}
},
disabled
(
val
)
{
if
(
this
.
ckEditor
&&
this
.
ckEditor
.
instanceReady
)
{
this
.
ckEditor
.
setReadOnly
(
val
)
}
}
},
methods
:
{
createEditor
()
{
const
config
=
{
height
:
400
,
uiColor
:
'#eeeeee'
,
filebrowserImageUploadUrl
:
'/api/ck/form/ckeditor-upload'
,
fileTools_requestHeaders
:
{
tenant
:
'sofia'
},
// 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'
]
}
]
}
// if (this.disabled !== null) {
// console.log(this.disabled)
// config.readOnly = this.disabled
// }
const
editor
=
(
this
.
ckEditor
=
CKEDITOR
.
replace
(
this
.
textareaElementId
,
config
))
editor
.
on
(
'instanceReady'
,
()
=>
{
const
data
=
this
.
value
editor
.
fire
(
'lockSnapshot'
)
editor
.
setData
(
data
,
{
callback
:
()
=>
{
this
.
bindEvent
()
const
newData
=
editor
.
getData
()
// Locking the snapshot prevents the 'change' event.
// Trigger it manually to update the bound data.
if
(
data
!==
newData
)
{
this
.
$once
(
'input'
,
()
=>
{
this
.
$emit
(
'ready'
,
editor
)
})
this
.
$emit
(
'input'
,
newData
)
}
else
{
this
.
$emit
(
'ready'
,
editor
)
}
editor
.
fire
(
'unlockSnapshot'
)
}
})
editor
.
setReadOnly
(
this
.
disabled
)
})
},
bindEvent
()
{
const
editor
=
this
.
ckEditor
editor
.
on
(
'change'
,
evt
=>
{
const
data
=
editor
.
getData
()
if
(
this
.
value
!==
data
)
{
this
.
$emit
(
'input'
,
data
,
evt
,
editor
)
}
})
editor
.
on
(
'focus'
,
evt
=>
{
this
.
$emit
(
'focus'
,
evt
,
editor
)
})
editor
.
on
(
'blur'
,
evt
=>
{
this
.
$emit
(
'blur'
,
evt
,
editor
)
})
}
},
mounted
()
{
this
.
createEditor
()
},
beforeDestroy
()
{
this
.
ckEditor
&&
this
.
ckEditor
.
destroy
()
this
.
ckEditor
=
null
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
*
{
margin
:
0
;
padding
:
0
;
}
</
style
>
src/modules/viewer/components/common/upload.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"upload"
>
<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
>
<div
class=
"el-upload__tips"
>
<slot
name=
"tip"
></slot>
</div>
</
template
>
</el-upload>
<div
class=
"file-list"
v-if=
"fileList.length"
>
<div
class=
"file-list-item"
v-for=
"(item, index) in fileList"
:key=
"index"
>
<a
:href=
"item.url"
:download=
"item.name"
target=
"_blank"
>
<i
class=
"el-icon-document"
></i>
{{ item.name }}
</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=
"item.url"
:download=
"item.name"
target=
"_blank"
>
<el-tooltip
effect=
"dark"
content=
"下载"
>
<i
class=
"el-icon-download"
></i>
</el-tooltip>
</a>
</div>
</div>
</div>
</div>
</template>
<
script
>
import
*
as
api
from
'../../api'
export
default
{
name
:
'VUpload'
,
props
:
{
value
:
{
type
:
[
String
,
Array
]
},
disabled
:
{
type
:
Boolean
,
default
:
false
}
},
data
()
{
return
{
fileList
:
[]
}
},
watch
:
{
value
:
{
immediate
:
true
,
handler
(
value
)
{
if
(
!
value
)
{
return
}
let
fileList
=
[]
if
(
Array
.
isArray
(
value
))
{
fileList
=
value
.
map
(
item
=>
{
return
{
name
:
item
.
name
||
item
,
url
:
item
.
url
||
item
}
})
}
else
{
fileList
.
push
({
name
:
'附件下载'
,
url
:
value
})
}
this
.
fileList
=
fileList
}
}
},
methods
:
{
httpRequest
(
xhr
)
{
api
.
uploadFile
({
file
:
xhr
.
file
})
.
then
(
response
=>
{
if
(
response
.
success
)
{
if
(
Array
.
isArray
(
this
.
value
))
{
this
.
fileList
.
push
({
name
:
xhr
.
file
.
name
,
url
:
response
.
url
})
this
.
$emit
(
'input'
,
this
.
fileList
)
}
else
{
this
.
fileList
=
[
response
.
url
]
this
.
$emit
(
'input'
,
response
.
url
)
}
}
})
.
catch
(
error
=>
{
this
.
$message
.
error
(
error
.
message
)
})
},
handleRemove
(
index
)
{
this
.
fileList
.
splice
(
index
,
1
)
this
.
$emit
(
'input'
,
Array
.
isArray
(
this
.
value
)
?
this
.
fileList
:
''
)
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.file-list-item
{
display
:
flex
;
margin-bottom
:
10px
;
padding
:
0
10px
;
justify-content
:
space-between
;
line-height
:
30px
;
background-color
:
#fff
;
border-radius
:
4px
;
a
{
text-decoration
:
none
;
color
:
#333
;
&
:hover
{
color
:
#b49441
;
}
}
}
</
style
>
src/modules/viewer/components/layout.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<component
:is=
"currentCompoent"
:chapter=
"chapter"
v-bind=
"$attrs"
v-on=
"$listeners"
v-if=
"chapter"
:key=
"pid"
/>
</
template
>
<
script
>
// components
import
ChapterPlayer
from
'./player/chapterPlayer.vue'
// 章节视频
import
ChapterWork
from
'./work/index.vue'
// 章节作业
import
ChapterExam
from
'./work/chapterExam.vue'
// 章节考试
import
ChapterRead
from
'./read/chapterRead.vue'
// 章节资料
import
ChapterLive
from
'./live/chapterLive.vue'
// 章节直播
import
CourseWork
from
'./work/courseWork.vue'
// 课程大作业
import
CourseRead
from
'./read/courseRead.vue'
// 课程资料
import
CourseExam
from
'./work/courseExam.vue'
// 课程考试
export
default
{
name
:
'ViewerLayout'
,
components
:
{
ChapterPlayer
,
ChapterWork
,
ChapterRead
,
ChapterExam
,
ChapterLive
,
CourseWork
,
CourseRead
,
CourseExam
},
props
:
{
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
computed
:
{
currentCompoent
()
{
const
componentNames
=
{
2
:
'ChapterPlayer'
,
// 视频
3
:
'ChapterWork'
,
// 作业
4
:
'ChapterRead'
,
// 资料
5
:
'ChapterLive'
,
// CC直播
8
:
'ChapterLive'
,
// CC直播
9
:
'ChapterExam'
,
// 考试
99
:
'CourseWork'
,
// 课程大作业
100
:
'CourseRead'
,
// 课程资料
101
:
'CourseExam'
// 课程考试
}
return
this
.
chapter
?
componentNames
[
this
.
chapter
.
type
]
||
''
:
''
},
pid
()
{
return
this
.
$route
.
params
.
id
}
}
}
</
script
>
src/modules/viewer/components/live/chapterLive.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
style=
"width: 100%; height: 100%"
>
<div
class=
"course-viewer-content"
v-if=
"isLiveEnd && !hasRecord"
>
<div
class=
"empty"
>
直播已结束
</div>
</div>
<iframe
:src=
"iframeUrl"
frameborder=
"0"
width=
"100%"
height=
"100%"
allow=
"autoplay;geolocation;microphone;camera;midi;encrypted-media;"
v-else
></iframe>
</div>
</
template
>
<
script
>
// 章节视频
export
default
{
name
:
'ChapterLive'
,
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
computed
:
{
user
()
{
return
window
.
G
.
UserInfo
?
window
.
G
.
UserInfo
.
student_info
:
{}
},
nickName
()
{
return
this
.
user
.
personal_name
||
'匿名'
},
live
()
{
const
live
=
this
.
chapter
.
live
||
{}
live
.
live_status
=
parseInt
(
live
.
live_status
)
return
live
},
// 是否直播结束
isLiveEnd
()
{
return
[
3
,
5
].
includes
(
this
.
live
.
live_status
)
},
// 是否有回放
hasRecord
()
{
// enable_record 0:不启用回放 1:开启回放
return
this
.
live
.
enable_record
===
1
&&
this
.
live
.
record_url
},
iframeUrl
()
{
if
(
this
.
live
.
type
===
5
)
{
return
this
.
ccUrl
}
if
(
this
.
live
.
type
===
8
)
{
return
this
.
zoomUrl
}
},
// cc直播
ccUrl
()
{
const
live
=
this
.
live
if
(
this
.
isLiveEnd
&&
this
.
hasRecord
)
{
// 查看回放
return
live
.
record_url
.
replace
(
/^http:|^https:/
,
''
)
}
else
{
// 直播
live
.
user_name
=
live
.
user_name
||
this
.
nickName
return
`https://view.csslcloud.net/api/view/index?roomid=
${
live
.
room_id
}
&userid=
${
live
.
account_id
}
&autoLogin=true&viewername=
${
live
.
user_name
}
&viewertoken=
${
live
.
play_pass
}
`
}
},
// zoom直播
zoomUrl
()
{
return
this
.
live
.
record_url
||
this
.
live
.
join_url
}
}
}
</
script
>
<
style
scoped
>
.empty
{
padding
:
100px
;
font-size
:
30px
;
text-align
:
center
;
}
</
style
>
src/modules/viewer/components/player/chapterPlayer.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"player"
v-if=
"chatperResources"
>
<div
class=
"player-main"
>
<div
class=
"player-column"
v-show=
"videoVisible"
>
<!-- 视频 -->
<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=
"onPPTClose"
@
fullscreen=
"onPPTFullscreen"
@
videoSyncTime=
"onVideoSyncTime"
></ppt-player>
</div>
</div>
<div
class=
"player-footer"
>
<em
class=
"player-button player-button-download"
v-if=
"chapter.pdf"
>
<a
:href=
"chapter.pdf"
download
target=
"_blank"
>
下载PPT
</a>
</em>
<em
:class=
"pptClass"
@
click=
"togglePPTVisible"
v-if=
"chatperResources.ppts.length"
>
同步显示PPT
</em>
<em
:class=
"skipClass"
@
click=
"toggleSkip"
>
始终跳过片头
</em>
</div>
</div>
</
template
>
<
script
>
import
Cookies
from
'js-cookie'
import
{
throttle
}
from
'lodash'
// api
import
*
as
api
from
'../../api'
// components
import
videoPlayer
from
'./videoPlayer.vue'
import
pptPlayer
from
'./pptPlayer.vue'
export
default
{
name
:
'ChapterPlayer'
,
components
:
{
videoPlayer
,
pptPlayer
},
props
:
{
// 当前章节
chapter
:
{
type
:
Object
},
// 是否是PPT播放跳转
isSeek
:
{
type
:
Boolean
,
default
:
false
},
// PPT当前选中的索引
pptIndex
:
{
type
:
Number
,
default
:
0
}
},
data
()
{
// 是否跳过片头
const
isSkip
=
window
.
localStorage
.
getItem
(
'isSkip'
)
===
'true'
return
{
videoVisible
:
true
,
pptVisible
:
false
,
isSkip
,
skipTime
:
6
,
chatperResources
:
null
,
throttled
:
null
,
throttleWait
:
5
,
// 秒
progress
:
{
cpt
:
0
,
// 当前播放时间
mpt
:
0
,
// 当前播放最大时间
progress
:
0
,
// 进度
pt
:
0
// 累计观看时间
},
player
:
null
,
watchedTime
:
0
,
watchedTimePoint
:
[]
// 视频观看的时间点
}
},
watch
:
{
pptIndex
(
index
)
{
this
.
isSeek
&&
this
.
updateVideoCurrentTime
(
index
)
}
},
computed
:
{
// 学期ID
sid
()
{
return
this
.
$route
.
params
.
sid
},
// 课程ID
cid
()
{
return
this
.
$route
.
params
.
cid
},
// 视频资源ID
resourceId
()
{
return
this
.
chapter
.
resource_id
},
/**
* 视频提供者
* @return 1是CC加密; 2是非加密; 3是阿里云
*/
videoProvider
()
{
const
video
=
this
.
chapter
.
video
||
{}
return
video
.
video_provider
||
3
},
pptClass
()
{
return
{
'player-button'
:
true
,
'player-button-ppt'
:
!
this
.
pptVisible
,
'player-button-ppt__active'
:
this
.
pptVisible
}
},
skipClass
()
{
return
{
'player-button'
:
true
,
'player-button-skip'
:
!
this
.
isSkip
,
'player-button-skip__active'
:
this
.
isSkip
}
}
},
methods
:
{
// 同步显示PPT
togglePPTVisible
()
{
this
.
videoVisible
=
true
this
.
pptVisible
=
!
this
.
pptVisible
},
// 始终跳过片头
toggleSkip
()
{
this
.
isSkip
=
!
this
.
isSkip
window
.
localStorage
.
setItem
(
'isSkip'
,
this
.
isSkip
)
},
// 关闭PPT
onPPTClose
()
{
this
.
pptVisible
=
false
this
.
videoVisible
=
true
},
// PPT全屏
onPPTFullscreen
(
value
)
{
this
.
videoVisible
=
!
value
},
// 设置视频时间为当前PPT时间
onVideoSyncTime
(
time
)
{
this
.
player
.
seek
(
time
)
},
// 播放器ready
onReady
(
player
)
{
this
.
player
=
player
// 跳转播放进度
if
(
this
.
progress
.
cpt
)
{
this
.
player
.
seek
(
this
.
progress
.
cpt
)
}
},
// 当前播放时间更新
onTimeupdate
(
time
)
{
time
=
Math
.
floor
(
time
)
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
=
time
// 观看的最大点
this
.
progress
.
mpt
=
Math
.
max
(
time
,
this
.
progress
.
mpt
)
const
hasTimePoint
=
this
.
watchedTimePoint
.
includes
(
this
.
progress
.
cpt
)
if
(
!
hasTimePoint
)
{
this
.
watchedTimePoint
.
push
(
this
.
progress
.
cpt
)
}
// 更新视频观看总时长
this
.
updateWatchTime
(
time
)
// 更新视频进度,10秒更新一次
if
(
this
.
throttled
)
{
this
.
throttled
(
time
,
durations
)
}
else
{
this
.
throttled
=
throttle
(
this
.
updateChapterVideoProgress
,
this
.
throttleWait
*
1000
,
{
leading
:
false
})
}
},
// 更新视频当前播放时间
updateVideoCurrentTime
()
{
const
ppt
=
this
.
chatperResources
.
ppts
[
this
.
pptIndex
]
ppt
&&
this
.
player
.
seek
(
ppt
.
ppt_point
)
// 增加2秒
},
// 获取章节视频详情
getChapterVideo
()
{
// 视频播放类型 1是CC加密; 2是非加密; 3是阿里云
if
(
this
.
videoProvider
===
3
)
{
api
.
getChapterVideoAliyun
(
this
.
resourceId
).
then
(
response
=>
{
this
.
chatperResources
=
response
Array
.
isArray
(
response
.
ppts
)
&&
this
.
$emit
(
'pptupdate'
,
response
.
ppts
)
})
}
else
{
api
.
getChapterVideo
(
this
.
resourceId
).
then
(
response
=>
{
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
)
{
// 登录用户信息
const
user
=
window
.
G
.
UserInfo
const
params
=
{
sid
:
user
.
student_info
.
id
,
uid
:
user
.
id
,
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
(
time
)
{
if
(
time
===
this
.
watchedTime
)
{
return
}
this
.
watchedTime
=
time
// 增加跳过片头时间
if
(
this
.
isSkip
&&
!
this
.
progress
.
pt
)
{
this
.
progress
.
pt
=
this
.
skipTime
+
20
}
// 默认增加时间
this
.
progress
.
pt
=
this
.
progress
.
pt
||
20
this
.
progress
.
pt
++
}
},
beforeMount
()
{
// 获取视频
this
.
getChapterVideo
()
// 获取视频进度
this
.
getChapterVideoProgress
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.player
{
display
:
flex
;
flex-direction
:
column
;
width
:
100%
;
height
:
100%
;
background-color
:
#3f3f3f
;
}
.player-main
{
display
:
flex
;
flex
:
1
;
overflow
:
hidden
;
}
.player-column
{
flex
:
1
;
height
:
100%
;
}
.player-footer
{
display
:
flex
;
align-items
:
center
;
height
:
54px
;
padding
:
0
20px
;
font-size
:
14px
;
color
:
#a0a0a0
;
a
{
color
:
#a0a0a0
;
text-decoration
:
none
;
}
em
{
margin-right
:
40px
;
cursor
:
pointer
;
}
}
.player-button
{
display
:
inline-block
;
color
:
#a0a0a0
;
padding-left
:
25px
;
font-size
:
14px
;
line-height
:
18px
;
margin
:
0
20px
;
background
:
url(../../assets/play-icons.png)
no-repeat
0
0
;
cursor
:
pointer
;
}
.player-button-download
{
background-position
:
0
-240px
;
}
.player-button-ppt
{
background-position
:
0
-240px
;
}
.player-button-ppt__active
{
background-position
:
0
-280px
;
color
:
#b19241
;
}
.player-button-skip
{
background-position
:
0
-160px
;
}
.player-button-skip__active
{
background-position
:
0
-200px
;
color
:
#b19241
;
}
</
style
>
src/modules/viewer/components/player/pptPlayer.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"ppt-player"
>
<template
v-if=
"ppts.length"
>
<div
class=
"ppt-player-preview"
>
<img
:src=
"pptUrl"
v-if=
"pptUrl"
/>
</div>
<div
class=
"ppt-player-controls"
>
<div
class=
"ppt-player-controls__page"
>
<template
v-if=
"currentIndex >= 0"
>
<i
class=
"el-icon-arrow-left"
@
click=
"prev"
></i>
</
template
>
<
template
v-if=
"currentIndex + 1 < ppts.length"
>
<i
class=
"el-icon-arrow-right"
@
click=
"next"
></i>
</
template
>
</div>
<div
class=
"ppt-player-controls__pages"
>
<span
class=
"is-active"
>
{{currentIndex + 1}}
</span>
/
<span>
{{ppts.length}}
</span>
页
</div>
<div
class=
"ppt-player-controls__tools"
>
<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>
</div>
</template>
<
script
>
export
default
{
name
:
'ppt-player'
,
props
:
{
ppts
:
{
type
:
Array
},
index
:
{
type
:
Number
,
default
:
0
}
},
data
()
{
return
{
currentIndex
:
this
.
index
,
isSync
:
true
,
isFullscreen
:
false
}
},
watch
:
{
index
:
{
handler
(
value
)
{
if
(
this
.
isSync
)
{
this
.
currentIndex
=
value
}
}
}
},
computed
:
{
pptUrl
()
{
return
this
.
ppts
[
this
.
currentIndex
]
?
this
.
ppts
[
this
.
currentIndex
].
ppt_url
:
''
}
},
methods
:
{
gotoIndex
(
index
)
{
this
.
currentIndex
=
index
},
getIndex
(
index
)
{
return
Math
.
min
(
this
.
ppts
.
length
-
1
,
Math
.
max
(
0
,
index
))
},
prev
()
{
this
.
currentIndex
=
this
.
getIndex
(
this
.
currentIndex
-
1
)
this
.
isSync
=
false
},
next
(
e
)
{
this
.
currentIndex
=
this
.
getIndex
(
this
.
currentIndex
+
1
)
this
.
isSync
=
false
},
onToggleSync
(
e
)
{
this
.
isSync
=
!
this
.
isSync
},
setVideoTime
(
e
)
{
this
.
isSync
=
true
this
.
$emit
(
'videoSyncTime'
,
this
.
ppts
[
this
.
currentIndex
].
ppt_point
)
},
// 全屏
fullscreen
()
{
this
.
isFullscreen
=
!
this
.
isFullscreen
this
.
$emit
(
'fullscreen'
,
this
.
isFullscreen
)
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.ppt-player
{
position
:
relative
;
width
:
100%
;
height
:
100%
;
background-color
:
#000
;
}
.ppt-player-preview
{
height
:
100%
;
img
{
width
:
100%
;
height
:
100%
;
object-fit
:
contain
;
}
}
.ppt-player-controls
{
position
:
absolute
;
left
:
0
;
right
:
0
;
bottom
:
0
;
height
:
44px
;
line-height
:
44px
;
padding
:
0
14px
;
background-color
:
rgba
(
0
,
0
,
0
,
0
.5
);
display
:
flex
;
}
.ppt-player-controls__page
{
width
:
90px
;
color
:
#fff
;
i
{
padding
:
0
10px
;
font-size
:
18px
;
cursor
:
pointer
;
}
}
.ppt-player-controls__pages
{
flex
:
1
;
color
:
#fff
;
text-align
:
center
;
}
.ppt-player-controls__pages
.is-active
{
color
:
#d29f29
;
}
.ppt-player-controls__tools
{
float
:
right
;
}
.ppt-player-controls__tools
i
{
color
:
#fff
;
margin
:
0
10px
;
cursor
:
pointer
;
}
.ppt-player-controls__tools
i
.active
,
.ppt-player-controls__tools
i
:hover
{
color
:
#d29f29
;
}
.ppt-player-controls__tools
.icon-rotate
{
font-size
:
1
.125em
;
}
</
style
>
src/modules/viewer/components/player/videoPlayer.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"video-player"
id=
"player"
></div>
</
template
>
<
script
>
export
default
{
name
:
'VideoPlayer'
,
props
:
{
isSkip
:
Boolean
,
video
:
Object
,
autoplay
:
{
type
:
Boolean
,
default
:
false
}
},
data
()
{
return
{
player
:
null
}
},
methods
:
{
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
:
this
.
autoplay
,
isLive
:
false
,
controlBarVisibility
:
'always'
,
definition
:
'FD,LD,SD'
,
defaultDefinition
:
'LD'
,
useHlsPluginForSafari
:
true
},
function
(
player
)
{
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
)
})
}
)
}
},
mounted
()
{
this
.
createPlayer
()
},
beforeDestroy
()
{
this
.
player
&&
this
.
player
.
dispose
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.video-player
{
width
:
100%
;
height
:
100%
;
}
</
style
>
src/modules/viewer/components/read/chapterRead.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"chapter.name"
>
<file-list
:files=
"files"
></file-list>
</container>
</
template
>
<
script
>
// components
import
Container
from
'../common/container.vue'
import
FileList
from
'./fileList.vue'
// 章节阅读资料
export
default
{
name
:
'ChapterRead'
,
components
:
{
Container
,
FileList
},
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
computed
:
{
files
()
{
const
reading
=
this
.
chapter
.
reading
const
file
=
{
file_name
:
reading
.
reading_content
,
file_url
:
reading
.
reading_attachment
}
return
[
file
]
}
}
}
</
script
>
src/modules/viewer/components/read/courseRead.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"chapter.name"
>
<file-list
:files=
"files"
></file-list>
</container>
</
template
>
<
script
>
// components
import
Container
from
'../common/container.vue'
import
FileList
from
'./fileList.vue'
// 课程阅读资料
export
default
{
name
:
'CourseRead'
,
components
:
{
Container
,
FileList
},
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
computed
:
{
files
()
{
return
this
.
data
.
files
||
[]
}
}
}
</
script
>
src/modules/viewer/components/read/fileList.vue
0 → 100644
浏览文件 @
49505039
<
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
class=
"file-list-item__inner"
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
;
&
: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
;
}
.file-list-item__inner
{
margin
:
0
10px
!
important
;
}
</
style
>
src/modules/viewer/components/work/chapterExam.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"detail.paper_title"
v-loading=
"loading"
>
<template
v-slot:header-aside
v-if=
"isExamComplete"
>
分数:
{{
exam
.
score
.
total
}}
分
</
template
>
<div
class=
"exam"
>
<
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 items"
:index=
"index"
:type=
"item.type"
:data=
"item"
:value=
"item.formModel"
:disabled=
"isSubmited"
:key=
"item.id"
></exam-item>
</
template
>
<div
class=
"exam-buttons"
>
<el-tooltip
effect=
"dark"
content=
"提交之后就不能修改了哦"
placement=
"right"
>
<el-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"onSubmit"
>
{{submitText}}
</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</template>
</div>
</container>
</template>
<
script
>
import
Base64
from
'Base64'
// components
import
Container
from
'../common/container.vue'
import
ExamItem
from
'./examItem.vue'
// api
import
*
as
api
from
'../../api'
// 章节测试题
export
default
{
name
:
'ChapterExam'
,
components
:
{
Container
,
ExamItem
},
props
:
{
// 当前选中的章节
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
data
()
{
return
{
loading
:
false
,
detail
:
{},
questions
:
[],
messageInstance
:
null
,
exam
:
{},
isStartExam
:
false
,
// 是否开始考试
autoSubmitTimer
:
null
,
// 自动提交定时器
submitLoading
:
false
}
},
watch
:
{
chapter
:
{
immediate
:
true
,
handler
(
data
)
{
this
.
detail
=
data
.
paper
this
.
questions
=
data
.
paper
?
this
.
genQuestions
(
data
.
paper
.
examination
)
:
[]
}
}
},
computed
:
{
// 学期ID
sid
()
{
return
this
.
$route
.
params
.
sid
},
// 课程ID
cid
()
{
return
this
.
$route
.
params
.
cid
},
// 当前页面的ID
pid
()
{
return
this
.
$route
.
params
.
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
},
// 考试按钮
startExamButtonText
()
{
return
this
.
isExamTime
?
'开始考试'
:
'考试结束'
},
// 考试完成
isExamComplete
()
{
// 考试完成,批改完成并且公布成绩
return
this
.
exam
.
is_published
===
1
&&
this
.
exam
.
type
===
2
},
// 是否提交
isSubmited
()
{
return
this
.
exam
.
type
===
1
||
this
.
exam
.
type
===
2
},
// 提交按钮文本
submitText
()
{
return
this
.
isSubmited
?
'已提交'
:
'提交'
}
},
methods
:
{
// 开始考试
onStartExam
()
{
this
.
isStartExam
=
true
// 自动提交答题
this
.
autoSubmit
()
},
// 组装问题数据
genQuestions
(
list
)
{
if
(
!
list
)
{
return
[]
}
return
list
.
map
(
data
=>
{
let
{
radioList
,
checkboxList
,
shortAnswerList
}
=
data
// 单选
radioList
=
radioList
.
map
(
item
=>
{
const
temp
=
{
type
:
1
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
||
''
}
}
return
Object
.
assign
({},
item
,
temp
)
})
// 多选
checkboxList
=
checkboxList
.
map
(
item
=>
{
const
temp
=
{
type
:
2
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
||
[]
}
}
return
Object
.
assign
({},
item
,
temp
)
})
// 问答
shortAnswerList
=
shortAnswerList
.
map
(
item
=>
{
const
temp
=
{
type
:
3
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
?
Base64
.
decode
(
item
.
user_answer
.
replace
(
/ /gi
,
'+'
))
:
''
,
attachments
:
item
.
attachments
||
[]
}
}
return
Object
.
assign
({},
item
,
temp
)
})
return
[...
radioList
,
...
checkboxList
,
...
shortAnswerList
]
})
},
// 获取考试结果
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
()
{
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
},
// 提交
onSubmit
()
{
// 校验
if
(
!
this
.
checkSubmit
())
{
this
.
messageInstance
&&
this
.
messageInstance
.
close
()
this
.
messageInstance
=
this
.
$message
.
error
(
'还有题目未做,不能提交'
)
return
}
// 提交的答案数据
const
answers
=
this
.
handleSubmitData
()
// 提交参数
const
params
=
{
answers
:
JSON
.
stringify
(
answers
),
type
:
1
}
// 请求接口
this
.
submitLoading
=
true
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
)
},
3000
)
},
// 处理请求接口答案数据
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
)
{
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
)
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
}
},
beforeMount
()
{
// 获取考试结果
this
.
getExamResult
()
},
destroyed
()
{
this
.
autoSubmitTimer
&&
clearInterval
(
this
.
autoSubmitTimer
)
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.exam-buttons
{
padding
:
40px
0
;
text-align
:
center
;
.el-button
{
width
:
240px
;
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
>
src/modules/viewer/components/work/chapterTest.vue
0 → 100644
浏览文件 @
49505039
<
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 questions"
: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"
:loading=
"submitLoading"
@
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
,
submitLoading
:
false
}
},
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
)
{
this
.
submitLoading
=
true
api
.
sbumitChapterHomework
(
params
)
.
then
(
response
=>
{
if
(
response
.
status
)
{
this
.
getDetail
()
}
else
{
this
.
$message
.
error
(
response
.
data
.
error
)
}
})
.
catch
(
error
=>
{
this
.
$message
.
error
(
error
.
message
)
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
}
},
beforeMount
()
{
this
.
getDetail
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.exam-buttons
{
padding
:
40px
0
;
text-align
:
center
;
.el-button
{
width
:
240px
;
margin
:
40px
auto
;
}
}
</
style
>
src/modules/viewer/components/work/chapterWork.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"chapter.name"
v-loading=
"loading"
>
<div
class=
"exam-form"
>
<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=
"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
.
checker_time
}}
</p>
<div
class=
"paper-check-item"
>
<b>
评分:
</b>
{{
detail
.
score
}}
</div>
<div
class=
"paper-check-item"
>
<b>
评语:
</b>
<div
class=
"edit_html"
v-html=
"detail.check_comments"
></div>
</div>
</div>
</
template
>
<
template
v-else-if=
"detail.created_time"
>
<p
class=
"help"
>
已于
{{
detail
.
created_time
}}
提交,等待老师批改中。
</p>
<template
v-if=
"
detail.updated_time &&
detail.updated_time !== detail.created_time
"
>
<p
class=
"help"
>
最近提交时间:
{{
detail
.
updated_time
}}
</p>
</
template
>
</template>
</div>
</div>
<div
class=
"buttons"
>
<el-tooltip
content=
"在获老师批改之前,可以多次提交,将以最后一次提交为准"
placement=
"right"
>
<el-button
type=
"primary"
:disabled=
"disabled || !isWorkTime"
:loading=
"submitLoading"
@
click=
"onSubmit"
>
{{ submitText }}
</el-button>
</el-tooltip>
</div>
</template>
</container>
</template>
<
script
>
import
Base64
from
'Base64'
// componets
import
Container
from
'../common/container.vue'
import
ExamItem
from
'./examItem.vue'
// api
import
*
as
api
from
'../../api'
// 章节作业
export
default
{
name
:
'ChapterWork'
,
components
:
{
Container
,
ExamItem
},
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
data
()
{
return
{
loading
:
false
,
detail
:
null
,
questions
:
[],
// 问题列表
startTime
:
new
Date
().
getTime
(),
// 进入时间
messageInstance
:
null
,
deadline
:
''
,
// 截止时间
disabled
:
false
,
submitLoading
:
false
}
},
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
},
// 是否批改
isRevised
()
{
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
.
getChapterHomework
(
this
.
sid
,
this
.
cid
,
this
.
resourceId
)
.
then
(
response
=>
{
this
.
detail
=
Array
.
isArray
(
response
)
?
null
:
response
if
(
this
.
detail
)
{
// -1未处理 0已处理 1驳回
this
.
disabled
=
[
0
,
1
].
includes
(
this
.
detail
.
status
)
const
parseAnswers
=
JSON
.
parse
(
this
.
detail
.
work_contents
)
// 设置答案
this
.
questions
=
this
.
questions
.
map
(
item
=>
{
const
found
=
parseAnswers
.
find
(
answer
=>
answer
.
question_id
===
item
.
id
)
if
(
found
)
{
item
.
user_answer
=
found
.
descreption
item
.
attachments
=
found
.
file_url
}
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
answers
=
this
.
questions
.
map
(
item
=>
{
return
{
question_id
:
item
.
id
,
descreption
:
item
.
question_type
===
3
?
Base64
.
encode
(
item
.
formModel
.
user_answer
)
:
item
.
formModel
.
user_answer
,
file_url
:
item
.
formModel
.
attachments
,
is_encoded
:
1
}
})
// 提交参数
const
params
=
{
semester_id
:
this
.
sid
,
course_id
:
this
.
cid
,
chapter_id
:
this
.
pid
,
work_id
:
this
.
resourceId
,
work_contents
:
JSON
.
stringify
(
answers
),
duration
}
// 请求接口
this
.
handleSubmitRequest
(
params
)
},
// 请求提交接口
handleSubmitRequest
(
params
)
{
this
.
submitLoading
=
true
api
.
sbumitChapterHomework
(
params
)
.
then
(
response
=>
{
if
(
response
.
status
)
{
this
.
$message
.
success
(
'提交成功,等待批改'
)
this
.
getDetail
()
}
else
{
this
.
$message
.
error
(
response
.
data
.
error
)
}
})
.
catch
(
error
=>
{
this
.
$message
.
error
(
error
.
message
)
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
},
// 重新编辑
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
>
src/modules/viewer/components/work/courseExam.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"exam.title"
v-loading=
"!loaded"
>
<template
v-slot:header-aside
>
<template
v-if=
"isCompleted"
>
分数:
{{
exam
.
score
.
total
}}
分
</
template
>
<
template
v-else
>
考试时间:
{{
status
.
start_time
}}
~
{{
status
.
terminate_time
}}
</
template
>
</template>
<div
class=
"exam"
>
<
template
v-if=
"status.examination_status === '00'"
>
<div
class=
"no-exam"
>
暂无考试
</div>
</
template
>
<
template
v-else-if=
"isSubmited && !isCompleted && !isMultipleExams"
>
<div
class=
"no-exam"
>
试卷批改中,请耐心等待
</div>
</
template
>
<
template
v-else
>
<!-- 考试试题 -->
<div
class=
"exam-form"
v-if=
"loaded"
>
<el-form
:disabled=
"!canEditable"
>
<template
v-for=
"items in questions"
>
<exam-item
v-for=
"(item, index) in items"
:index=
"index"
:type=
"item.type"
:data=
"item"
:value=
"item.formModel"
:disabled=
"!canEditable"
:showResult=
"isCompleted"
:key=
"item.id"
></exam-item>
</
template
>
</el-form>
<div
class=
"exam-buttons"
>
<!-- 允许多次提交 -->
<
template
v-if=
"isMultipleExams"
>
<el-button
type=
"primary"
@
click=
"handlePrev"
v-if=
"hasPrev"
>
上一套试卷
</el-button>
<el-button
type=
"primary"
@
click=
"handleNext"
v-if=
"hasNext"
>
下一套试卷
</el-button>
<el-button
type=
"primary"
@
click=
"handleNewExam"
v-if=
"hasResubmit"
>
再考一次
</el-button>
</
template
>
<
template
v-if=
"!(isSubmited && isMultipleExams)"
>
<el-tooltip
effect=
"dark"
content=
"提交之后就不能修改了哦"
placement=
"right"
>
<el-button
type=
"primary"
:disabled=
"!canEditable"
:loading=
"submitLoading"
@
click=
"onSubmit"
>
{{
submitText
}}
</el-button>
</el-tooltip>
</
template
>
</div>
</div>
</template>
</div>
</container>
</template>
<
script
>
import
Base64
from
'Base64'
// components
import
Container
from
'../common/container.vue'
import
ExamItem
from
'./examItem.vue'
// api
import
*
as
api
from
'../../api'
// 章节测试题
export
default
{
name
:
'CourseExam'
,
components
:
{
Container
,
ExamItem
},
props
:
{
// 当前选中的章节
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
data
()
{
return
{
loaded
:
false
,
detail
:
{},
status
:
{},
questions
:
[],
messageInstance
:
null
,
exam
:
{},
autoSubmitTimer
:
null
,
// 自动提交定时器
checkStatusTimer
:
null
,
// 考试状态定时器
submitLoading
:
false
,
isMultipleExams
:
false
,
// 是否可以多次考试
maxExams
:
3
,
// 最多考试几次
examCount
:
this
.
data
.
exist_examination
.
length
||
0
// 试卷数量
}
},
watch
:
{
offset
:
{
immediate
:
true
,
handler
()
{
this
.
init
()
}
}
},
computed
:
{
// 学期ID
sid
()
{
return
this
.
$route
.
params
.
sid
},
// 课程ID
cid
()
{
return
this
.
$route
.
params
.
cid
},
// 当前页面的ID
pid
()
{
return
this
.
data
.
course_examination
},
// 是否是考试时间
isExamTime
()
{
// 大于开始时间,小于结束时间
return
this
.
status
.
examination_status
===
'20'
},
// 是否提交
isSubmited
()
{
return
this
.
exam
.
type
===
1
||
this
.
exam
.
type
===
2
},
// 考试完成
isCompleted
()
{
// 考试完成,批改完成并且公布成绩
return
this
.
exam
.
is_published
===
1
&&
this
.
exam
.
type
===
2
},
// 可以编辑
canEditable
()
{
return
!
this
.
isSubmited
&&
this
.
isExamTime
},
// 提交按钮文本
submitText
()
{
return
this
.
isSubmited
?
'已提交'
:
'提交'
},
// 试卷页码
offset
()
{
const
{
query
}
=
this
.
$route
return
parseInt
(
query
.
offset
)
||
0
},
// 是否显示上一套试题
hasPrev
()
{
return
!!
this
.
offset
},
// 是否显示下一套试题
hasNext
()
{
return
this
.
offset
<
this
.
examCount
-
1
},
// 是否显示再考一次
hasResubmit
()
{
if
(
this
.
examList
.
length
>=
this
.
maxExams
)
{
return
false
}
// 判断状态是否还有未提交的试题
for
(
const
exam
of
this
.
examList
)
{
if
(
!
[
'1'
,
'2'
].
includes
(
exam
.
status
))
{
return
false
}
}
return
true
// return this.isSubmited && this.isExamTime && this.examCount
<
this
.
maxExams
},
// 已存在的试题列表
examList
()
{
return
this
.
data
.
exist_examination
}
},
methods
:
{
// 初始化
async
init
()
{
this
.
clearTimer
()
// 自动获取考试状态
await
this
.
autoCheckExamStatus
()
// 获取试题
this
.
getExam
()
},
// 获取考试状态
async
getExamStatus
()
{
await
api
.
getCourseExamStatus
(
this
.
sid
,
this
.
cid
,
this
.
pid
).
then
(
response
=>
{
this
.
status
=
response
if
(
this
.
isSubmited
||
response
.
examination_status
===
'90'
)
{
this
.
checkStatusTimer
&&
clearInterval
(
this
.
checkStatusTimer
)
}
})
},
// 自动获取考试状态
async
autoCheckExamStatus
()
{
// 获取试题状态
await
this
.
getExamStatus
()
this
.
checkStatusTimer
&&
clearInterval
(
this
.
checkStatusTimer
)
this
.
checkStatusTimer
=
setInterval
(
this
.
getExamStatus
,
5000
)
},
// 获取试题
getExam
()
{
this
.
loaded
=
false
api
.
getCourseExamResult
(
this
.
sid
,
this
.
cid
,
this
.
pid
,
{
offset
:
this
.
offset
})
.
then
(
response
=>
{
this
.
exam
=
response
this
.
questions
=
this
.
genQuestions
(
response
.
sheet
)
// 自动提交
this
.
canEditable
&&
this
.
autoSubmit
()
// 更新菜单
this
.
isMultipleExams
&&
this
.
$emit
(
'update'
)
})
.
finally
(()
=>
{
this
.
loaded
=
true
})
},
// 组装问题数据
genQuestions
(
list
)
{
if
(
!
list
)
{
return
[]
}
return
list
.
map
(
data
=>
{
let
{
radioList
,
checkboxList
,
shortAnswerList
}
=
data
// 单选
radioList
=
radioList
.
map
(
item
=>
{
const
temp
=
{
type
:
1
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
||
''
}
}
return
Object
.
assign
({},
item
,
temp
)
})
// 多选
checkboxList
=
checkboxList
.
map
(
item
=>
{
const
temp
=
{
type
:
2
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
||
[]
}
}
return
Object
.
assign
({},
item
,
temp
)
})
// 问答
shortAnswerList
=
shortAnswerList
.
map
(
item
=>
{
const
temp
=
{
type
:
3
,
formModel
:
{
id
:
item
.
id
,
user_answer
:
item
.
user_answer
?
Base64
.
decode
(
item
.
user_answer
.
replace
(
/ /gi
,
'+'
))
:
''
,
attachments
:
item
.
attachments
||
[]
}
}
return
Object
.
assign
({},
item
,
temp
)
})
return
[...
radioList
,
...
checkboxList
,
...
shortAnswerList
]
})
},
// 提交校验
checkSubmit
()
{
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
},
// 提交
onSubmit
()
{
// 校验
if
(
!
this
.
checkSubmit
())
{
this
.
messageInstance
&&
this
.
messageInstance
.
close
()
this
.
messageInstance
=
this
.
$message
.
error
(
'还有题目未做,不能提交'
)
return
}
// 提交的答案数据
const
answers
=
this
.
handleSubmitData
()
// 提交参数
const
params
=
{
answers
:
JSON
.
stringify
(
answers
),
type
:
1
}
// 请求接口
this
.
submitLoading
=
true
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
)
},
3000
)
},
// 处理请求接口答案数据
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
)
{
params
.
offset
=
this
.
offset
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
.
getExam
()
}
else
{
this
.
$message
.
error
(
response
.
data
.
error
)
}
})
.
catch
(
error
=>
{
this
.
$message
.
error
(
error
.
message
)
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
},
// 上一套试卷
handlePrev
()
{
const
offset
=
this
.
offset
-
1
this
.
$router
.
push
({
query
:
{
offset
}
})
},
handleNext
()
{
const
offset
=
this
.
offset
+
1
this
.
$router
.
push
({
query
:
{
offset
}
})
},
handleNewExam
()
{
this
.
$router
.
push
({
query
:
{
offset
:
this
.
examCount
}
})
this
.
examCount
++
},
// 清除定时器
clearTimer
()
{
this
.
autoSubmitTimer
&&
clearInterval
(
this
.
autoSubmitTimer
)
this
.
checkStatusTimer
&&
clearInterval
(
this
.
checkStatusTimer
)
}
},
beforeMount
()
{
// // 自动获取考试状态
// this.autoCheckExamStatus()
// // 获取试题
// this.getExam()
},
destroyed
()
{
this
.
clearTimer
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.exam-buttons
{
padding
:
40px
0
;
text-align
:
center
;
.el-button
{
min-width
:
160px
;
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
>
src/modules/viewer/components/work/courseWork.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<container
:title=
"chapter.name"
v-loading=
"loading"
>
<el-steps
direction=
"vertical"
v-if=
"data.curriculum"
>
<el-step
title=
"阅读大作业要求"
status=
"process"
>
<template
v-slot:description
>
<div
v-html=
"data.curriculum.curriculum_essay"
></div>
<p>
截止日期:
{{
data
.
essay_date
}}
</p>
</
template
>
</el-step>
<el-step
title=
"填写作业主题、正文,上传附件(点击“提交”保存)"
status=
"process"
>
<
template
v-slot:description
>
<el-form
:model=
"ruleForm"
:rules=
"rules"
:hide-required-asterisk=
"true"
:disabled=
"isRevised"
label-position=
"top"
ref=
"ruleForm"
>
<el-form-item
label=
"主题"
prop=
"essay_name"
>
<el-input
v-model=
"ruleForm.essay_name"
placeholder=
"主题"
maxlength=
"50"
></el-input>
</el-form-item>
<el-form-item
label=
"正文"
prop=
"essay_description"
>
<!-- 编辑器 -->
<v-editor
:disabled=
"isRevised"
v-model=
"ruleForm.essay_description"
></v-editor>
</el-form-item>
<el-form-item
prop=
"url"
>
<!-- 文件上传 -->
<v-upload
v-model=
"ruleForm.url"
>
请上传对应的文件附件:
<!--
<template
v-slot:tip
>
只支持docx格式的文件,文件小于10M
</
template
>
-->
</v-upload>
</el-form-item>
</el-form>
</template>
</el-step>
<el-step
title=
"截止日期前提交"
status=
"process"
>
<
template
v-slot:description
>
<div
class=
"work-bottom"
v-if=
"detail"
>
<div
class=
"info"
>
<template
v-if=
"isRevised"
>
<div
class=
"paper-check"
>
<p>
批改时间:
{{
detail
.
check_date
}}
</p>
<div
class=
"paper-check-item"
>
<b>
评分:
</b>
{{
detail
.
score
}}
</div>
<div
class=
"paper-check-item"
>
<b>
评语:
</b>
<div
class=
"edit_html"
v-html=
"detail.check_comments"
></div>
</div>
</div>
</
template
>
<
template
v-else-if=
"detail.created_time"
>
<p
class=
"help"
>
已于
{{
detail
.
created_time
}}
提交,等待老师批改中。
</p>
<template
v-if=
"detail.updated_time && detail.updated_time !== detail.created_time"
>
<p
class=
"help"
>
最近提交时间:
{{
detail
.
updated_time
}}
</p>
</
template
>
</template>
</div>
</div>
<div
class=
"buttons"
>
<el-tooltip
content=
"在获老师批改之前,可以多次提交,将以最后一次提交为准"
placement=
"right"
>
<el-button
type=
"primary"
:disabled=
"isRevised"
:loading=
"submitLoading"
@
click=
"onSubmit"
>
{{submitText}}
</el-button>
</el-tooltip>
</div>
</template>
</el-step>
</el-steps>
</container>
</template>
<
script
>
// componets
import
Container
from
'../common/container.vue'
import
VEditor
from
'../common/editor.vue'
import
VUpload
from
'../common/upload.vue'
// api
import
*
as
api
from
'../../api'
// 课程大作业
export
default
{
name
:
'CourseWork'
,
components
:
{
Container
,
VEditor
,
VUpload
},
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
data
()
{
return
{
ruleForm
:
{
essay_name
:
''
,
essay_description
:
''
,
url
:
''
},
rules
:
{
essay_name
:
[
{
required
:
true
,
message
:
'请输入主题'
,
trigger
:
'blur'
}
],
essay_description
:
[
{
required
:
true
,
message
:
'请输入正文'
,
trigger
:
'change'
}
],
url
:
[{
required
:
true
,
message
:
'请上传附件'
,
trigger
:
'change'
}]
},
detail
:
null
,
loading
:
false
,
messageInstance
:
null
,
submitLoading
:
false
}
},
computed
:
{
// 学期ID
sid
()
{
return
this
.
$route
.
params
.
sid
},
// 课程ID
cid
()
{
return
this
.
$route
.
params
.
cid
},
// 是否批改
isRevised
()
{
return
this
.
detail
?
!!
this
.
detail
.
check_date
:
false
},
// 提交按钮文本
submitText
()
{
return
this
.
isRevised
?
'已批改'
:
'提交'
}
},
methods
:
{
// 获取大作业详情
getDetail
()
{
this
.
loading
=
true
api
.
getCourseWork
(
this
.
sid
,
this
.
cid
)
.
then
(
response
=>
{
this
.
detail
=
Array
.
isArray
(
response
)
?
null
:
response
if
(
this
.
detail
)
{
this
.
ruleForm
.
essay_name
=
this
.
detail
.
essay_name
this
.
ruleForm
.
essay_description
=
this
.
detail
.
essay_description
this
.
ruleForm
.
url
=
this
.
detail
.
file_url
}
})
.
finally
(()
=>
{
this
.
loading
=
false
})
},
// 提交
onSubmit
()
{
this
.
messageInstance
&&
this
.
messageInstance
.
close
()
if
(
!
this
.
ruleForm
.
essay_name
)
{
this
.
messageInstance
=
this
.
$message
.
error
(
'请输入主题'
)
return
}
if
(
!
this
.
ruleForm
.
essay_description
)
{
this
.
messageInstance
=
this
.
$message
.
error
(
'请输入正文'
)
return
}
if
(
!
this
.
ruleForm
.
url
)
{
this
.
messageInstance
=
this
.
$message
.
error
(
'请上传附件'
)
return
}
const
params
=
Object
.
assign
(
this
.
ruleForm
,
{
semester_id
:
this
.
sid
,
course_id
:
this
.
cid
})
this
.
handleSubmitRequest
(
params
)
},
// 请求提交接口
handleSubmitRequest
(
params
)
{
this
.
submitLoading
=
true
api
.
updateCourseWork
(
this
.
sid
,
this
.
cid
,
params
)
.
then
(
response
=>
{
if
(
response
.
status
)
{
this
.
$message
.
success
(
'提交成功,等待批改'
)
this
.
getDetail
()
}
else
{
this
.
$message
.
error
(
response
.
data
.
error
)
}
})
.
catch
(
error
=>
{
this
.
$message
.
error
(
error
.
message
)
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
}
},
beforeMount
()
{
this
.
getDetail
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
p
{
margin
:
0
;
}
::v-deep
.el-step__title
{
border-bottom
:
1px
dashed
#cecece
;
}
::v-deep
.el-step__description
{
padding
:
20px
0
30px
;
font-size
:
14px
;
}
::v-deep
.el-form-item__label
{
font-weight
:
bold
;
line-height
:
24px
;
padding
:
0
0
5px
;
}
.work-bottom
{
.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
;
}
.paper-check-item
{
display
:
flex
;
b
{
white-space
:
nowrap
;
}
}
</
style
>
src/modules/viewer/components/work/examItem.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<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"
></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"
>
<!-- 单选 -->
<el-radio-group
v-model=
"currentValue.user_answer"
v-if=
"type === 1"
>
<div
class=
"q-option-item"
v-for=
"item in currentOptions"
:key=
"item.id"
>
<el-radio
:class=
"genClass(item)"
:label=
"item.id"
>
<div
class=
"q-option-item__answer"
v-html=
"item.abc_option"
></div>
</el-radio>
</div>
</el-radio-group>
<!-- 多选 -->
<el-checkbox-group
v-model=
"currentValue.user_answer"
v-if=
"type === 2"
>
<div
class=
"q-option-item"
v-for=
"item in currentOptions"
:key=
"item.id"
>
<el-checkbox
:class=
"genClass(item)"
:label=
"item.id"
>
<div
class=
"q-option-item__answer"
v-html=
"item.abc_option"
></div>
</el-checkbox>
</div>
</el-checkbox-group>
<!-- 简答题 -->
<
template
v-if=
"type === 3"
>
<v-editor
v-model=
"currentValue.user_answer"
:disabled=
"disabled"
></v-editor>
<v-upload
:disabled=
"disabled"
v-model=
"currentValue.attachments"
>
请上传对应的文件附件:
</v-upload>
</
template
>
</div>
<div
class=
"q-item-ft"
v-if=
"disabled && showResult"
>
<
template
v-if=
"type === 3"
>
<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>
</p>
<p>
<span>
正确答案:
</span>
<span>
{{
correctAnswerText
}}
</span>
</p>
</div>
</
template
>
<p
v-if=
"data.hasOwnProperty('get_score')"
>
<span>
评分:
</span>
<span>
{{ data.get_score }}分
</span>
</p>
<div
class=
"analyze"
v-if=
"data.analysis"
>
<span>
解析:
</span>
<div
class=
"analyze-main"
>
<span
style=
"color: blue; cursor: pointer"
@
click=
"showAnalyze = !showAnalyze"
>
查看解析
</span>
<div
v-html=
"data.analysis"
v-if=
"data.analysis"
v-show=
"showAnalyze"
class=
"analyze-content"
></div>
</div>
</div>
</div>
</div>
</template>
<
script
>
// components
import
VEditor
from
'../common/editor.vue'
import
VUpload
from
'../common/upload.vue'
export
default
{
name
:
'ExamItem'
,
components
:
{
VEditor
,
VUpload
},
props
:
{
// 索引
index
:
{
type
:
Number
},
// 问题类型
type
:
{
type
:
Number
},
// 单条数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 提交的答案
value
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 是否禁用,提交过的是禁用状态
disabled
:
{
type
:
Boolean
,
default
:
false
},
showResult
:
{
type
:
Boolean
,
default
:
true
}
},
data
()
{
return
{
currentValue
:
{},
showAnalyze
:
false
}
},
watch
:
{
value
:
{
immediate
:
true
,
handler
(
value
)
{
this
.
currentValue
=
value
}
}
},
computed
:
{
// 26个英文字母
A_Z
()
{
const
result
=
[]
for
(
let
i
=
0
;
i
<
26
;
i
++
)
{
result
.
push
(
String
.
fromCharCode
(
65
+
i
))
}
return
result
},
// 选项类型
typeText
()
{
const
map
=
{
1
:
'单选题'
,
2
:
'多选题'
}
return
map
[
this
.
type
]
},
// 处理后的options数据
currentOptions
()
{
if
(
!
this
.
data
.
options
)
{
return
[]
}
return
this
.
data
.
options
.
map
((
item
,
index
)
=>
{
// 英文字母 + 名称
item
.
abc
=
this
.
A_Z
[
index
]
item
.
abc_option
=
`
${
this
.
A_Z
[
index
]}
.
${
item
.
option
}
`
// 提交时的选中状态
const
value
=
this
.
value
.
user_answer
||
''
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
})
},
// 正确答案显示的英文字母
correctAnswerText
()
{
const
result
=
this
.
currentOptions
.
reduce
((
result
,
item
)
=>
{
item
.
checked
&&
result
.
push
(
item
.
abc
)
return
result
},
[])
return
result
.
join
(
'、'
)
},
// 提交答案显示的英文字母
submitAnswerText
()
{
const
result
=
this
.
currentOptions
.
reduce
((
result
,
item
)
=>
{
item
.
selected
&&
result
.
push
(
item
.
abc
)
return
result
},
[])
return
result
.
join
(
'、'
)
},
// 是否回答正确
isCorrect
()
{
const
options
=
this
.
currentOptions
for
(
let
i
=
0
;
i
<
options
.
length
;
i
++
)
{
if
(
options
[
i
].
checked
!==
!!
options
[
i
].
selected
)
{
return
false
}
}
return
true
}
},
methods
:
{
// 生成class
genClass
(
item
)
{
if
(
!
this
.
disabled
||
!
this
.
showResult
)
{
return
null
}
return
{
'is-error'
:
!
this
.
isCorrect
&&
item
.
selected
,
'is-success'
:
this
.
isCorrect
&&
item
.
selected
}
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.q-item
{
font-size
:
16px
;
padding
:
10px
0
;
border-bottom
:
1px
solid
#c9c9c9
7a
;
.upload
{
font-size
:
14px
;
}
}
.q-item-hd
{
display
:
flex
;
padding
:
10px
0
20px
;
::v-deep
p
{
margin
:
0
;
padding
:
0
;
}
::v-deep
ul
{
margin
:
0
;
padding
:
0
;
list-style
:
none
;
}
}
.q-item-num
{
width
:
20px
;
text-align
:
center
;
}
.q-item-title
{
flex
:
1
;
::v-deep
img
{
max-width
:
100%
;
}
}
.q-item-aside
{
padding-left
:
20px
;
// align-self: flex-end;
}
.q-option-item
{
padding-left
:
20px
;
margin-bottom
:
14px
;
}
.q-option-item__answer
{
display
:
inline
;
::v-deep
*
{
display
:
inline
;
}
}
.is-success
{
color
:
#090
;
}
.is-error
{
color
:
#d80000
;
}
::v-deep
.el-radio
{
&
.is-disabled
.el-radio__label
{
color
:
#3c3c3c
;
}
&
.is-error
.el-radio__label
{
color
:
#d80000
;
}
&
.is-success
.el-radio__label
{
color
:
#090
;
}
}
::v-deep
.el-checkbox
{
&
.is-disabled
.el-checkbox__label
{
color
:
#3c3c3c
;
}
&
.is-error
.el-checkbox__label
{
color
:
#d80000
;
}
&
.is-success
.el-checkbox__label
{
color
:
#090
;
}
}
.q-item-ft
{
padding
:
10px
0
;
p
{
font-size
:
14px
;
margin
:
0
0
10px
0
;
}
.result
{
display
:
flex
;
justify-content
:
flex-end
;
p
{
padding-left
:
20px
;
}
}
.analyze
{
display
:
flex
;
font-size
:
14px
;
}
.analyze-main
{
flex
:
1
;
overflow
:
hidden
;
}
.analyze-content
{
margin-top
:
10px
;
background-color
:
#c9c9c9
7a
;
border
:
1px
solid
#c9c9c9
7a
;
padding
:
10px
;
::v-deep
*
{
margin
:
0
;
padding
:
0
;
max-width
:
100%
;
}
}
}
</
style
>
src/modules/viewer/components/work/index.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<component
:is=
"currentCompoent"
:chapter=
"chapter"
:data=
"data"
v-bind=
"$attrs"
v-on=
"$listeners"
v-if=
"chapter"
/>
</
template
>
<
script
>
// componets
import
ChapterWork
from
'./chapterWork.vue'
import
ChapterTest
from
'./chapterTest.vue'
export
default
{
name
:
'ViewerWork'
,
components
:
{
ChapterWork
,
ChapterTest
},
props
:
{
// 当前选中的
chapter
:
{
type
:
Object
,
default
()
{
return
{}
}
},
// 课程详情接口返回的数据
data
:
{
type
:
Object
,
default
()
{
return
{}
}
}
},
computed
:
{
currentCompoent
()
{
const
componentNames
=
{
1
:
'ChapterTest'
,
// 课后测验
2
:
'ChapterWork'
// 作业
}
const
homework
=
this
.
chapter
.
homework
return
homework
?
componentNames
[
homework
.
work_type
]
:
''
}
}
}
</
script
>
src/modules/viewer/index.vue
0 → 100644
浏览文件 @
49505039
<
template
>
<div
class=
"course-viewer"
>
<div
class=
"course-viewer-main"
>
<!-- 顶部区域 -->
<div
class=
"course-viewer-main-hd"
>
<router-link
:to=
"`/app/learn/course-detail/$
{sid}/${cid}`">
<i
class=
"el-icon-arrow-left"
></i>
</router-link>
<h1
class=
"course-viewer-main-hd__title"
>
{{
detail
.
course_name
}}
</h1>
<!-- 直播的时候显示帮助按钮 -->
<template
v-if=
"isLive"
>
<router-link
to=
"/app/feedback/feedback-create"
target=
"_blank"
>
<el-tooltip
effect=
"light"
content=
"意见反馈"
>
<i
class=
"el-icon-self-fankuiyijian"
></i>
</el-tooltip>
</router-link>
<router-link
to=
"/mobile/help/student"
target=
"_blank"
>
<el-tooltip
effect=
"light"
content=
"帮助"
>
<i
class=
"el-icon-self-icon-test"
></i>
</el-tooltip>
</router-link>
</
template
>
<div
class=
"course-menu"
@
click=
"menuVisible = !menuVisible"
>
<i
class=
"el-icon-s-unfold"
v-if=
"menuVisible"
></i>
<i
class=
"el-icon-s-fold"
v-else
></i>
</div>
</div>
<!-- 主体区域 -->
<div
class=
"course-viewer-main-bd"
>
<router-view
:data=
"detail"
:chapter=
"activeChapter"
:pptIndex=
"pptIndex"
:isSeek=
"isSeek"
:key=
"pid"
@
pptupdate=
"handlePPTupdate"
@
change-ppt=
"handleChangePPT(...arguments, false)"
@
update=
"getCourse"
/>
</div>
</div>
<!-- 侧边栏 -->
<v-aside
:data=
"detail"
:chapters=
"chapters"
:active=
"activeChapter"
:ppts=
"ppts"
:pptIndex=
"pptIndex"
@
change-ppt=
"handleChangePPT(...arguments, true)"
v-if=
"detail.chapters"
v-show=
"menuVisible"
></v-aside>
</div>
</template>
<
script
>
// api
import
*
as
api
from
'./api'
// components
import
VAside
from
'./components/aside/index.vue'
export
default
{
name
:
'CourseViewer'
,
components
:
{
VAside
},
data
()
{
return
{
detail
:
{},
ppts
:
[],
pptIndex
:
0
,
isSeek
:
false
,
menuVisible
:
true
}
},
watch
:
{
activeChapter
()
{
this
.
ppts
=
[]
this
.
pptIndex
=
0
},
isLive
(
value
)
{
if
(
value
)
{
this
.
menuVisible
=
false
}
},
isCourseExam
(
value
)
{
if
(
value
)
{
this
.
menuVisible
=
false
}
}
},
computed
:
{
// 学期ID
sid
()
{
return
this
.
$route
.
params
.
sid
},
// 课程ID
cid
()
{
return
this
.
$route
.
params
.
cid
},
// 当前页面的ID
pid
()
{
return
this
.
$route
.
params
.
id
},
// 章节列表
chapters
()
{
const
chapters
=
this
.
detail
.
chapters
||
[]
if
(
!
chapters
.
length
)
{
return
[]
}
const
customeChapter
=
{
name
:
'大作业及资料'
,
children
:
[
{
name
:
'课程大作业'
,
id
:
'course_work'
,
type
:
99
},
{
name
:
'课程资料'
,
id
:
'course_info'
,
type
:
100
},
{
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
},
// 当前选中的章节
activeChapter
()
{
const
id
=
this
.
pid
const
list
=
this
.
chapters
return
this
.
findChapter
(
id
,
list
)
},
// 直播
isLive
()
{
return
this
.
activeChapter
?
[
5
,
8
].
includes
(
this
.
activeChapter
.
type
)
:
false
},
// 课程考试
isCourseExam
()
{
return
this
.
activeChapter
?
this
.
activeChapter
.
type
===
101
:
false
}
},
methods
:
{
// 查找当前章节
findChapter
(
id
,
list
)
{
for
(
const
item
of
list
)
{
if
(
item
.
id
===
id
)
{
return
item
}
if
(
item
.
children
&&
item
.
children
.
length
)
{
const
found
=
this
.
findChapter
(
id
,
item
.
children
)
if
(
found
)
{
return
found
}
}
}
return
null
},
// 获取课程详情
getCourse
()
{
api
.
getCourse
(
this
.
sid
,
this
.
cid
).
then
(
response
=>
{
this
.
detail
=
response
})
},
// PPT列表更新
handlePPTupdate
(
list
)
{
this
.
ppts
=
list
},
// 右侧菜单选中的PPT修改
handleChangePPT
(
index
,
isSeek
)
{
this
.
pptIndex
=
index
this
.
isSeek
=
isSeek
}
},
beforeMount
()
{
this
.
getCourse
()
}
}
</
script
>
<
style
lang=
"scss"
>
.course-viewer
{
display
:
flex
;
height
:
100vh
;
overflow
:
hidden
;
}
.course-viewer-main
{
flex
:
1
;
display
:
flex
;
flex-direction
:
column
;
}
.course-viewer-main-hd
{
display
:
flex
;
align-items
:
center
;
background-color
:
#3f3f3f
;
height
:
56px
;
a
{
color
:
#fff
;
padding
:
10px
;
}
i
{
font-size
:
24px
;
}
}
.course-viewer-main-hd__title
{
flex
:
1
;
font-size
:
1
.5em
;
// text-align: center;
color
:
#a0a0a0
;
}
.course-viewer-main-bd
{
flex
:
1
;
height
:
calc
(
100vh
-
56px
);
overflow-y
:
auto
;
}
.course-viewer-content
{
// min-height: 50%;
max-width
:
900px
;
padding
:
40px
120px
80px
;
margin
:
40px
auto
;
background-color
:
#f2f2f2
;
box-shadow
:
0
0
2px
rgba
(
0
,
0
,
0
,
0
.05
);
}
.course-viewer-content-hd
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
padding
:
40px
0
;
// text-align: center;
}
.course-viewer-content-hd__title
{
position
:
relative
;
display
:
inline-block
;
margin
:
0
0
0
20px
;
padding
:
0
0
5px
;
font-size
:
20px
;
border-bottom
:
3px
solid
#707070
;
&
:
:
before
{
content
:
'·'
;
position
:
absolute
;
left
:
-30px
;
top
:
50%
;
font-size
:
30px
;
transform
:
translateY
(
-50%
);
}
&
:
:
after
{
content
:
''
;
position
:
absolute
;
left
:
0
;
bottom
:
-8px
;
width
:
100%
;
height
:
1px
;
background-color
:
#707070
;
}
}
.course-viewer-content-hd__aside
{
font-size
:
18px
;
// border-bottom: 3px solid #707070;
}
.course-menu
{
width
:
24px
;
height
:
24px
;
padding
:
12px
;
margin-right
:
10px
;
color
:
#fff
;
text-align
:
center
;
border-radius
:
50%
;
cursor
:
pointer
;
&
:hover
{
background-color
:
rgba
(
255
,
255
,
255
,
0
.08
);
}
}
</
style
>
src/modules/viewer/routes.js
0 → 100644
浏览文件 @
49505039
export
default
[
{
path
:
'/viewer/:sid/:cid'
,
component
:
()
=>
import
(
'./index.vue'
),
children
:
[
{
name
:
'viewerCourseChapter'
,
path
:
':id'
,
component
:
()
=>
import
(
'./components/layout.vue'
)
}
]
}
]
src/pages/account/password.vue
浏览文件 @
49505039
<
template
>
<
template
>
<app-container
title=
"修改密码"
>
<app-container
title=
"修改密码"
>
<el-form
:model=
"ruleForm"
:rules=
"rules"
label-width=
"
9
0px"
ref=
"ruleForm"
class=
"form"
>
<el-form
:model=
"ruleForm"
:rules=
"rules"
label-width=
"
10
0px"
ref=
"ruleForm"
class=
"form"
>
<el-form-item
label=
"旧密码"
prop=
"old_password"
>
<el-form-item
label=
"旧密码"
prop=
"old_password"
>
<el-input
type=
"password"
v-model=
"ruleForm.old_password"
placeholder=
"请输入密码"
></el-input>
<el-input
type=
"password"
v-model=
"ruleForm.old_password"
placeholder=
"请输入密码"
></el-input>
</el-form-item>
</el-form-item>
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
<el-input
type=
"password"
v-model=
"ruleForm.passwordR"
placeholder=
"请重复输入新密码"
></el-input>
<el-input
type=
"password"
v-model=
"ruleForm.passwordR"
placeholder=
"请重复输入新密码"
></el-input>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
type=
"primary"
@
click=
"handleSubmit"
>
保存
</el-button>
<el-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"handleSubmit"
>
保存
</el-button>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
</app-container>
</app-container>
...
@@ -19,16 +19,38 @@
...
@@ -19,16 +19,38 @@
<
script
>
<
script
>
import
AppContainer
from
'@/components/AppContainer'
import
AppContainer
from
'@/components/AppContainer'
import
*
as
api
from
'@/api/account'
export
default
{
export
default
{
components
:
{
AppContainer
},
components
:
{
AppContainer
},
data
()
{
data
()
{
const
validatePass
=
(
rule
,
value
,
callback
)
=>
{
if
(
value
===
''
)
{
callback
(
new
Error
(
'请再次输入密码'
))
}
else
if
(
value
!==
this
.
ruleForm
.
password
)
{
callback
(
new
Error
(
'两次输入密码不一致'
))
}
else
{
callback
()
}
}
return
{
return
{
ruleForm
:
{
ruleForm
:
{
old_password
:
''
,
old_password
:
''
,
password
:
''
,
password
:
''
,
passwordR
:
''
passwordR
:
''
},
},
rules
:
{}
rules
:
{
old_password
:
{
required
:
true
,
message
:
'请输入登录密码'
,
trigger
:
'blur'
},
password
:
[
{
required
:
true
,
message
:
'请输入新的登录密码'
,
trigger
:
'blur'
},
{
min
:
6
,
max
:
20
,
message
:
'长度为6-20个字符'
,
trigger
:
'blur'
}
],
passwordR
:
[
{
required
:
true
,
message
:
'请再次输入新的登录密码'
,
trigger
:
'blur'
},
{
validator
:
validatePass
,
trigger
:
'blur'
}
]
},
submitLoading
:
false
}
}
},
},
methods
:
{
methods
:
{
...
@@ -36,7 +58,17 @@ export default {
...
@@ -36,7 +58,17 @@ export default {
this
.
$refs
.
ruleForm
.
validate
().
then
(
this
.
handleSubmitRequest
)
this
.
$refs
.
ruleForm
.
validate
().
then
(
this
.
handleSubmitRequest
)
},
},
handleSubmitRequest
()
{
handleSubmitRequest
()
{
console
.
log
(
this
.
ruleForm
)
this
.
submitLoading
=
true
api
.
updatePassword
(
this
.
ruleForm
)
.
then
(
response
=>
{
this
.
$message
({
message
:
'密码修改成功'
,
type
:
'success'
})
// 重置表单
this
.
$refs
.
ruleForm
.
resetFields
()
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
}
}
}
}
}
}
...
...
src/pages/course/learn/item.vue
浏览文件 @
49505039
<
template
>
<
template
>
<div
class=
"main-container"
v-loading=
"!loaded"
>
<div
class=
"main-container"
element-loading-text=
"加载中..."
v-loading=
"!loaded"
>
<div
class=
"course-top"
v-if=
"detail.curriculum"
>
<div
class=
"course-top"
v-if=
"detail.curriculum"
>
<div
class=
"course-top-hd"
>
<div
class=
"course-top-hd"
>
<div
class=
"course-top__title"
>
{{
detail
.
curriculum
.
curriculum_name
}}
</div>
<div
class=
"course-top__title"
>
{{
detail
.
curriculum
.
curriculum_name
}}
</div>
...
@@ -10,10 +10,10 @@
...
@@ -10,10 +10,10 @@
</div>
</div>
</div>
</div>
<el-tabs
v-model=
"tabActive"
>
<el-tabs
v-model=
"tabActive"
>
<el-tab-pane
label=
"按章节学习"
>
<el-tab-pane
la
zy
la
bel=
"按章节学习"
>
<course-chapter
:courseId=
"courseId"
:data=
"detail.chapters"
@
on-click=
"handleClick"
></course-chapter>
<course-chapter
:courseId=
"courseId"
:data=
"detail.chapters"
@
on-click=
"handleClick"
></course-chapter>
</el-tab-pane>
</el-tab-pane>
<el-tab-pane
label=
"按考点学习"
>
<el-tab-pane
la
zy
la
bel=
"按考点学习"
>
<course-tag
:courseId=
"courseId"
></course-tag>
<course-tag
:courseId=
"courseId"
></course-tag>
</el-tab-pane>
</el-tab-pane>
</el-tabs>
</el-tabs>
...
...
src/pages/course/tag/api.js
deleted
100644 → 0
浏览文件 @
f63bcf66
import
BaseAPI
from
'@/api/base_api'
const
httpRequest
=
new
BaseAPI
(
webConf
)
/**
* 获取课程列表
*/
export
function
getCourseList
()
{
return
httpRequest
.
get
(
'/api/zy/v2/education/mokuai'
)
}
src/pages/course/tag/components/courseTagMessage.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"course-tag-message"
>
<div
class=
"course-tag-message-hd"
>
<div
class=
"course-tag-message__title"
>
{{
data
.
name
}}
</div>
<div
class=
"course-tag-message__more"
@
click=
"viewMore"
v-if=
"hasMore"
>
<span>
更多
</span>
<van-icon
name=
"arrow"
/>
</div>
</div>
<div
class=
"course-tag-message-bd"
v-if=
"dataList && dataList.length"
>
<ul
class=
"message-tag-list"
ref=
"content"
>
<template
v-for=
"item in dataList"
>
<li
class=
"course-tag-item"
:key=
"item.id"
@
click=
"onClick(item)"
>
<p>
{{
item
.
title
}}{{
item
.
free
?
'(免费)'
:
''
}}
</p>
</li>
</
template
>
</ul>
</div>
</div>
</template>
<
script
>
import
{
mapState
}
from
'vuex'
export
default
{
name
:
'CourseTagMessage'
,
props
:
{
courseId
:
{
type
:
String
},
isTest
:
{
type
:
Boolean
,
default
:
false
},
data
:
{
type
:
Object
}
},
data
()
{
return
{
maxCount
:
8
}
},
computed
:
{
...
mapState
([
'isWeapp'
,
'isAndroid'
,
'isIos'
,
'isVip'
,
'isLogin'
]),
hasMore
()
{
return
this
.
data
.
tag
?
this
.
data
.
tag
.
length
>
this
.
maxCount
:
false
},
// 最多显示8条
dataList
()
{
let
list
=
this
.
data
.
tag
||
[]
if
(
!
this
.
isVip
)
{
// 免费的在前
list
=
[...
list
.
filter
(
item
=>
item
.
free
),
...
list
.
filter
(
item
=>
!
item
.
free
)]
}
return
list
.
slice
(
0
,
this
.
maxCount
)
},
moreText
()
{
if
(
this
.
isVip
)
{
return
'更多'
}
if
(
this
.
isIos
)
{
return
'更多请开通'
}
return
'更多请购买'
}
},
methods
:
{
onClick
(
data
)
{
// 未登录
if
(
!
data
.
free
&&
!
this
.
isLogin
)
{
this
.
isWeapp
?
wx
.
miniProgram
.
navigateTo
({
url
:
'/pages/login/index'
})
:
this
.
$router
.
push
({
name
:
'login'
})
return
}
// 未开通
if
(
!
data
.
free
&&
!
this
.
isVip
)
{
this
.
isWeapp
?
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
window
.
location
.
origin
}
/pay`
})
:
this
.
$router
.
push
({
name
:
'pay'
})
return
}
if
(
this
.
isTest
)
{
// 知识点测试
const
path
=
`/exam/courseNodeExam?tag_id=
${
data
.
id
}
`
if
(
this
.
isWeapp
)
{
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
this
.
$router
.
push
({
path
})
}
}
else
{
this
.
$emit
(
'change'
,
data
)
}
},
viewMore
()
{
const
path
=
`/course/learn/
${
this
.
courseId
}
/tag/
${
this
.
data
.
id
}
?is_test=
${
this
.
isTest
?
'1'
:
'0'
}
`
if
(
this
.
isWeapp
)
{
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
this
.
$router
.
push
({
path
})
}
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.course-tag-message
{
padding
:
10px
;
background-color
:
#fff
;
border-radius
:
6px
;
}
.course-tag-message-hd
{
display
:
flex
;
}
.course-tag-message__title
{
flex
:
1
;
font-size
:
15px
;
font-weight
:
bold
;
color
:
#222
;
}
.course-tag-message__more
{
display
:
flex
;
align-items
:
center
;
margin-left
:
20px
;
font-size
:
13px
;
color
:
#f47885
;
}
.message-tag-list
{
margin-top
:
20px
;
}
.course-tag-item
{
display
:
inline-block
;
max-width
:
100%
;
margin
:
0
10px
10px
0
;
padding
:
0
15px
;
background
:
#f47885
;
border-radius
:
12px
;
cursor
:
pointer
;
p
{
font-size
:
13px
;
color
:
#fff
;
line-height
:
24px
;
}
}
.more
{
padding-top
:
10px
;
border-top
:
1px
solid
#eee
;
font-size
:
13px
;
color
:
#222
;
text-align
:
center
;
cursor
:
pointer
;
}
</
style
>
src/pages/course/tag/components/messageCard.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"message-card"
:class=
"classes"
>
<div
class=
"message-card-content"
>
<div
class=
"message-arrow"
></div>
<slot
:data=
"data.payload"
>
<div
class=
"message-card-text"
v-if=
"data.type === 0"
>
{{
data
.
payload
.
text
}}
</div>
<course-tag-message
:courseId=
"courseId"
:isTest=
"isTest"
:data=
"data.payload"
v-on=
"$listeners"
v-if=
"data.type === 1"
></course-tag-message>
<search-tag-message
:courseId=
"courseId"
:isTest=
"isTest"
:data=
"data.payload"
v-on=
"$listeners"
v-if=
"data.type === 2"
></search-tag-message>
<tag-message
:courseId=
"courseId"
:data=
"data.payload"
v-on=
"$listeners"
v-if=
"data.type === 3"
></tag-message>
</slot>
</div>
</div>
</
template
>
<
script
>
import
CourseTagMessage
from
'./courseTagMessage.vue'
import
SearchTagMessage
from
'./searchTagMessage.vue'
import
TagMessage
from
'./tagMessage.vue'
export
default
{
name
:
'MessageCard'
,
props
:
{
courseId
:
{
type
:
String
},
isTest
:
{
type
:
Boolean
,
default
:
false
},
data
:
{
type
:
Object
}
},
components
:
{
CourseTagMessage
,
SearchTagMessage
,
TagMessage
},
computed
:
{
isMyPublish
()
{
return
this
.
data
.
from
===
'user'
},
classes
()
{
return
{
'is-my'
:
this
.
isMyPublish
,
'is-system'
:
!
this
.
isMyPublish
}
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.message-card
{
clear
:
both
;
margin-bottom
:
20px
;
}
.message-card-text
{
display
:
inline-block
;
padding
:
10px
;
background-color
:
#fff
;
border-radius
:
6px
;
}
.is-my
{
.message-arrow
{
position
:
absolute
;
right
:
-5px
;
top
:
16px
;
width
:
0
;
height
:
0
;
border-top
:
4px
solid
transparent
;
border-bottom
:
4px
solid
transparent
;
border-left
:
5px
solid
#f47885
;
}
.message-card-content
{
text-align
:
right
;
}
.message-card-text
{
text-align
:
left
;
color
:
#fff
;
background-color
:
#f47885
;
}
}
.message-card-content
{
position
:
relative
;
}
.is-system
{
.message-arrow
{
position
:
absolute
;
left
:
-5px
;
top
:
16px
;
width
:
0
;
height
:
0
;
border-top
:
4px
solid
transparent
;
border-bottom
:
4px
solid
transparent
;
border-right
:
5px
solid
#fff
;
}
}
</
style
>
src/pages/course/tag/components/searchTagMessage.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"search-tag-message"
>
<p
class=
"tips"
>
交小通猜你想查:
</p>
<ul
class=
"search-tag-list"
>
<template
v-for=
"(item,index) in dataList"
>
<li
class=
"search-tag-item"
:key=
"item.id"
@
click=
"onClick(item)"
>
<span
class=
"num"
>
{{
index
+
1
}}
:
</span>
<span
class=
"text"
>
{{
item
.
title
}}{{
item
.
free
?
'(免费)'
:
''
}}
</span>
</li>
</
template
>
</ul>
<div
class=
"more"
@
click=
"toggleMore"
v-if=
"hasMore"
>
<
template
v-if=
"!showMore"
>
<span>
更多
</span>
<van-icon
name=
"arrow-down"
></van-icon>
</
template
>
<
template
v-else
>
<span>
收起
</span>
<van-icon
name=
"arrow-up"
></van-icon>
</
template
>
</div>
</div>
</template>
<
script
>
export
default
{
name
:
'SearchTagMessage'
,
props
:
{
courseId
:
{
type
:
String
},
isTest
:
{
type
:
Boolean
,
default
:
false
},
data
:
{
type
:
Array
,
default
()
{
return
[]
}
}
},
data
()
{
return
{
showMore
:
false
,
maxCount
:
7
}
},
computed
:
{
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
},
isVip
()
{
return
this
.
$store
.
state
.
isVip
},
isLogin
()
{
return
this
.
$store
.
state
.
isLogin
},
hasMore
()
{
return
this
.
data
.
length
>
this
.
maxCount
},
dataList
()
{
if
(
this
.
hasMore
&&
!
this
.
showMore
)
{
return
this
.
data
.
filter
((
item
,
index
)
=>
index
<
this
.
maxCount
)
}
return
this
.
data
}
},
methods
:
{
onClick
(
data
)
{
// 未登录
if
(
!
data
.
free
&&
!
this
.
isLogin
)
{
this
.
isWeapp
?
wx
.
miniProgram
.
navigateTo
({
url
:
'/pages/login/index'
})
:
this
.
$router
.
push
({
name
:
'login'
})
return
}
// 未开通
if
(
!
data
.
free
&&
!
this
.
isVip
)
{
this
.
isWeapp
?
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
window
.
location
.
origin
}
/pay`
})
:
this
.
$router
.
push
({
name
:
'pay'
})
return
}
if
(
this
.
isTest
)
{
// 知识点测试
const
path
=
`/exam/courseNodeExam?tag_id=
${
data
.
id
}
`
if
(
this
.
isWeapp
)
{
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
this
.
$router
.
push
({
path
})
}
}
else
{
this
.
$emit
(
'change'
,
data
)
}
},
toggleMore
()
{
this
.
showMore
=
!
this
.
showMore
}
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.search-tag-message
{
padding
:
10px
;
background-color
:
#fff
;
border-radius
:
6px
;
}
.search-tag-item
{
display
:
flex
;
margin
:
10px
0
;
.num
{
padding-right
:
5px
;
}
.text
{
border-bottom
:
1px
solid
#c62245
;
cursor
:
pointer
;
}
}
.more
{
padding-top
:
10px
;
border-top
:
1px
solid
#eee
;
font-size
:
13px
;
color
:
#222
;
text-align
:
center
;
cursor
:
pointer
;
}
</
style
>
src/pages/course/tag/components/tagMessage.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div>
<div
class=
"tag-message"
:class=
"classes"
@
click=
"showMore = true"
>
<div
class=
"tag-message-bd"
>
<div
class=
"tag-message-content"
ref=
"content"
v-html=
"html"
></div>
</div>
<div
class=
"more"
@
click
.
stop=
"toggleMore"
v-if=
"hasMore"
>
<template
v-if=
"!showMore"
>
<span>
更多
</span>
<van-icon
name=
"arrow-down"
></van-icon>
</
template
>
<
template
v-else
>
<span>
收起
</span>
<van-icon
name=
"arrow-up"
></van-icon>
</
template
>
</div>
</div>
<div
class=
"tools"
>
<ul>
<li
@
click=
"toExamPage"
v-if=
"data.has_kaoshi"
>
相关试题
</li>
<li
@
click=
"toCourseVideo"
v-if=
"data.has_video"
>
相关视频
</li>
</ul>
</div>
</div>
</template>
<
script
>
export
default
{
name
:
'TagMessage'
,
props
:
{
courseId
:
{
type
:
String
},
data
:
{
type
:
Object
}
},
data
()
{
return
{
showMore
:
false
,
maxHeight
:
90
,
contentHeight
:
0
}
},
computed
:
{
hasMore
()
{
return
this
.
contentHeight
>
this
.
maxHeight
},
classes
()
{
return
{
'has-more'
:
this
.
hasMore
&&
!
this
.
showMore
}
},
html
()
{
let
contents
=
this
.
data
.
contents
||
''
const
hasHtmlTag
=
contents
.
includes
(
'</p>'
)
contents
=
contents
.
replace
(
/---------- start ----------
[\n]{0,1}
/gi
,
''
)
.
replace
(
/---------- end ----------/gi
,
''
)
return
hasHtmlTag
?
contents
:
contents
.
replace
(
/
\n
/g
,
'<br/>'
)
},
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
}
},
methods
:
{
toggleMore
()
{
this
.
showMore
=
!
this
.
showMore
},
viewMore
()
{
if
(
this
.
isWeapp
)
{
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
window
.
location
.
origin
}
/course/tag/
${
this
.
data
.
id
}
`
})
}
else
{
this
.
$router
.
push
({
name
:
'courseTagItem'
,
params
:
{
id
:
this
.
data
.
id
}
})
}
},
// 去知识点考试页面
toExamPage
()
{
const
path
=
`/exam/courseNodeExam?tag_id=
${
this
.
data
.
id
}
`
if
(
this
.
isWeapp
)
{
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
this
.
$router
.
push
({
path
})
}
},
// 去课程视频页面
toCourseVideo
()
{
if
(
this
.
isWeapp
)
{
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/course/item?id=
${
this
.
data
.
course_id
}
&chapter_id=
${
this
.
data
.
section_id
}
`
})
}
else
{
window
.
alert
(
'请在微信小程序中打开'
)
}
}
},
mounted
()
{
this
.
contentHeight
=
this
.
$refs
.
content
.
offsetHeight
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.tag-message
{
padding
:
10px
;
background-color
:
#fff
;
border-radius
:
6px
;
}
.has-more
.tag-message-bd
{
height
:
90px
;
overflow
:
hidden
;
}
.tag-message-content
{
font-size
:
13px
;
line-height
:
30px
;
::v-deep
img
{
max-width
:
100%
;
}
::v-deep
b
{
font-weight
:
bold
;
}
}
.more
{
padding-top
:
10px
;
border-top
:
1px
solid
#eee
;
font-size
:
13px
;
color
:
#222
;
text-align
:
center
;
cursor
:
pointer
;
}
.tools
{
margin-top
:
5px
;
li
{
display
:
inline-block
;
height
:
24px
;
margin
:
0
10px
10px
0
;
padding
:
0
15px
;
font-size
:
13px
;
color
:
#fff
;
line-height
:
24px
;
background
:
#f47885
;
border-radius
:
12px
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
cursor
:
pointer
;
}
}
</
style
>
src/pages/course/tag/index.vue
浏览文件 @
49505039
<
template
>
<
template
>
<div
class=
"course-tag"
>
<el-collapse
v-model=
"activeNames"
>
<div
class=
"messages"
ref=
"messages"
>
<el-collapse-item
:title=
"item.name"
:name=
"item.id"
v-for=
"item in detail.chapters"
:key=
"item.id"
>
<template
v-for=
"item in messageList"
>
<ul>
<message-card
<li
v-for=
"subItem in item.tag"
:key=
"subItem.id"
@
click=
"handleClick(subItem)"
>
:courseId=
"courseId"
<div
class=
"name"
>
{{
subItem
.
title
}}
</div>
:isTest=
"isTest"
</li>
:data=
"item"
</ul>
:key=
"item.id"
</el-collapse-item>
@
search=
"onSearchTag"
</el-collapse>
@
change=
"onChangeTag"
></message-card>
</
template
>
</div>
<div
class=
"send"
>
<div
class=
"inner"
>
<form
action=
"/"
>
<van-search
v-model=
"searchValue"
left-icon
show-action
background=
"#f7f7f7"
:maxlength=
"40"
placeholder=
"全国道路运输安全生产管理培训"
@
search=
"onSearch"
>
<
template
#
action
>
<div
@
click=
"onSearch"
>
搜索
</div>
</
template
>
</van-search>
</form>
</div>
</div>
<div
class=
"backtop"
@
click=
"scrollTop"
v-show=
"showBacktop"
></div>
</div>
</
template
>
</
template
>
<
script
>
<
script
>
import
MessageCard
from
'./components/messageCard.vue'
import
*
as
api
from
'@/api/course.js'
import
*
as
api
from
'@/api/course.js'
export
default
{
export
default
{
props
:
{
props
:
{
...
@@ -48,164 +22,56 @@ export default {
...
@@ -48,164 +22,56 @@ export default {
},
},
isTest
:
{
type
:
Boolean
,
default
:
false
}
isTest
:
{
type
:
Boolean
,
default
:
false
}
},
},
components
:
{
MessageCard
},
data
()
{
data
()
{
return
{
return
{
detail
:
{
chapters
:
[]
},
detail
:
{
chapters
:
[]
},
messageList
:
[],
// {id:'', type: 1, payload: {}}
activeNames
:
[]
searchValue
:
''
,
showBacktop
:
false
}
},
watch
:
{
messageList
(
list
)
{
if
(
list
.
length
>
this
.
detail
.
chapters
.
length
)
{
this
.
scrollBottom
()
}
this
.
$emit
(
'message'
,
list
)
}
}
},
},
methods
:
{
methods
:
{
// 获取知识点列表
// 获取知识点列表
getCourseTagList
()
{
getCourseTagList
()
{
api
.
getCourseTagList
(
this
.
courseId
).
then
(
response
=>
{
api
.
getCourseTagList
(
this
.
courseId
).
then
(
response
=>
{
this
.
$emit
(
'ready'
,
response
)
this
.
detail
=
response
this
.
detail
=
response
this
.
messageList
=
response
.
chapters
.
map
((
item
,
index
)
=>
{
return
{
id
:
this
.
genId
(
index
),
type
:
1
,
from
:
'system'
,
payload
:
item
}
})
})
},
// 输入搜索
onSearch
()
{
if
(
!
this
.
searchValue
.
trim
())
{
return
}
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
0
,
from
:
'user'
,
payload
:
{
text
:
this
.
searchValue
}
})
this
.
searchTag
(
this
.
searchValue
)
this
.
searchValue
=
''
},
// 点击标签
onSearchTag
(
data
)
{
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
0
,
from
:
'user'
,
payload
:
{
text
:
data
.
name
}
})
this
.
searchTag
(
data
.
name
)
},
searchTag
(
keywords
)
{
api
.
getSearchTagList
({
keywords
,
course_id
:
this
.
courseId
}).
then
(
response
=>
{
if
(
response
.
length
)
{
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
2
,
from
:
'system'
,
payload
:
response
})
}
else
{
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
0
,
from
:
'system'
,
payload
:
{
text
:
'找不到相关内容'
}
})
}
})
},
onChangeTag
(
data
)
{
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
0
,
from
:
'user'
,
payload
:
{
text
:
data
.
title
}
})
this
.
getCourseTag
(
data
.
id
)
},
// 获取知识点详情
getCourseTag
(
tagId
)
{
api
.
getCourseTag
(
tagId
).
then
(
response
=>
{
this
.
messageList
.
push
({
id
:
this
.
genId
(),
type
:
3
,
from
:
'system'
,
payload
:
response
})
})
},
// 生成消息ID
genId
(
index
)
{
index
=
index
||
this
.
messageList
.
length
return
`message_
${
index
}
`
},
// 滚动到底部
scrollBottom
()
{
this
.
$nextTick
(()
=>
{
window
.
scrollTo
(
0
,
document
.
body
.
scrollHeight
)
})
})
},
},
// 滚到到顶部
handleClick
(
data
)
{
scrollTop
()
{
this
.
$router
.
push
({
name
:
'courseTagItem'
,
params
:
{
id
:
data
.
id
}
})
window
.
scrollTo
(
0
,
0
)
},
handleScroll
()
{
const
scrollTop
=
window
.
pageYOffset
||
document
.
documentElement
.
scrollTop
||
document
.
body
.
scrollTop
this
.
showBacktop
=
scrollTop
>=
10
}
}
},
},
beforeMount
()
{
beforeMount
()
{
this
.
getCourseTagList
()
this
.
getCourseTagList
()
},
mounted
()
{
window
.
addEventListener
(
'scroll'
,
this
.
handleScroll
)
},
destroyed
()
{
window
.
removeEventListener
(
'scroll'
,
this
.
handleScroll
)
}
}
}
}
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
.
messages
{
.
el-collapse
{
b
ackground
:
#eee
;
b
order
:
0
;
}
}
.send
{
::v-deep
.el-collapse-item__header
{
height
:
50px
;
font-size
:
14px
;
padding-bottom
:
env
(
safe-area-inset-bottom
);
font-weight
:
600
;
.inner
{
color
:
#222
;
position
:
fixed
;
}
left
:
0
;
::v-deep
.el-collapse-item__content
{
right
:
0
;
padding-bottom
:
10px
;
bottom
:
0
;
background
:
rgba
(
247
,
247
,
247
,
1
);
box-shadow
:
0px
0px
6px
0px
rgba
(
0
,
0
,
0
,
0
.05
);
z-index
:
999
;
padding-bottom
:
env
(
safe-area-inset-bottom
);
}
::v-deep
.van-search__content
{
background-color
:
#fff
;
}
}
}
.backtop
{
li
{
position
:
fixed
;
display
:
flex
;
right
:
15px
;
padding
:
5px
0
;
bottom
:
64px
;
width
:
46px
;
height
:
46px
;
background
:
url('../../../assets/images/icon_backtop.png')
no-repeat
;
background-size
:
contain
;
cursor
:
pointer
;
cursor
:
pointer
;
margin-bottom
:
env
(
safe-area-inset-bottom
);
color
:
#666
;
&
:hover
{
color
:
#c01540
;
}
.name
{
flex
:
1
;
overflow
:
hidden
;
}
.progress
{
margin-left
:
20px
;
color
:
#999
;
}
}
}
</
style
>
</
style
>
src/pages/course/tag/item.vue
浏览文件 @
49505039
<
template
>
<
template
>
<div
class=
"course-tag-wrapper"
v-show=
"loaded"
>
<div
class=
"course-tag-wrapper"
element-loading-text=
"加载中..."
v-loading=
"!loaded"
>
<div
class=
"course-tag-hd"
>
<h1
class=
"tag-title"
>
{{
detail
.
title
}}
</h1>
</div>
<div
class=
"course-tag-bd"
>
<div
class=
"course-tag-bd"
>
<div
class=
"course-tag-main"
>
<div
class=
"course-tag-main"
>
<div
class=
"tag-content"
v-html=
"html"
></div>
<div
class=
"tag-content"
v-html=
"html"
></div>
</div>
</div>
<div
class=
"tools"
>
<div
class=
"tools"
>
<div
class=
"inner"
>
<div>
<ul>
<el-button
type=
"primary"
size=
"medium"
@
click=
"toExamPage"
v-if=
"detail.has_kaoshi"
>
去测试
</el-button>
<li
@
click=
"toExamPage"
v-if=
"detail.has_kaoshi"
>
去测试
</li>
<el-button
type=
"primary"
size=
"medium"
@
click=
"toCourseVideo"
v-if=
"detail.has_video"
>
相关视频
</el-button>
<li
@
click=
"toCourseVideo"
v-if=
"detail.has_video"
>
相关视频
</li>
</div>
</ul>
<div>
<ul>
<el-button
type=
"primary"
size=
"medium"
@
click=
"getCourseTag(detail.last)"
v-if=
"detail.last"
<li
@
click=
"getCourseTag(detail.last)"
v-if=
"detail.last"
>
上一点
</li>
>
上一点
</el-button
<li
@
click=
"getCourseTag(detail.next)"
v-if=
"detail.next"
>
下一点
</li>
>
</ul>
<el-button
type=
"primary"
size=
"medium"
@
click=
"getCourseTag(detail.next)"
v-if=
"detail.next"
>
下一点
</el-button
>
</div>
</div>
</div>
</div>
</div>
</div>
...
@@ -49,9 +48,6 @@ export default {
...
@@ -49,9 +48,6 @@ export default {
.
replace
(
/---------- start ----------
[\n]{0,1}
/gi
,
''
)
.
replace
(
/---------- start ----------
[\n]{0,1}
/gi
,
''
)
.
replace
(
/---------- end ----------/gi
,
''
)
.
replace
(
/---------- end ----------/gi
,
''
)
return
hasHtmlTag
?
contents
:
contents
.
replace
(
/
\n
/g
,
'<br/>'
)
return
hasHtmlTag
?
contents
:
contents
.
replace
(
/
\n
/g
,
'<br/>'
)
},
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
}
}
},
},
methods
:
{
methods
:
{
...
@@ -66,25 +62,10 @@ export default {
...
@@ -66,25 +62,10 @@ export default {
// 去知识点考试页面
// 去知识点考试页面
toExamPage
()
{
toExamPage
()
{
const
path
=
`/exam/courseNodeExam?tag_id=
${
this
.
detail
.
id
}
`
const
path
=
`/exam/courseNodeExam?tag_id=
${
this
.
detail
.
id
}
`
if
(
this
.
isWeapp
)
{
this
.
$router
.
push
({
path
})
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
this
.
$router
.
push
({
path
})
}
},
},
// 去课程视频页面
// 去课程视频页面
toCourseVideo
()
{
toCourseVideo
()
{}
if
(
this
.
isWeapp
)
{
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/course/item?id=
${
this
.
detail
.
course_id
}
&chapter_id=
${
this
.
detail
.
section_id
}
`
})
}
else
{
window
.
alert
(
'请在微信小程序中打开'
)
}
}
},
},
beforeMount
()
{
beforeMount
()
{
this
.
getCourseTag
()
this
.
getCourseTag
()
...
@@ -93,25 +74,10 @@ export default {
...
@@ -93,25 +74,10 @@ export default {
</
script
>
</
script
>
<
style
lang=
"scss"
scoped
>
<
style
lang=
"scss"
scoped
>
.course-tag-wrapper
{
min-height
:
100vh
;
background-color
:
#eee
;
box-sizing
:
border-box
;
}
.course-tag-hd
{
position
:
sticky
;
top
:
0
;
padding
:
20px
;
background-color
:
#fff
;
z-index
:
10
;
}
.course-tag-bd
{
padding
:
20px
;
}
.course-tag-main
{
.course-tag-main
{
padding
:
1
0px
;
padding
:
3
0px
;
background-color
:
#fff
;
background-color
:
#fff
;
border-radius
:
6
px
;
border-radius
:
8
px
;
}
}
.tag-title
{
.tag-title
{
font-size
:
15px
;
font-size
:
15px
;
...
@@ -120,7 +86,6 @@ export default {
...
@@ -120,7 +86,6 @@ export default {
line-height
:
24px
;
line-height
:
24px
;
}
}
.tag-content
{
.tag-content
{
padding
:
10px
0
;
font-size
:
13px
;
font-size
:
13px
;
line-height
:
22px
;
line-height
:
22px
;
::v-deep
img
{
::v-deep
img
{
...
@@ -131,44 +96,15 @@ export default {
...
@@ -131,44 +96,15 @@ export default {
}
}
}
}
.tools
{
.tools
{
margin-top
:
5px
;
height
:
50px
;
height
:
50px
;
padding-bottom
:
env
(
safe-area-inset-bottom
);
position
:
sticky
;
.inner
{
bottom
:
0
;
position
:
fixed
;
display
:
flex
;
left
:
0
;
justify-content
:
space-between
;
right
:
0
;
align-items
:
center
;
bottom
:
0
;
height
:
50px
;
display
:
flex
;
padding
:
0
30px
;
justify-content
:
space-between
;
background-color
:
#fff
;
align-items
:
center
;
box-shadow
:
0px
0px
6px
0px
rgba
(
0
,
0
,
0
,
0
.05
);
height
:
50px
;
padding
:
0
20px
;
padding-bottom
:
env
(
safe-area-inset-bottom
);
background-color
:
#fff
;
box-shadow
:
0px
0px
6px
0px
rgba
(
0
,
0
,
0
,
0
.05
);
}
ul
{
font-size
:
0
;
}
li
{
display
:
inline-block
;
height
:
35px
;
margin-left
:
10px
;
// padding: 0 30px;
padding
:
0
10px
;
font-size
:
13px
;
color
:
#fff
;
line-height
:
35px
;
background
:
#c62245
;
border-radius
:
6px
;
white-space
:
nowrap
;
text-overflow
:
ellipsis
;
overflow
:
hidden
;
cursor
:
pointer
;
&
:first-child
{
margin-left
:
0
;
}
}
}
}
</
style
>
</
style
>
src/pages/course/tag/list.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"course-tag-wrapper"
v-show=
"loaded"
>
<div
class=
"course-tag-hd"
>
<h1
class=
"course-title"
>
{{
chapter
.
name
}}
</h1>
</div>
<div
class=
"course-tag-bd"
>
<div
class=
"course-tag-main"
>
<ul
class=
"tag-list"
>
<li
class=
"tag-item"
v-for=
"item in tagList"
:key=
"item.id"
@
click=
"onClick(item)"
>
<div
class=
"tag-item__title"
>
<span>
{{
item
.
title
}}{{
item
.
free
?
'(免费)'
:
''
}}
</span>
</div>
<!--
<div
class=
"tag-item__free"
v-if=
"item.free"
>
(免费)
</div>
-->
<div
class=
"tag-item__arrow"
>
<van-icon
name=
"arrow"
/>
</div>
</li>
</ul>
</div>
</div>
</div>
</
template
>
<
script
>
import
*
as
api
from
'@/api/course.js'
export
default
{
name
:
'CourseTagList'
,
metaInfo
()
{
return
{
title
:
this
.
detail
.
course_name
||
''
}
},
data
()
{
return
{
loaded
:
false
,
detail
:
{
chapters
:
[]
}
}
},
computed
:
{
courseId
()
{
return
this
.
$route
.
params
.
courseId
},
chapterId
()
{
return
this
.
$route
.
params
.
chapterId
},
chapter
()
{
const
found
=
this
.
detail
.
chapters
.
find
(
item
=>
item
.
id
===
this
.
chapterId
)
return
found
||
{}
},
tagList
()
{
return
this
.
chapter
.
tag
||
[]
},
isTest
()
{
return
this
.
$route
.
query
.
is_test
===
'1'
},
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
}
},
methods
:
{
// 获取知识点列表
getCourseTagList
()
{
this
.
loaded
=
false
api
.
getCourseTagList
(
this
.
courseId
).
then
(
response
=>
{
this
.
loaded
=
true
this
.
detail
=
response
})
},
onClick
({
id
})
{
const
path
=
`/exam/courseNodeExam?tag_id=
${
id
}
`
if
(
this
.
isWeapp
)
{
if
(
this
.
isTest
)
{
// 知识点测试
const
src
=
encodeURIComponent
(
`
${
window
.
location
.
origin
}${
path
}
`
)
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
src
}
`
})
}
else
{
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/web/index?src=
${
window
.
location
.
origin
}
/course/tag/
${
id
}
`
})
}
}
else
{
if
(
this
.
isTest
)
{
// 知识点测试
this
.
$router
.
push
({
path
})
}
else
{
this
.
$router
.
push
({
name
:
'courseTagItem'
,
params
:
{
id
}
})
}
}
}
},
beforeMount
()
{
this
.
getCourseTagList
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.course-tag-wrapper
{
min-height
:
100vh
;
background-color
:
#eee
;
box-sizing
:
border-box
;
}
.course-tag-hd
{
position
:
sticky
;
top
:
0
;
padding
:
20px
;
background-color
:
#fff
;
z-index
:
10
;
}
.course-tag-bd
{
padding
:
20px
;
}
.course-tag-main
{
padding
:
10px
;
background-color
:
#fff
;
border-radius
:
6px
;
}
.course-title
{
font-size
:
15px
;
font-weight
:
bold
;
color
:
#222
;
}
.tag-title
{
font-size
:
15px
;
font-weight
:
bold
;
color
:
#222
;
line-height
:
24px
;
}
.tag-item
{
display
:
flex
;
align-items
:
center
;
padding
:
15px
0
;
}
.tag-item
+
.tag-item
{
border-top
:
1px
solid
#eee
;
}
.tag-item__title
{
flex
:
1
;
overflow
:
hidden
;
span
{
display
:
inline-block
;
padding
:
0
15px
;
font-size
:
13px
;
color
:
#fff
;
line-height
:
24px
;
background
:
#f47885
;
border-radius
:
12px
;
}
}
.tag-item__free
{
margin-left
:
10px
;
color
:
#999
;
}
.tag-item__arrow
{
margin-left
:
10px
;
height
:
16px
;
font-size
:
16px
;
color
:
#999
;
}
</
style
>
src/pages/feedback/index.vue
浏览文件 @
49505039
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
<el-input
v-model=
"ruleForm.title"
style=
"max-width: 280px"
></el-input>
<el-input
v-model=
"ruleForm.title"
style=
"max-width: 280px"
></el-input>
</el-form-item>
</el-form-item>
<el-form-item>
<el-form-item>
<el-button
type=
"primary"
@
click=
"handleSubmit"
>
提交
</el-button>
<el-button
type=
"primary"
:loading=
"submitLoading"
@
click=
"handleSubmit"
>
提交
</el-button>
</el-form-item>
</el-form-item>
</el-form>
</el-form>
</app-container>
</app-container>
...
@@ -16,15 +16,20 @@
...
@@ -16,15 +16,20 @@
<
script
>
<
script
>
import
AppContainer
from
'@/components/AppContainer'
import
AppContainer
from
'@/components/AppContainer'
import
*
as
api
from
'@/api/my'
export
default
{
export
default
{
components
:
{
AppContainer
},
components
:
{
AppContainer
},
data
()
{
data
()
{
return
{
return
{
ruleForm
:
{
ruleForm
:
{
contents
:
''
,
title
:
''
,
title
:
''
contents
:
''
},
},
rules
:
{}
rules
:
{
title
:
{
required
:
true
,
message
:
'请输入您的联系方式'
,
trigger
:
'blur'
},
contents
:
{
required
:
true
,
message
:
'请输入您的意见'
,
trigger
:
'blur'
}
},
submitLoading
:
false
}
}
},
},
methods
:
{
methods
:
{
...
@@ -32,7 +37,17 @@ export default {
...
@@ -32,7 +37,17 @@ export default {
this
.
$refs
.
ruleForm
.
validate
().
then
(
this
.
handleSubmitRequest
)
this
.
$refs
.
ruleForm
.
validate
().
then
(
this
.
handleSubmitRequest
)
},
},
handleSubmitRequest
()
{
handleSubmitRequest
()
{
console
.
log
(
this
.
ruleForm
)
this
.
submitLoading
=
true
api
.
submitFeedback
(
this
.
ruleForm
)
.
then
(
response
=>
{
this
.
$message
({
message
:
'提交成功'
,
type
:
'success'
})
// 重置表单
this
.
$refs
.
ruleForm
.
resetFields
()
})
.
finally
(()
=>
{
this
.
submitLoading
=
false
})
}
}
}
}
}
}
...
...
src/pages/my/course/index.vue
浏览文件 @
49505039
<
template
>
<
template
>
<
div></div
>
<
course-list
:showProgress=
"true"
/
>
</
template
>
</
template
>
<
script
>
import
CourseList
from
'@/components/CourseList.vue'
export
default
{
name
:
'CourseLearn'
,
components
:
{
CourseList
}
}
</
script
>
src/pages/my/learn/index.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"main-container"
style=
"margin:0 0.15rem"
>
<course-list
:show-progress=
"true"
:requestCallback=
"requestCallback"
@
on-click=
"handleClick"
>
<template
#
empty
>
<van-empty
description=
"您还没有学习课程,快快开始学习吧!"
/>
</
template
>
</course-list>
</div>
</template>
<
script
>
import
CourseList
from
'@/components/CourseList.vue'
export
default
{
name
:
'MyCourse'
,
components
:
{
CourseList
},
metaInfo
:
{
title
:
'已学课程'
},
data
()
{
return
{}
},
computed
:
{
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
}
},
methods
:
{
requestCallback
(
response
)
{
// 扁平数据
const
flattenList
=
response
.
reduce
((
result
,
item
)
=>
{
return
result
.
concat
(
item
.
child
)
},
[])
// 查找有没有进度,没有进度返回空数组
const
found
=
flattenList
.
find
(
item
=>
item
.
video_progress
)
return
found
?
response
:
[]
},
handleClick
(
data
)
{
if
(
this
.
isWeapp
)
{
const
path
=
`
${
window
.
location
.
origin
}
/my/learn/
${
data
.
id
}
?refresh=true`
const
url
=
`/pages/web/index?src=
${
encodeURIComponent
(
path
)}
`
wx
.
miniProgram
.
navigateTo
({
url
})
}
else
{
this
.
$router
.
push
({
name
:
'myLearnItem'
,
params
:
{
id
:
data
.
id
}
})
}
}
}
}
</
script
>
src/pages/my/learn/item.vue
deleted
100644 → 0
浏览文件 @
f63bcf66
<
template
>
<div
class=
"main-container"
v-if=
"loaded"
>
<course-chapter
:courseId=
"courseId"
:data=
"detail.chapters"
:show-progress=
"true"
@
on-click=
"handleClick"
v-if=
"detail.chapters && detail.chapters.length"
></course-chapter>
<van-empty
description=
"暂无内容"
v-else
/>
</div>
</
template
>
<
script
>
import
CourseChapter
from
'@/components/CourseChapter.vue'
import
*
as
api
from
'@/api/course.js'
export
default
{
name
:
'CourseLearnItem'
,
components
:
{
CourseChapter
},
metaInfo
()
{
return
{
title
:
this
.
detail
.
course_name
||
''
}
},
data
()
{
return
{
tabActive
:
0
,
loaded
:
false
,
detail
:
{}
}
},
computed
:
{
courseId
()
{
return
this
.
$route
.
params
.
id
},
isWeapp
()
{
return
this
.
$store
.
state
.
isWeapp
},
isVip
()
{
return
this
.
$store
.
state
.
isVip
}
},
methods
:
{
// 课程学习
getCourse
()
{
this
.
loaded
=
false
api
.
getCourse
(
this
.
courseId
).
then
(
response
=>
{
this
.
loaded
=
true
response
.
chapters
=
response
.
chapters
.
filter
(
item
=>
{
item
.
children
=
item
.
children
.
filter
(
child
=>
child
.
type
===
2
&&
child
.
resource_id
&&
child
.
resource_id
!==
'6684350363920760832'
)
return
item
.
children
.
length
})
this
.
detail
=
response
})
},
handleClick
(
data
)
{
if
(
this
.
isWeapp
)
{
wx
.
miniProgram
.
navigateTo
({
url
:
`/pages/course/item?id=
${
this
.
courseId
}
&chapter_id=
${
data
.
id
}
`
})
}
else
{
window
.
alert
(
'请在微信小程序中打开'
)
}
}
},
beforeMount
()
{
this
.
getCourse
()
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.course-title
{
font-size
:
15px
;
font-weight
:
600
;
color
:
#222
;
padding
:
20px
0
10px
;
}
.tab-content
{
margin-left
:
-0
.4rem
;
margin-right
:
-0
.4rem
;
min-height
:
100vh
;
background
:
#eee
;
padding
:
0
.4rem
;
}
</
style
>
src/router/routes.js
浏览文件 @
49505039
...
@@ -2,6 +2,7 @@ import Layout from '@/components/layout'
...
@@ -2,6 +2,7 @@ import Layout from '@/components/layout'
// 考证课程
// 考证课程
const
courseRoutes
=
[
const
courseRoutes
=
[
{
path
:
'/course'
,
redirect
:
'/course/learn'
},
// 课程学习列表
// 课程学习列表
{
path
:
'/course/learn'
,
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/learn'
)
},
{
path
:
'/course/learn'
,
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/learn'
)
},
// 课程学习详情
// 课程学习详情
...
@@ -9,6 +10,17 @@ const courseRoutes = [
...
@@ -9,6 +10,17 @@ const courseRoutes = [
path
:
'/course/learn/:id'
,
path
:
'/course/learn/:id'
,
name
:
'courseLearnItem'
,
name
:
'courseLearnItem'
,
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/learn/item'
)
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/learn/item'
)
},
{
path
:
'/course/learn/:courseId/:chapterId'
,
name
:
'courseLearnChapter'
,
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/learn/item'
)
},
// 课程知识点详情
{
path
:
'/course/tag/:id'
,
name
:
'courseTagItem'
,
component
:
()
=>
import
(
/* webpackChunkName: "course-learn" */
'@/pages/course/tag/item'
)
}
}
]
]
...
@@ -175,8 +187,6 @@ export default [
...
@@ -175,8 +187,6 @@ export default [
children
:
[
children
:
[
/* 首页 */
/* 首页 */
{
path
:
'/index'
,
component
:
()
=>
import
(
/* webpackChunkName: "feedback" */
'@/pages/home'
)
},
{
path
:
'/index'
,
component
:
()
=>
import
(
/* webpackChunkName: "feedback" */
'@/pages/home'
)
},
// 搜索
{
path
:
'/search'
,
component
:
()
=>
import
(
/* webpackChunkName: "search" */
'@/pages/search'
)
},
/* 意见反馈 */
/* 意见反馈 */
{
path
:
'/feedback'
,
component
:
()
=>
import
(
/* webpackChunkName: "feedback" */
'@/pages/feedback'
)
},
{
path
:
'/feedback'
,
component
:
()
=>
import
(
/* webpackChunkName: "feedback" */
'@/pages/feedback'
)
},
/* 联系客服 */
/* 联系客服 */
...
@@ -192,6 +202,13 @@ export default [
...
@@ -192,6 +202,13 @@ export default [
...
courseRoutes
...
courseRoutes
]
]
},
},
/* 搜索 */
{
path
:
'/search'
,
component
:
Layout
,
props
:
{
showAside
:
false
},
children
:
[{
path
:
''
,
component
:
()
=>
import
(
/* webpackChunkName: "search" */
'@/pages/search'
)
}]
},
/* 我的 */
/* 我的 */
{
{
path
:
'/my'
,
path
:
'/my'
,
...
...
src/utils/axios.js
浏览文件 @
49505039
...
@@ -27,9 +27,15 @@ httpRequest.interceptors.request.use(
...
@@ -27,9 +27,15 @@ httpRequest.interceptors.request.use(
// 响应拦截
// 响应拦截
httpRequest
.
interceptors
.
response
.
use
(
httpRequest
.
interceptors
.
response
.
use
(
function
(
response
)
{
function
(
response
)
{
return
response
.
data
const
{
data
}
=
response
if
(
data
.
code
)
{
Message
.
error
(
data
.
msg
)
return
Promise
.
reject
(
data
)
}
return
data
},
},
function
(
error
)
{
function
(
error
)
{
console
.
log
(
error
)
if
(
error
.
response
)
{
if
(
error
.
response
)
{
const
{
status
,
message
}
=
error
.
response
.
data
const
{
status
,
message
}
=
error
.
response
.
data
if
(
status
===
400
)
{
if
(
status
===
400
)
{
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论