Преглед изворни кода

Merge branch 'persist_answers' into dev

# Conflicts:
#	src/views/exam/components/CodePane.vue
ChengYuXuan пре 5 година
родитељ
комит
650d184f3f
4 измењених фајлова са 90 додато и 56 уклоњено
  1. 2 2
      src/api/record.ts
  2. 12 4
      src/store/index.ts
  3. 76 42
      src/views/exam/ExamPane.vue
  4. 0 8
      src/views/exam/components/CodePane.vue

+ 2 - 2
src/api/record.ts

@@ -3,7 +3,7 @@ import { RECORD_MODULE } from '@/api/prefix'
 import { ExamQuestionSubmitRecord, ID, ReceivedData } from '@/api/types'
 
 /**
- * 获得某一考试某一题目的提交记录,暂时没有使用
+ * 获得某一考试某一题目的提交记录,目前用于代码题的提交记录,客观题均只提交一次
  * @param examId
  * @param questionId
  */
@@ -13,7 +13,7 @@ export const getExamQuestionSubmitRecord = (examId: ID, questionId: ID) => {
   )
 }
 /**
- * 获得考试所有题目的提交记录,目前用来判断是否参加过考试
+ * 获得考试所有题目的提交记录
  * @param examId
  */
 export const getExamSubmitRecords = (examId: ID) => {

+ 12 - 4
src/store/index.ts

@@ -1,6 +1,6 @@
 import Vue from 'vue'
 import Vuex, { GetterTree, MutationTree } from 'vuex'
-import { UserSerializer } from '@/api/types'
+import { CodeTuple, ID, Record, UserSerializer } from '@/api/types'
 import createPersistedState from 'vuex-persistedstate'
 
 Vue.use(Vuex)
@@ -8,23 +8,31 @@ Vue.use(Vuex)
 export type RootState = {
   user: UserSerializer
   token?: string
+  examAnswers?: Object
+  examId?: ID
 }
 
 export const state = (): RootState => ({
   user: (null as unknown) as UserSerializer,
-  token: ''
+  token: '',
+  examAnswers: (null as unknown) as Object,
+  examId: (null as unknown) as ID
 })
 
 export const mutations: MutationTree<RootState> = {
   setToken: (state, token) => (state.token = token),
-  setUser: (state, user) => (state.user = user)
+  setUser: (state, user) => (state.user = user),
+  setExamAnswers: (state, answers) => (state.examAnswers = answers),
+  setExamId: (state, examId) => (state.examId = examId)
 }
 
 export const getters: GetterTree<RootState, RootState> = {
   isStudent: (state) => state.user.role === 'STUDENT',
   isTeacher: (state) => state.user.role === 'TEACHER',
   getId: (state) => state.user.id,
-  isLogin: (state) => Boolean(state.token)
+  isLogin: (state) => Boolean(state.token),
+  getExamAnswers: (state) => state.examAnswers,
+  getExamId: (state) => state.examId
 }
 
 export default new Vuex.Store({

+ 76 - 42
src/views/exam/ExamPane.vue

@@ -207,13 +207,19 @@
     <v-snackbar v-model="submitting" absolute centered color="info">
       <span v-if="examTimesUp">评测结束</span> 提交试卷中...
     </v-snackbar>
-    <v-snackbar v-model="examAvailableDialog" centered absolute color="error">
+    <v-snackbar v-model="examNotAvailable" centered absolute color="error">
       评测不可用
     </v-snackbar>
+    <v-snackbar v-model="reenter" centered absolute color="error">
+      已经提交试卷,考试结束
+    </v-snackbar>
     <v-snackbar v-model="submittedError" absolute>
       提交失败,请重试
     </v-snackbar>
     <v-snackbar v-model="endTimeChanged" absolute>评测已延长</v-snackbar>
+    <v-snackbar v-model="resetLast" absolute color="info" top
+      >已恢复到上次答题状态</v-snackbar
+    >
   </div>
 </template>
 
@@ -224,20 +230,22 @@ import ChoicePane from '@/views/exam/components/ChoicePane.vue'
 import CountDown from '@/views/exam/components/CountDown.vue'
 import BlankPane from '@/views/exam/components/BlankPane.vue'
 import {
+  CodeQuestionSerializer,
   ExamQuestionSerializer,
   ExamSerializer,
-  CodeQuestionSerializer,
+  ID,
+  LocalDateTime,
   QuestionSerializer,
   QuestionType,
-  Record,
-  LocalDateTime,
-  ID
+  Record
 } from '@/api/types'
-import { getExamById, getExamQuestions, getExamCodeQuestions } from '@/api/exam'
+import { getExamById, getExamCodeQuestions, getExamQuestions } from '@/api/exam'
 import { getQuestionStem } from '@/api/question'
 import { objectiveJudge } from '@/api/judge'
 import dayjs from 'dayjs'
 import { TIME_FORMAT } from '@/utils/common'
+import { mapGetters, mapMutations } from 'vuex'
+import { getExamSubmitRecords } from '@/api/record'
 
 export default Vue.extend({
   name: 'ExamPane',
@@ -260,11 +268,11 @@ export default Vue.extend({
       currentQuestion: (null as unknown) as QuestionSerializer,
       currentQuestionIndex: -1,
       objectiveAnswers: {} as Map<ID, Record>,
-      questionIds: [] as Array<string>,
+      questionIds: [] as Array<ID>,
       questionStartTime: dayjs().format(TIME_FORMAT) as LocalDateTime,
       examId: 0 as ID,
       codeQuestions: [] as Array<CodeQuestionSerializer>,
-      examAvailableDialog: false,
+      examNotAvailable: false,
       submitting: false,
       finished: false,
       answered: false,
@@ -274,7 +282,9 @@ export default Vue.extend({
       objectiveQuestionNum: 0,
       codeQuestionNum: 0,
       codeQuestionsReady: false,
-      endTimeChanged: false
+      endTimeChanged: false,
+      resetLast: false,
+      reenter: false
     }
   },
   computed: {
@@ -286,6 +296,8 @@ export default Vue.extend({
     }
   },
   methods: {
+    ...mapGetters(['getExamAnswers', 'getExamId']),
+    ...mapMutations(['setExamAnswers', 'setExamId']),
     questionType(): QuestionType {
       return this.currentQuestion.type
     },
@@ -304,41 +316,35 @@ export default Vue.extend({
       this.examTimesUp = true
       this.submitAnswer()
     },
-    // saveSubmittedCode(code: string, lang: string) {
-    //   const changedCodeQuestion = this.codeQuestions[this.codeIndex]
-    //   const changedCodeTuple =
-    //     changedCodeQuestion.lastCommitCodes?.find((val) => {
-    //       return val.lang === lang
-    //     }) ?? {}
-    //   changedCodeTuple.code = code
-    // },
     saveObjectiveAnswer(answer: string) {
       let questionEndTime = dayjs().format(TIME_FORMAT)
+
       this.objectiveAnswers.set(this.questionIds[this.currentQuestionIndex], {
         answer,
         startAt: this.questionStartTime,
         endAt: questionEndTime
       })
+
+      this.setExamAnswers(Object.fromEntries(this.objectiveAnswers))
+    },
+    // 清空本地存储
+    clearLocalStorage() {
+      this.setExamId('')
+      this.setExamAnswers({})
     },
     leaveExam() {
+      // 离开考试暂时不清除本地存储
       this.exitDialog = false
-      this.$router.push('/')
+      this.$router.push({ name: 'Home' })
     },
     submitAnswer() {
       this.submitDialog = false
       this.submitting = true
-      console.log(this.objectiveAnswers)
-      console.log(this.questionIds)
-
-      let mapObj = Object.create(null)
-      for (let [k, v] of this.objectiveAnswers) {
-        if (v.answer) {
-          mapObj[k] = v
-        }
-      }
 
-      objectiveJudge(this.examId, mapObj)
+      objectiveJudge(this.examId, Object.fromEntries(this.objectiveAnswers))
         .then(() => {
+          // 交卷后清除本地存储
+          this.clearLocalStorage()
           setTimeout(() => {
             this.$router.push({
               name: 'result',
@@ -369,13 +375,7 @@ export default Vue.extend({
       }
 
       if (this.currentQuestionIndex < this.totalQuestionNum - 1) {
-        if (
-          !this.objectiveAnswers.get(
-            this.questionIds[this.currentQuestionIndex]
-          )
-        ) {
-          //TODO 是否需要提示未作答?
-        }
+        //TODO 是否需要提示未作答?
         this.questionStartTime = dayjs().format(TIME_FORMAT)
 
         this.currentQuestionIndex++
@@ -388,16 +388,26 @@ export default Vue.extend({
     fetchData() {
       this.examId = this.$route.params.examId
 
+      // 提交过客观题,说明已经交卷
+      getExamSubmitRecords(this.examId).then(({ data }) => {
+        if (data.result.find((val) => !val.message)) {
+          this.reenter = true
+          setTimeout(() => {
+            this.leaveExam()
+          }, 1000)
+        }
+      })
+
       getExamById(this.examId).then(({ data }) => {
         this.examInfo = data.result
         this.endTime = dayjs(this.examInfo.endTime, TIME_FORMAT).valueOf()
         this.currentTime = dayjs().valueOf()
         const startTime = dayjs(this.examInfo.startTime, TIME_FORMAT).valueOf()
-
+        // 检查时间
         if (this.currentTime >= this.endTime || this.currentTime < startTime) {
-          this.examAvailableDialog = true
+          this.examNotAvailable = true
           setTimeout(() => {
-            this.$router.push({ name: 'Home' })
+            this.leaveExam()
           }, 1000)
         }
         getExamQuestions(this.examId).then(({ data }) => {
@@ -424,12 +434,36 @@ export default Vue.extend({
               })
               this.codeQuestionsReady = true
 
-              this.currentQuestionIndex = 0
-              this.fetchQuestionStem(0)
+              if (this.getExamId() != this.examId) {
+                // 新参加的考试,将 vuex 状态清空,不存在同时参加多场考试的情况
+                this.clearLocalStorage()
+                this.setExamId(this.examId)
+                this.objectiveAnswers = new Map()
+                this.currentQuestionIndex = 0
+                this.fetchQuestionStem(0)
+              } else {
+                // 中途重进考试,可以基于本地存储恢复现场
+                this.objectiveAnswers = new Map(
+                  Object.entries(this.getExamAnswers())
+                )
+
+                getExamSubmitRecords(this.examId).then(({ data }) => {
+                  if (data.result.find((val) => val.message)) {
+                    // 提交过代码题,表示已经做完客观题
+                    this.currentQuestionIndex = this.codeQuestionStartIndex
+                  } else {
+                    if (this.objectiveAnswers.size === 0) {
+                      this.currentQuestionIndex = 0
+                    } else {
+                      this.currentQuestionIndex = this.objectiveAnswers.size - 1
+                    }
+                    this.fetchQuestionStem(this.currentQuestionIndex)
+                  }
+                  this.resetLast = true
+                })
+              }
             })
           })
-
-          this.objectiveAnswers = new Map<ID, Record>()
         })
       })
     }

+ 0 - 8
src/views/exam/components/CodePane.vue

@@ -332,14 +332,6 @@ export default Vue.extend({
       this.currentCode = this.rollbackAnswer
       this.rollbackCodeDialog = false
     },
-    resetOriginCode() {
-      // const originCodes = this.question.codeTuples ?? []
-      // const originTuple =
-      //   originCodes.find((val) => {
-      //     return val.lang === this.currentLang
-      //   }) ?? {}
-      // this.currentCode = originTuple.code ?? ''
-    },
     prevQuestion() {
       this.$emit('prevQuestion')
     },