import { commitExamineePaper, commitExamineeQuestion, loadExamineePaper, openExamineePaper, scoreExamineeQuestion, scoreFinish, teacherScoreExamineeQuestions } from '@/api/webApi/studentEvaluating.js' import config from '@/common/mx-config.js' import consts from '@/common/mx-const' import { mapGetters } from 'vuex' import { formatDuration } from '@/utils/index' import eventMixin from './mx-question-event-mixin' import EventBus from '@/components/EventBus' export default { mixins: [eventMixin], props: { options: { type: Object, default: _ => ({ paper: null, navigatorSpan: 6, extData: {}, currentTab: 0, questionOptionsCache: {}, committingQuestions: [], loading: false, committing: false, initWithAnswerOfPrevQuestion: false, // extra customScoredAction: false, customCommittedAction: false }) } }, computed: { ...mapGetters(['isFrontTeacher', 'isFrontStudent', 'currentUser', 'firstClassName']), // properties paper() { return this.options.paper }, extData() { return this.options.extData }, questionOptionsCache() { return this.options.questionOptionsCache }, allowAnswer() { return this.paper.allowAnswer }, allowScore() { return this.paper.allowScore }, examineeId() { return this.extData.examineeId || this.paper.examineeId }, examineeType() { return this.extData.examineeType || this.paper?.examineeType || consts.enum.paper.examineeType.evaluation }, evaluationClassId() { return this.extData.evaluationClassId }, studentName() { return this.extData.studentName || this.currentUser.nickName }, studentClassName() { return this.extData.class || this.firstClassName }, avatar() { return this.extData.avatar || config.avatar_default }, loading() { return this.options.loading }, committing() { return this.options.committing }, currentTab() { return this.options.currentTab }, committingQuestions() { return this.options.committingQuestions }, // business data countdownDisplay() { return formatDuration(this.paper.remaining) }, stateStr() { if (this.paper.allowAnswer || this.paper.allowScore) return '' return this.paper.stateStr }, scoreColor() { return this.paper.allowAnswer ? '' : config.color.error }, currentQuestion() { if (this.paper?.questions?.length > this.currentTab) { return this.paper.questions[this.currentTab] } return null }, currentQuestionOptions() { if (!this.currentQuestion) return null return this.questionOptionsCache[this.currentTab] }, hasPrevious() { return this.currentTab > 0 }, hasNext() { return this.currentTab < this.paper.questions?.length - 1 }, queryApi() { return this.isFrontStudent ? openExamineePaper : loadExamineePaper }, scoreApi() { return this.isFrontStudent ? scoreExamineeQuestion : teacherScoreExamineeQuestions } }, methods: { // for shortcut forceBlur() { // Give the document focus if (document.activeElement) { document.activeElement.blur() } this.$el.focus() }, loadPaper(extData, overrideOptions) { // set ext this.options.extData = extData if (overrideOptions) { Object.keys(overrideOptions).forEach(key => this.options[key] = overrideOptions[key]) } // query let queryApi = this.queryApi this.options.loading = true queryApi(extData).then(res => { this.options.currentTab = 0 // reset position const paper = res.data const autoScoreQuestions = this.eventProcessPaper(paper, this.questionOptionsCache) this.options.paper = paper this.autoScoreObjectives(autoScoreQuestions) }).finally(() => { return this.options.loading = false }) }, beforeQuestionChangeAction() { this.triggerAutoCommitQuestion() }, goIndexAction(index) { if (this.currentTab != index) { this.triggerAutoCommitQuestion() this.options.currentTab = index } }, goPreviousAction() { this.triggerAutoCommitQuestion() this.options.currentTab = this.currentTab - 1 }, goNextAction() { this.triggerAutoCommitQuestion() this.options.currentTab = this.currentTab + 1 }, goUnfinishedQuestionAction(index) { this.options.currentTab = index }, commitQuestionAction(commit) { // 部分提交,断点续传 this.committingQuestions.push(commit) this.commitQuestionCore() }, autoScoreObjectives(scoredQuestions) { if (!scoredQuestions?.length) return // 静默提交 const func = this.scoreApi func({ examineeId: this.examineeId, examineeType: this.examineeType, questions: scoredQuestions }).then(res => { // donothing }) }, paperTimeUpAction() { this.$once('singleCommitCompleted', _ => this.commitPaper()) this.triggerAutoCommitQuestion() }, paperSubmitAction() { this.$once('singleCommitCompleted', _ => { const unfinishedList = this.eventUnfinishedQuestions(this.paper) if (unfinishedList.length) { const msg = `检测到您还有第(${unfinishedList.map(q => q.seq)})题没有完成,是否做完再交卷?!` this.$confirm(msg, '提示', { distinguishCancelAndClose: true, confirmButtonText: '继续做题', cancelButtonText: '确认交卷' }).then(() => { this.goUnfinishedQuestionAction(unfinishedList.first().seq - 1) }).catch(action => { if (action == 'cancel') { this.commitPaper() } }) } else { const msg = '检测到您已完成答题' this.$confirm(msg, '提示', { confirmButtonText: '确认提交', cancelButtonText: '再检查一遍' }).then(() => this.commitPaper()) } }) this.triggerAutoCommitQuestion() }, triggerAutoCommitQuestion() { if (!this.paper.allowAnswer) return const commit = this.eventBuildCommitAnswerData(this.currentQuestion) this.commitQuestionAction(commit) }, commitQuestionCore() { if (this.committingQuestions.length == 0) { this.$emit('singleCommitCompleted') return } const target = this.committingQuestions[0] const commit = { examineeId: this.examineeId, examineeType: this.examineeType, questions: [{ ...target, examineeType: this.examineeType }] } // 对比历史作答与现在提交内容是否有差异,防止切换时重复计时 const rawQuestion = this.paper.questions.find(q => q.questionId == target.questionId) if (target.answer == rawQuestion.answer && target.attachments?.toString() == rawQuestion.attachments?.toString()) { // 答题没有变化,不需要重复提交 this.committingQuestions.shift() setTimeout(_ => this.commitQuestionCore(), 200) return } commitExamineeQuestion(commit) .then(res => { // 成功后可用提交属性覆盖题目属性 this.eventHandleAnswerSubmitted(target, rawQuestion) this.committingQuestions.shift() setTimeout(_ => this.commitQuestionCore(), 200) }).catch(e => { // 失败后可尝试重复提交 // TODO: hht 2022.1.2 这里如果一直异常了会陷入死循环 // setTimeout(_ => this.commitQuestionCore(), 200) }) }, commitPaper() { if (this.committing) return this.options.committing = true const commit = { examineeId: this.examineeId, examineeType: this.examineeType, questions: this.paper.questions.map(q => ({ questionId: q.questionId, answer: q.answer, attachments: q.attachments, duration: q.duration, examineeType: this.examineeType })) } commitExamineePaper(commit).then(res => { this.$alert('交卷成功').then(() => this.customCommitCompleted()) }).finally(() => this.options.committing = false) }, async scoreFinishAction() { const unScoredQuestions = this.eventUnScoredQuestions(this.paper) if (unScoredQuestions.length > 0) { const msg = `第${unScoredQuestions.map(q => q.seq)}题未完成评分` this.msgError(msg) this.goUnfinishedQuestionAction(unScoredQuestions.first().seq - 1) return } await this.$confirm('确认提交?') if (this.committing) return this.options.committing = true scoreFinish({ examineeId: this.examineeId, examineeType: this.examineeType }).then(res => { this.$alert('阅卷完毕').then(() => this.customScoreCompleted()) }).finally(() => this.options.committing = false) }, customCommitCompleted() { if (this.options.customCommittedAction) { // NOTE: 用eventBus解耦组件层级限制 // TODO: 这里的if-else分支不是唯一做法,也可以重写包含提交逻辑的组件,用slot装载,不知道哪种方式会更好 this.$nextTick(() => EventBus.instance.$emit('customCommittedAction')) } else { this.$router.back() } }, customScoreCompleted() { if (this.options.customScoredAction) { // NOTE: 用eventBus解耦组件层级限制 // TODO: 这里的if-else分支不是唯一做法,也可以重写包含提交逻辑的组件,用slot装载,不知道哪种方式会更好 this.$nextTick(() => EventBus.instance.$emit('customScoredAction')) } else { this.$router.back() } } } }