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

feat: 章节考试支持多次考试

上级 fb4b247e
...@@ -196,3 +196,27 @@ export function addChapterHomeworkComment(data) { ...@@ -196,3 +196,27 @@ export function addChapterHomeworkComment(data) {
export function getChapterHomeworkComment(params) { export function getChapterHomeworkComment(params) {
return httpRequest.get('/api/lms/v2/education/homeworks/fmcomment', params) return httpRequest.get('/api/lms/v2/education/homeworks/fmcomment', params)
} }
/**
* 获取章节多次考试信息
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} chapterId 章节ID
*/
export function getChapterMultipleExams(semesterId, courseId, chapterId, params) {
return httpRequest.get(`/api/lms/v3/education/chapter-examination/${semesterId}/${courseId}/${chapterId}`, params)
}
/**
* 提交章节多次考试信息
* @param {string} semesterId 学期ID
* @param {string} courseId 课程ID
* @param {string} chapterId 章节ID
* @param {string} examId 试题ID
*/
export function submitChapterMultipleExams(semesterId, courseId, chapterId, examId, params) {
return httpRequest.post(
`/api/lms/v3/education/chapter-examination/${semesterId}/${courseId}/${chapterId}/${examId}`,
params
)
}
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
import ChapterPlayer from './player/chapterPlayer.vue' // 章节视频 import ChapterPlayer from './player/chapterPlayer.vue' // 章节视频
import ChapterWork from './work/index.vue' // 章节作业 import ChapterWork from './work/index.vue' // 章节作业
import ChapterExam from './work/chapterExam.vue' // 章节考试 import ChapterExam from './work/chapterExam.vue' // 章节考试
import ChapterMultipleExam from './work/ChapterMultipleExam.vue' // 章节多次考试
import ChapterRead from './read/chapterRead.vue' // 章节资料 import ChapterRead from './read/chapterRead.vue' // 章节资料
import ChapterLive from './live/chapterLive.vue' // 章节直播 import ChapterLive from './live/chapterLive.vue' // 章节直播
import CourseWork from './work/courseWork.vue' // 课程大作业 import CourseWork from './work/courseWork.vue' // 课程大作业
...@@ -20,6 +21,7 @@ export default { ...@@ -20,6 +21,7 @@ export default {
ChapterWork, ChapterWork,
ChapterRead, ChapterRead,
ChapterExam, ChapterExam,
ChapterMultipleExam,
ChapterLive, ChapterLive,
CourseWork, CourseWork,
CourseRead, CourseRead,
...@@ -42,6 +44,7 @@ export default { ...@@ -42,6 +44,7 @@ export default {
5: 'ChapterLive', // CC直播 5: 'ChapterLive', // CC直播
8: 'ChapterLive', // CC直播 8: 'ChapterLive', // CC直播
9: 'ChapterExam', // 考试 9: 'ChapterExam', // 考试
10: 'ChapterMultipleExam', // 多次考试
99: 'CourseWork', // 课程大作业 99: 'CourseWork', // 课程大作业
100: 'CourseRead', // 课程资料 100: 'CourseRead', // 课程资料
101: 'CourseExam' // 课程考试 101: 'CourseExam' // 课程考试
......
<template>
<div class="multiple-exam">
<div class="multiple-exam-header" v-if="exam.can_more">
<div class="multiple-exam-header__history" v-if="historyScoreText">历史分数:{{ historyScoreText }}</div>
<el-button type="primary" plain :disabled="!exam.can_next" @click="onNextExam">再次考试</el-button>
<el-button plain :disabled="!maxScoreExamId" @click="onViewMaxScoreExam">查看历史最高分数试卷</el-button>
</div>
<container :title="exam.title" v-loading="loading">
<template v-slot:header-aside v-if="isExamComplete">
{{ $t('viewerWork.fraction') }}{{ exam.score.total }}{{ $t('viewerWork.fractionUnit') }}
</template>
<div class="exam">
<!-- 批改中 -->
<div class="no-exam" v-if="isSubmited && !isExamComplete">{{ $t('viewerWork.examSubmitedTips') }}</div>
<!-- 考试试题 -->
<div class="exam-form" v-else>
<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="$t('viewerWork.examSubmitButtonTips')" placement="right">
<el-button type="primary" :loading="submitLoading" @click="onSubmit">{{ submitText }}</el-button>
</el-tooltip>
</div>
</el-form>
</div>
</div>
</container>
</div>
</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: 'ChapterMultipleExam',
components: { Container, ExamItem },
props: {
// 当前选中的章节
chapter: { type: Object, default: () => ({}) },
// 课程详情接口返回的数据
data: { type: Object, default: () => ({}) }
},
data() {
return {
loading: false,
exam: {},
questions: [],
autoSubmitTimer: null, // 自动提交定时器
submitLoading: false,
messageInstance: null
}
},
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.exam.dead_line) return true
// 大于开始时间,小于结束时间
const endTime = +new Date(this.exam.dead_line)
const currentTime = new Date().getTime()
return currentTime < endTime
},
// 考试按钮
startExamButtonText() {
return this.isExamTime ? this.$t('viewerWork.examStartButtonText') : this.$t('viewerWork.examEndButtonText')
},
// 考试完成
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 ? this.$t('viewerWork.submitedText') : this.$t('viewerWork.submitText')
},
// 历史分数
historyScoreText() {
if (!this.exam.history) return
return this.exam.history.map(item => item.score).join('、')
},
// 最高分试卷ID
maxScoreExamId() {
if (!this.exam.history) return
let maxScore = 0
let maxItem = null
this.exam.history.forEach(item => {
if (item.score >= maxScore) {
maxScore = item.score
maxItem = item
}
})
return maxItem ? maxItem.sheet_id : ''
}
},
methods: {
// 组装问题数据
genQuestions(list) {
if (!list) return []
return list.map(data => {
let { radioList = [], checkboxList = [], shortAnswerList = [], judgeList = [] } = 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)
})
// 判断题
judgeList = judgeList.map(item => {
const temp = { type: 4, formModel: { id: item.id, user_answer: item.user_answer || '' } }
return Object.assign({}, item, temp)
})
return [...radioList, ...checkboxList, ...judgeList, ...shortAnswerList]
})
},
// 获取考试结果
getExamResult(params = {}) {
this.loading = true
api
.getChapterMultipleExams(this.sid, this.cid, this.pid, params)
.then(response => {
// 设置问题列表数据
if (response.code !== 8001) {
this.exam = response
this.questions = this.genQuestions(response.sheet)
// 自动提交
!this.isSubmited && this.autoSubmit()
}
})
.finally(() => {
this.loading = false
})
},
// 再考一次
onNextExam() {
this.getExamResult({ is_new: 1 })
},
// 查看最高分试卷
onViewMaxScoreExam() {
this.getExamResult({ sheet_id: this.maxScoreExamId })
},
// 提交校验
checkSubmit() {
if (!this.questions.length) {
return
}
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(this.$t('viewerWork.examSubmitError'))
return
}
// 提交的答案数据
const answers = this.handleSubmitData()
// 提交参数
const params = { answers: JSON.stringify(answers), type: 1 }
// 请求接口
this.submitLoading = true
this.handleSubmitRequest(params)
},
// 自动提交,10秒提交一次
autoSubmit() {
this.autoSubmitTimer && clearInterval(this.autoSubmitTimer)
this.autoSubmitTimer = setInterval(() => {
// 提交的答案数据
const answers = this.handleSubmitData()
const params = { answers: JSON.stringify(answers), type: 0 }
// 请求接口
this.handleSubmitRequest(params)
}, 10000)
},
// 处理请求接口答案数据
handleSubmitData() {
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)
}
// 判断题
if (item.type === 4) {
result.judgeList.push(item.formModel)
}
return result
},
{ radioList: [], checkboxList: [], shortAnswerList: [], judgeList: [] }
)
})
},
// 请求提交接口
handleSubmitRequest(params) {
params.paper_type = 0
api
.submitChapterMultipleExams(this.sid, this.cid, this.pid, this.exam.id, params)
.then(response => {
if (params.type === 0) {
console.log('暂存成功')
return
}
if (response.code === 200) {
this.$message.success(this.$t('viewerWork.examSubmitSuccess'))
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>
.multiple-exam {
max-width: 900px;
margin: 0 auto;
}
.multiple-exam-header {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 40px 0;
padding: 40px;
background-color: #f2f2f2;
}
.multiple-exam-header__history {
flex: 1;
}
.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>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论