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

课程考试增加计时功能

上级 08328d13
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -29,6 +29,13 @@ ...@@ -29,6 +29,13 @@
"node": ">=8.9" "node": ">=8.9"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2",
"acorn": "^7.1.1", "acorn": "^7.1.1",
"ali-oss": "^6.11.2", "ali-oss": "^6.11.2",
"autoprefixer": "^9.8.6", "autoprefixer": "^9.8.6",
...@@ -49,27 +56,20 @@ ...@@ -49,27 +56,20 @@
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^6.2.2",
"file-loader": "^6.1.1", "file-loader": "^6.1.1",
"html-replace-webpack-plugin": "^2.5.6",
"html-webpack-plugin": "^4.5.0", "html-webpack-plugin": "^4.5.0",
"mini-css-extract-plugin": "^0.9.0", "mini-css-extract-plugin": "^0.9.0",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"request": "^2.88.2",
"sass-loader": "^10.0.3", "sass-loader": "^10.0.3",
"semver": "^1.1.4",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"url-loader": "^4.1.1", "url-loader": "^4.1.1",
"vconsole-webpack-plugin": "^1.5.2",
"webpack": "^4.44.2", "webpack": "^4.44.2",
"webpack-cli": "^3.3.12", "webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0", "webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2", "webpack-merge": "^4.2.2"
"html-replace-webpack-plugin": "^2.5.6",
"request": "^2.88.2",
"semver": "^1.1.4",
"vconsole-webpack-plugin": "^1.5.2",
"@babel/core": "^7.11.6",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-jsx": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.11.5",
"@babel/preset-env": "^7.11.5",
"@babel/runtime-corejs3": "^7.11.2"
}, },
"dependencies": { "dependencies": {
"@ckeditor/ckeditor5-build-classic": "^24.0.0", "@ckeditor/ckeditor5-build-classic": "^24.0.0",
......
...@@ -42,7 +42,7 @@ ...@@ -42,7 +42,7 @@
<template v-if="isRevised"> <template v-if="isRevised">
<div class="paper-check"> <div class="paper-check">
<p>{{ $t('viewerWork.correctionTime') }}{{ detail.checker_time }}</p> <p>{{ $t('viewerWork.correctionTime') }}{{ detail.checker_time }}</p>
<div class="paper-check-item"> <div class="paper-check-item" v-if="hasScore">
<b>{{ $t('viewerWork.score') }}</b> <b>{{ $t('viewerWork.score') }}</b>
{{ detail.score }} {{ detail.score }}
</div> </div>
...@@ -156,6 +156,10 @@ export default { ...@@ -156,6 +156,10 @@ export default {
const endTime = +new Date(this.deadline) const endTime = +new Date(this.deadline)
const currentTime = new Date().getTime() const currentTime = new Date().getTime()
return currentTime < endTime return currentTime < endTime
},
hasScore() {
// allow_score 1 显示 2隐藏
return this.detail.allow_score !== 2
} }
}, },
methods: { methods: {
......
<template> <template>
<container :title="exam.title" v-loading="!loaded"> <container :title="status.title" v-loading="loading">
<template v-slot:header-aside> <template v-slot:header-aside v-if="isStartExam">
<template v-if="isCompleted"> <template v-if="isCompleted">
{{ $t('viewerWork.fraction') }}{{ exam.score.total }}{{ $t('viewerWork.fractionUnit') }} {{ $t('viewerWork.fraction') }}{{ exam.score.total }}{{ $t('viewerWork.fractionUnit') }}
</template> </template>
<template v-else>{{ $t('viewerWork.examTime') }}{{ status.start_time }} ~ {{ status.terminate_time }}</template> <template v-else>
<div v-if="hasCountdown">倒计时:{{ countdownDurationText }}</div>
<div v-else>{{ $t('viewerWork.examTime') }}{{ status.start_time }} ~ {{ status.terminate_time }}</div>
</template>
</template> </template>
<div class="exam"> <div class="exam">
<template v-if="status.examination_status === '00'"> <template v-if="status.examination_status === '00'">
...@@ -14,8 +17,16 @@ ...@@ -14,8 +17,16 @@
<div class="no-exam">{{ $t('viewerWork.examSubmitedTips') }}</div> <div class="no-exam">{{ $t('viewerWork.examSubmitedTips') }}</div>
</template> </template>
<template v-else> <template v-else>
<!-- 考试期间,未开始考试 -->
<div class="exam-welcome" v-if="!isStartExam">
<div class="inner">
<div>{{ $t('viewerWork.examTime') }}{{ status.start_time }} ~ {{ status.terminate_time }}</div>
<div v-if="hasCountdown">本场考试时间:{{ durationText }}<br />时间用尽自动交卷,请您注意答题时间。</div>
</div>
<el-button type="primary" :disabled="!isExamTime" @click="onStartExam">{{ startExamButtonText }}</el-button>
</div>
<!-- 考试试题 --> <!-- 考试试题 -->
<div class="exam-form" v-if="loaded"> <div class="exam-form" v-else>
<el-form :disabled="!canEditable"> <el-form :disabled="!canEditable">
<template v-for="items in questions"> <template v-for="items in questions">
<exam-item <exam-item
...@@ -61,30 +72,21 @@ import Container from '../common/container.vue' ...@@ -61,30 +72,21 @@ import Container from '../common/container.vue'
import ExamItem from './examItem.vue' import ExamItem from './examItem.vue'
// api // api
import * as api from '../../api' import * as api from '../../api'
import tool from '@/tool/index'
// 章节测试题 // 课后考试
export default { export default {
name: 'CourseExam', name: 'CourseExam',
components: { Container, ExamItem }, components: { Container, ExamItem },
props: { props: {
// 当前选中的章节 // 当前选中的章节
chapter: { chapter: { type: Object, default: () => {} },
type: Object,
default() {
return {}
}
},
// 课程详情接口返回的数据 // 课程详情接口返回的数据
data: { data: { type: Object, default: () => {} }
type: Object,
default() {
return {}
}
}
}, },
data() { data() {
return { return {
loaded: false, loading: false,
detail: {}, detail: {},
status: {}, status: {},
questions: [], questions: [],
...@@ -95,7 +97,9 @@ export default { ...@@ -95,7 +97,9 @@ export default {
submitLoading: false, submitLoading: false,
isMultipleExams: false, // 是否可以多次考试 isMultipleExams: false, // 是否可以多次考试
maxExams: 3, // 最多考试几次 maxExams: 3, // 最多考试几次
examCount: this.data.exist_examination ? this.data.exist_examination.length : 0 // 试卷数量 countdownDuration: 0, // 倒计时剩余时间
countdownTimer: null,
isStartExam: false
} }
}, },
watch: { watch: {
...@@ -104,6 +108,12 @@ export default { ...@@ -104,6 +108,12 @@ export default {
handler() { handler() {
this.init() this.init()
} }
},
examCount: {
immediate: true,
handler(value) {
this.isStartExam = !!value
}
} }
}, },
computed: { computed: {
...@@ -124,6 +134,10 @@ export default { ...@@ -124,6 +134,10 @@ export default {
// 大于开始时间,小于结束时间 // 大于开始时间,小于结束时间
return this.status.examination_status === '20' return this.status.examination_status === '20'
}, },
// 考试按钮
startExamButtonText() {
return this.status.examination_status === '90' ? '考试结束' : '开始考试'
},
// 是否提交 // 是否提交
isSubmited() { isSubmited() {
return this.exam.type === 1 || this.exam.type === 2 return this.exam.type === 1 || this.exam.type === 2
...@@ -156,7 +170,7 @@ export default { ...@@ -156,7 +170,7 @@ export default {
}, },
// 是否显示再考一次 // 是否显示再考一次
hasResubmit() { hasResubmit() {
if (this.examList.length >= this.maxExams) { if (this.examCount >= this.maxExams) {
return false return false
} }
// 判断状态是否还有未提交的试题 // 判断状态是否还有未提交的试题
...@@ -166,11 +180,25 @@ export default { ...@@ -166,11 +180,25 @@ export default {
} }
} }
return true return true
// return this.isSubmited && this.isExamTime && this.examCount < this.maxExams
}, },
// 已存在的试题列表 // 已存在的试题列表
examList() { examList() {
return this.data.exist_examination return this.data.exist_examination
},
// 试卷数量
examCount() {
return this.examList ? this.examList.length : 0
},
// 是否有倒计时
hasCountdown() {
return !!this.status.duration && !this.isSubmited
},
// 倒计时文字
countdownDurationText() {
return tool.convertTime.durationToTimeString(this.countdownDuration)
},
durationText() {
return tool.convertTime.durationToTimeString2(this.status.duration)
} }
}, },
methods: { methods: {
...@@ -180,11 +208,21 @@ export default { ...@@ -180,11 +208,21 @@ export default {
// 自动获取考试状态 // 自动获取考试状态
await this.autoCheckExamStatus() await this.autoCheckExamStatus()
// 获取试题 // 获取试题
this.isStartExam && this.getExam()
},
// 开始考试
onStartExam() {
this.isStartExam = true
this.getExam() this.getExam()
}, },
// 获取考试状态 // 获取考试状态
async getExamStatus() { async getExamStatus() {
await api.getCourseExamStatus(this.sid, this.cid, this.pid).then(response => { await api.getCourseExamStatus(this.sid, this.cid, this.pid).then(response => {
// examination_status
// 00: 考场未开放,不允许进入
// 10:考场开放,允许进入
// 20:开始答题
// 90:考试已结束
this.status = response this.status = response
if (this.isSubmited || response.examination_status === '90') { if (this.isSubmited || response.examination_status === '90') {
this.checkStatusTimer && clearInterval(this.checkStatusTimer) this.checkStatusTimer && clearInterval(this.checkStatusTimer)
...@@ -200,7 +238,7 @@ export default { ...@@ -200,7 +238,7 @@ export default {
}, },
// 获取试题 // 获取试题
getExam() { getExam() {
this.loaded = false this.loading = true
api api
.getCourseExamResult(this.sid, this.cid, this.pid, { offset: this.offset }) .getCourseExamResult(this.sid, this.cid, this.pid, { offset: this.offset })
.then(response => { .then(response => {
...@@ -210,9 +248,13 @@ export default { ...@@ -210,9 +248,13 @@ export default {
this.canEditable && this.autoSubmit() this.canEditable && this.autoSubmit()
// 更新菜单 // 更新菜单
this.isMultipleExams && this.$emit('update') this.isMultipleExams && this.$emit('update')
// 倒计时剩余时间
this.countdownDuration = this.status.duration - this.exam.duration
// 开始倒计时
this.hasCountdown && this.setCountdown()
}) })
.finally(() => { .finally(() => {
this.loaded = true this.loading = false
}) })
}, },
// 组装问题数据 // 组装问题数据
...@@ -340,7 +382,9 @@ export default { ...@@ -340,7 +382,9 @@ export default {
} }
}) })
.catch(error => { .catch(error => {
this.$message.error(error.message) params.type && this.$message.error(error.message)
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.getExam()
}) })
.finally(() => { .finally(() => {
this.submitLoading = false this.submitLoading = false
...@@ -357,12 +401,23 @@ export default { ...@@ -357,12 +401,23 @@ export default {
}, },
handleNewExam() { handleNewExam() {
this.$router.push({ query: { offset: this.examCount } }) this.$router.push({ query: { offset: this.examCount } })
this.examCount++
}, },
// 清除定时器 // 清除定时器
clearTimer() { clearTimer() {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer) this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.checkStatusTimer && clearInterval(this.checkStatusTimer) this.checkStatusTimer && clearInterval(this.checkStatusTimer)
this.countdownTimer && clearInterval(this.countdownTimer)
},
// 设置倒计时
setCountdown() {
this.countdownTimer && clearInterval(this.countdownTimer)
this.countdownTimer = setInterval(() => {
this.countdownDuration--
// if (this.countdownDuration <= 0) {
// this.getExam()
// this.clearTimer()
// }
}, 1000)
} }
}, },
beforeMount() { beforeMount() {
...@@ -392,9 +447,10 @@ export default { ...@@ -392,9 +447,10 @@ export default {
text-align: center; text-align: center;
} }
.exam-welcome { .exam-welcome {
padding: 40px; display: flex;
flex-direction: column;
align-items: center;
line-height: 30px; line-height: 30px;
text-align: center;
::v-deep .el-button { ::v-deep .el-button {
margin-top: 30px; margin-top: 30px;
} }
......
...@@ -28,7 +28,7 @@ export default { ...@@ -28,7 +28,7 @@ export default {
fraction: '分数', fraction: '分数',
fractionUnit: '分', fractionUnit: '分',
examEmpty: '暂无考试', examEmpty: '暂无考试',
examTime: '考试时间', examTime: '考试有效时间',
examDeadline: '考试截止时间', examDeadline: '考试截止时间',
examSubmitedTips: '试卷批改中,请耐心等待', examSubmitedTips: '试卷批改中,请耐心等待',
examSubmitButtonTips: '提交之后就不能修改了哦', examSubmitButtonTips: '提交之后就不能修改了哦',
......
...@@ -3,15 +3,32 @@ export default class ConvertTime { ...@@ -3,15 +3,32 @@ export default class ConvertTime {
* 工具方法 - 播放时间 转化 h:m:s * 工具方法 - 播放时间 转化 h:m:s
* @param {[string]} duration 时间戳 * @param {[string]} duration 时间戳
*/ */
durationToTimeString (duration) { durationToTimeString(duration) {
const h = Math.floor(duration / 3600) const h = Math.floor(duration / 3600)
const m = Math.floor((duration - h * 3600) / 60) const m = Math.floor((duration - h * 3600) / 60)
const s = (duration - h * 3600 - m * 60) % 60 const s = (duration - h * 3600 - m * 60) % 60
function tenify (a) { function tenify(a) {
return a >= 10 ? a : '0' + a return a >= 10 ? a : '0' + a
} }
const to = { h: tenify(h), m: tenify(m), s: tenify(s) } const to = { h: tenify(h), m: tenify(m), s: tenify(s) }
const format = 'h:m:s' const format = 'h:m:s'
return format.replace(/h|m|s/g, k => to[k]).replace(/^00:/, '') return format.replace(/h|m|s/g, k => to[k]).replace(/^00:/, '')
} }
durationToTimeString2(duration) {
const h = Math.floor(duration / 3600)
const m = Math.floor((duration - h * 3600) / 60)
const s = (duration - h * 3600 - m * 60) % 60
let output = ''
if (h) {
output += `${h}小时`
}
if (m) {
output += `${m}分钟`
}
if (s) {
output += `${s}秒`
}
return output
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论