Procházet zdrojové kódy

完成答题逻辑

ChengYuXuan před 5 roky
rodič
revize
629c7af73a

+ 2 - 1
src/api/types.d.ts

@@ -83,6 +83,7 @@ export interface OptionSerializer {
 export interface QuestionSerializer {
   id?: number
   stem?: string
+  index?: number
 }
 
 export type ChoiceAnswer = string
@@ -102,7 +103,7 @@ export interface CodeTuple {
 export interface CodeQuestionSerializer extends QuestionSerializer {
   studentId?: number
   codeTuples?: Array<CodeTuple>
-  lastCommitCode?: Array<CodeTuple>
+  lastCommitCodes?: Array<CodeTuple>
 }
 
 export interface ExamQuestionSerializer {

+ 9 - 9
src/mock/serializers.js

@@ -53,19 +53,19 @@ const serializersPlugin = (mock) => ({
       options: [
         {
           label: 'A',
-          content: 'a'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'B',
-          content: 'ab'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'C',
-          content: 'abc'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'D',
-          content: 'abcd'
+          content: '@CPARAGRAPH'
         }
       ]
     }),
@@ -78,19 +78,19 @@ const serializersPlugin = (mock) => ({
       options: [
         {
           label: 'A',
-          content: '1'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'B',
-          content: '12'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'C',
-          content: '123'
+          content: '@CPARAGRAPH'
         },
         {
           label: 'D',
-          content: '1234'
+          content: '@CPARAGRAPH'
         }
       ]
     }),
@@ -112,7 +112,7 @@ const serializersPlugin = (mock) => ({
           code: "if __name__ == '__main__':"
         }
       ],
-      lastCommitCode: [
+      lastCommitCodes: [
         {
           lang: 'cpp',
           code: 'int main(){}'

+ 121 - 56
src/views/exam/ExamPane.vue

@@ -3,7 +3,7 @@
     <v-app-bar dense app class="indigo lighten-5">
       <v-row>
         <v-col>
-          <span class="display-1">基础能力测试</span>
+          <span class="display-1">{{ examInfo.title }}</span>
         </v-col>
         <v-col class="d-inline-flex justify-end">
           <count-down
@@ -30,7 +30,7 @@
           class="overflow-auto flex-grow-1 pa-4 d-flex flex-column justify-space-around"
           style="box-sizing: border-box;"
         >
-          <div column class="justify-space-between">
+          <div class="justify-space-between">
             <div class="d-flex justify-center">
               <h2>单选题</h2>
             </div>
@@ -39,14 +39,14 @@
               :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
-              :disabled="i.answered"
+              :disabled="currentQuestionIndex + 1 !== i.index && i.answered"
               :color="currentQuestionIndex + 1 === i.index ? 'secondary' : ''"
               @click="onSelectQuestion(i.index)"
             >
               {{ i.index }}
             </v-btn>
           </div>
-          <div column class="justify-space-between d-inline">
+          <div class="justify-space-between d-inline">
             <div class="d-flex justify-center">
               <h2>多选题</h2>
             </div>
@@ -58,14 +58,14 @@
               :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
-              :disabled="i.answered"
+              :disabled="currentQuestionIndex + 1 !== i.index && i.answered"
               :color="currentQuestionIndex + 1 === i.index ? 'secondary' : ''"
               @click="onSelectQuestion(i.index)"
             >
               {{ i.index }}
             </v-btn>
           </div>
-          <div column class="justify-space-between">
+          <div class="justify-space-between">
             <div class="d-flex justify-center">
               <h2>代码题</h2>
             </div>
@@ -74,7 +74,6 @@
               :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
-              :disabled="i.answered"
               :color="currentQuestionIndex + 1 === i.index ? 'secondary' : ''"
               @click="onSelectQuestion(i.index)"
             >
@@ -140,18 +139,33 @@
     <v-main class="indigo lighten-5 pa-0">
       <v-container fluid class="pa-2" v-if="currentQuestionIndex >= 0">
         <choice-pane
+          ref="choicePane"
           v-if="isChoiceProblem"
-          v-on:nextQuestion="nextQuestion"
+          @nextQuestion="nextQuestion"
+          @saveChoiceAnswer="saveChoiceAnswer"
           :total-question-num="curTypeQuestionNum"
-          :question="curChoice"
+          :question="curChoiceQuestion"
         ></choice-pane>
-        <code-pane
-          v-else
-          v-on:nextQuestion="nextQuestion"
-          :question="curCodeQuestion"
-          :total-question-num="curTypeQuestionNum"
-        ></code-pane>
+        <div v-else>
+          <!--          用一个不显示的tabs,同时渲染所有的代码题面板,控制代码题的切换,使得代码切换保留已写代码-->
+          <v-tabs v-show="false" v-model="codeIndex">
+            <v-tab v-for="i in codeQuestionNum" :key="i"> {{ i }}</v-tab>
+          </v-tabs>
+          <v-tabs-items v-model="codeIndex">
+            <v-tab-item v-for="i in codeQuestionNum" :key="i">
+              <code-pane
+                :question="codeQuestions[i - 1]"
+                :total-question-num="curTypeQuestionNum"
+                @nextQuestion="nextQuestion"
+                @submitCode="saveSubmittedCode"
+              ></code-pane>
+            </v-tab-item>
+          </v-tabs-items>
+        </div>
       </v-container>
+      <v-snackbar v-model="noticeUnfinished">
+        还有题目未作答
+      </v-snackbar>
     </v-main>
   </div>
 </template>
@@ -165,6 +179,7 @@ import {
   ChoiceQuestionSerializer,
   CodeQuestionSerializer,
   ExamQuestionSerializer,
+  ExamSerializer,
   QuestionSerializer
 } from '@/api/types'
 import { getExamById, getExamQuestions } from '@/api/exam'
@@ -181,9 +196,10 @@ export default Vue.extend({
       exitDialog: false,
       submitDialog: false,
       submitted: false,
-      currentTime: new Date().getTime(),
+      currentTime: 0,
+      examInfo: {} as ExamSerializer,
       endTime: 0,
-      choiceAnswers: [] as Array<{ num: number; answer: string }>, // TODO codeAnswers
+      choiceAnswers: [] as Array<{ index: number; answer: string }>,
       examId: 0,
       singleChoiceQuestions: [] as Array<ChoiceQuestionSerializer>,
       multiChoiceQuestions: [] as Array<ChoiceQuestionSerializer>,
@@ -192,7 +208,9 @@ export default Vue.extend({
       answeredChoices: [] as Array<{ index: number; answered: boolean }>,
       answeredCodes: [] as Array<{ index: number; answered: boolean }>,
       answerStatus: [] as Array<{ index: number; answered: boolean }>,
-      currentQuestionIndex: -1
+      currentQuestionIndex: -1,
+      codeIndex: -1,
+      noticeUnfinished: false
     }
   },
   computed: {
@@ -221,7 +239,10 @@ export default Vue.extend({
     choiceQuestionNum(): number {
       return this.singleChoiceNum + this.multiChoiceNum
     },
-    curChoice(): ChoiceQuestionSerializer {
+    totalQuestionNum(): number {
+      return this.choiceQuestionNum + this.codeQuestionNum
+    },
+    curChoiceQuestion(): ChoiceQuestionSerializer {
       if (!this.isChoiceProblem) {
         return {} as ChoiceQuestionSerializer
       }
@@ -232,14 +253,6 @@ export default Vue.extend({
           this.currentQuestionIndex - this.singleChoiceNum
         ]
       }
-    },
-    curCodeQuestion(): CodeQuestionSerializer {
-      if (!this.isChoiceProblem) {
-        return this.codeQuestions[
-          this.currentQuestionIndex - this.choiceQuestionNum
-        ]
-      }
-      return {} as CodeQuestionSerializer
     }
   },
   watch: {
@@ -252,14 +265,40 @@ export default Vue.extend({
             return { index: value.index, answered: value.answer !== '' }
           })
       }
+    },
+    currentQuestionIndex(val) {
+      if (val > this.choiceQuestionNum) {
+        this.codeIndex = val - this.choiceQuestionNum
+      }
     }
   },
   methods: {
-    // 切换题目时,先保存答过的选择题的答案;提交代码后,保存提交的代码题答案
-    saveAnswer(answer: string, lang?: string) {},
-    onSelectQuestion(index: number) {
+    saveSubmittedCode(code: string, lang: string) {
+      const changedCodeQuestion = this.codeQuestions[this.codeIndex]
+      const changedCodeTuple =
+        changedCodeQuestion.lastCommitCodes?.find((val) => {
+          return val.lang === lang
+        }) ?? {}
+      changedCodeTuple.code = code
+    },
+    saveChoiceAnswer(answer: string | undefined) {
+      if (answer === undefined) {
+        return
+      }
+      this.choiceAnswers[this.currentQuestionIndex].answer = answer
+
+      if (answer !== '') {
+        this.answerStatus[this.currentQuestionIndex].answered = true
+      } else {
+        this.answerStatus[this.currentQuestionIndex].answered = false
+      }
+    },
+    onSelectQuestion(target: number) {
       // this.saveAnswer()
-      this.currentQuestionIndex = index - 1
+      this.currentQuestionIndex = target - 1
+      if (!this.isChoiceProblem) {
+        this.codeIndex = target - 1 - this.choiceQuestionNum
+      }
     },
     leaveExam() {
       this.exitDialog = false
@@ -267,42 +306,67 @@ export default Vue.extend({
     },
     submitAnswer() {
       this.submitDialog = false
+
+      // if (
+      //   this.answerStatus.find((value) => {
+      //     return !value.answered
+      //   }) !== undefined
+      // ) {
+      //   this.noticeUnfinished = true
+      //   return
+      // }
+
+      const answers = [
+        ...this.choiceAnswers.map((value) => {
+          return value.answer
+        })
+      ]
+
+      const codes = this.codeQuestions.map((value) => {
+        const tuples = value.lastCommitCodes ?? []
+        return JSON.stringify(tuples)
+      })
+
+      // TODO post
+      console.log(answers)
+      console.log(codes)
       this.$router.back()
     },
-    nextQuestion(answer: string) {
-      // this.saveAnswer(answer)
-      this.currentQuestionIndex += 1
-    },
-    prevQuestion(answer: string) {
-      // this.saveAnswer(answer)
-      this.currentQuestionIndex -= 1
+    nextQuestion() {
+      // if (this.isChoiceProblem) {
+      //   this.saveAnswer(answer.toString())
+      // }
+      do {
+        this.currentQuestionIndex =
+          ++this.currentQuestionIndex % this.totalQuestionNum
+      } while (this.answerStatus[this.currentQuestionIndex].answered)
     },
     fetchData() {
       this.examId = parseInt(this.$route.params.id)
+
       getExamById(this.examId).then(({ data }) => {
-        this.endTime = Date.parse(data.res.endAt)
-        console.log(this.endTime)
+        this.examInfo = data.res ?? {}
+        this.endTime = new Date().getTime() + 100
+        // this.endTime = Date.parse(this.examInfo.endAt ?? '')
+        this.currentTime = new Date().getTime()
       })
 
       getExamQuestions(this.examId)
         .then(({ data }) => {
-          const examQuestionsObject: ExamQuestionSerializer = data.res
+          const examQuestionsObject: ExamQuestionSerializer = data.res ?? {}
 
-          this.singleChoiceQuestions = examQuestionsObject.singleChoiceQuestions?.map(
-            (value, index) => {
-              return { ...value, id: index + 1 }
-            }
-          )
-          this.multiChoiceQuestions = examQuestionsObject.multiChoiceQuestions?.map(
-            (value, index) => {
-              return { ...value, id: index + 1 }
-            }
-          )
-          this.codeQuestions = examQuestionsObject.codeQuestions?.map(
-            (value, index) => {
-              return { ...value, id: index + 1 }
-            }
-          )
+          this.singleChoiceQuestions =
+            examQuestionsObject.singleChoiceQuestions?.map((value, index) => {
+              return { ...value, index: index + 1 }
+            }) ?? []
+          this.multiChoiceQuestions =
+            examQuestionsObject.multiChoiceQuestions?.map((value, index) => {
+              return { ...value, index: index + 1 }
+            }) ?? []
+          this.codeQuestions =
+            examQuestionsObject.codeQuestions?.map((value, index) => {
+              return { ...value, index: index + 1 }
+            }) ?? []
 
           this.allQuestions = [
             ...this.singleChoiceQuestions,
@@ -324,6 +388,7 @@ export default Vue.extend({
           })
 
           this.currentQuestionIndex = 0 // 表示数据初始化完成
+          this.codeIndex = -1
         })
         .catch(() => {
           console.log('No data')

+ 28 - 21
src/views/exam/components/ChoicePane.vue

@@ -8,8 +8,8 @@
       <div class="d-flex">
         <v-card-title v-if="question.single">单选题</v-card-title>
         <v-card-title v-else>多选题</v-card-title>
-        <v-chip class="align-self-center" style="background-color: darkgray"
-          >{{ question.id }} / {{ totalQuestionNum }}</v-chip
+        <v-chip class="align-self-center"
+          >{{ question.index }} / {{ totalQuestionNum }}</v-chip
         >
       </div>
       <markdown-text :raw="question.stem" class="pa-10"></markdown-text>
@@ -57,8 +57,7 @@
           </template>
         </v-list-item-group>
       </v-list>
-      <div class="d-flex justify-space-between px-10 py-10">
-        <v-btn color="info" @click="prevQuestion">上一题</v-btn>
+      <div class="d-flex justify-space-around px-10 py-10">
         <v-btn color="info" @click="nextQuestion">下一题</v-btn>
       </div>
     </v-card>
@@ -87,31 +86,39 @@ export default Vue.extend({
   },
   data() {
     return {
-      answer: [] as Array<String>,
+      answer: [] as Array<string>,
       choices: [] as Array<OptionSerializer>
     }
   },
+  computed: {
+    answerString(): string {
+      if (!this.answer || this.answer.length === 0) {
+        return ''
+      }
+      if (this.question.single) {
+        return this.answer[0]
+      } else {
+        const sorted = clone(this.answer).sort()
+        return sorted.join('')
+      }
+    }
+  },
   mounted() {
     this.choices = clone(this.question.options) ?? []
   },
-  // beforeUpdate() {
-  //   this.answer = []
-  // },
+  watch: {
+    question(val) {
+      this.choices = clone(val.options) ?? []
+      this.answer = []
+    }
+  },
   methods: {
     nextQuestion() {
-      if (this.question.single) {
-        this.$emit('nextQuestion', this.answer)
-      } else {
-        this.$emit('nextQuestion', this.answer.sort().join(''))
-      }
-    },
-    prevQuestion() {
-      if (this.question.single) {
-        this.$emit('prevQuestion', this.answer)
-      } else {
-        this.$emit('prevQuestion', this.answer.sort().join(''))
-      }
+      this.$emit('nextQuestion')
     }
+  },
+  beforeUpdate() {
+    this.$emit('saveChoiceAnswer', this.answerString.toString())
   }
 })
 </script>
@@ -119,7 +126,7 @@ export default Vue.extend({
 <style scoped>
 .questionCard {
   margin: 1vh 10vw;
-  height: 90vh;
+  min-height: 90vh;
   padding: 10px;
 }
 </style>

+ 1 - 1
src/views/exam/components/CodeEditor.vue

@@ -79,7 +79,7 @@ export default Vue.extend({
 
       this.editor = this.monaco.editor.create(this.$el as HTMLElement, options)
 
-      this.editor.onDidChangeModelContent((event) => {
+      this.editor.onDidChangeModelContent(() => {
         const value = this.editor.getValue()
         if (this.value !== value) {
           this.$emit('change', value)

+ 47 - 50
src/views/exam/components/CodePane.vue

@@ -16,7 +16,7 @@
                 ></markdown-text>
               </div>
             </v-tab-item>
-            <v-tab-item class="pa-2">
+            <v-tab-item>
               <div class="d-flex flex-column justify-space-between pa-2">
                 <v-card
                   flat
@@ -82,8 +82,7 @@
         </div>
 
         <div style="height: 32px" class="d-flex justify-space-between">
-          <v-btn color="info" small @click="prevQuestion">上一题</v-btn>
-          <span> 代码题 {{ question.id }} / {{ totalQuestionNum }}</span>
+          <v-chip> 代码题 {{ question.index }} / {{ totalQuestionNum }}</v-chip>
           <v-btn color="info" small @click="nextQuestion">下一题</v-btn>
         </div>
       </div>
@@ -114,6 +113,7 @@
                   class="mx-2 mb-2 pa-0 elevation-1 align-self-center"
                   v-bind="attrs"
                   v-on="on"
+                  @click="resetLastCommit"
                 >
                   <v-icon>mdi-arrow-left</v-icon>
                 </v-btn>
@@ -129,6 +129,7 @@
                   class="mx-2 mb-2 pa-0 elevation-1 align-self-center"
                   v-bind="attrs"
                   v-on="on"
+                  @click="resetOriginCode"
                 >
                   <v-icon>mdi-refresh</v-icon>
                 </v-btn>
@@ -144,7 +145,10 @@
             >保存测试</v-btn
           >
         </div>
-        <code-editor :language="currentLang" v-model="curCode"></code-editor>
+        <code-editor
+          :language="currentLang"
+          v-model="currentCode"
+        ></code-editor>
       </div>
     </div>
   </v-card>
@@ -183,12 +187,12 @@ export default Vue.extend({
   },
   data() {
     return {
-      tab: null,
+      tab: 0,
       commits: [] as CommitInfo[],
       languages: ['cpp', 'java', 'python'],
       currentLang: '',
-      curCode: '',
-      codeAnswer: {} as CodeQuestionSerializer,
+      currentCode: '',
+      codeCache: [] as CodeTuple[], // 切换语言时,将当前编辑的保存到cache中,下次切换回来可继续编辑
       currentCommit: (null as unknown) as CommitInfo,
       statusTags: [
         '所有用例通过',
@@ -200,47 +204,37 @@ export default Vue.extend({
       statusColors: ['success', 'warning', 'error', 'accent', 'info']
     }
   },
-  computed: {
-    // lastEdit: function() {
-    //   return clone(this.currentQuestion.currentCode)
-    // },
-    //
-    // codeByLanguage() {
-    //   for (const languageCode of this.currentQuestion.currentCode) {
-    //     if (this.currentLang === languageCode.type) {
-    //       return languageCode.code
-    //     }
-    //   }
-    //   return ''
-    // }
-  },
   watch: {
     currentLang(val, oldVal) {
       if (oldVal !== '' && val !== oldVal) {
-        const newCode = this.codeAnswer.codeTuples?.find((value) => {
-          return value.lang === val
-        }).code
+        const newCode =
+          this.codeCache.find((value) => {
+            return value.lang === val
+          })?.code ?? ''
 
-        const oldCodeIndex = this.codeAnswer.codeTuples.findIndex((value) => {
-          return value.lang === oldVal
-        })
-        this.codeAnswer.codeTuples[oldCodeIndex].code = this.curCode
+        const oldCodeTuple =
+          this.codeCache.find((value) => {
+            return value.lang === oldVal
+          }) ?? {}
 
-        this.curCode = newCode
+        oldCodeTuple.code = this.currentCode
+        this.currentCode = newCode
       }
     }
   },
   created() {
-    console.log(this.question)
-
-    this.languages = this.question.codeTuples?.map((value) => {
-      return value.lang
-    })
+    this.languages =
+      this.question?.codeTuples?.map((value) => {
+        return value.lang ?? ''
+      }) ?? []
     this.currentLang = this.languages[0]
 
-    this.curCode = this.question.codeTuples[0].code
+    this.currentCode =
+      this.question?.codeTuples?.find((value, index) => {
+        return index === 0
+      })?.code ?? ''
 
-    this.codeAnswer = clone(this.question)
+    this.codeCache = clone(this.question.codeTuples) ?? []
 
     for (let i = 0; i < 10; i++) {
       const r = Math.floor(Math.random() * 4 + 1)
@@ -255,8 +249,8 @@ export default Vue.extend({
   },
   methods: {
     submitCode(): void {
-      console.log(this.currentCode)
-      this.$emit('submitCode')
+      this.tab = 1
+      this.$emit('submitCode', this.currentCode, this.currentLang)
     },
     getCurrentCommit(): CommitInfo {
       return this.commits[0]
@@ -264,22 +258,25 @@ export default Vue.extend({
     statusToColor(status: string): string {
       return this.statusColors[this.statusTags.indexOf(status)]
     },
-    fetchData() {},
-    resetCode() {
-      for (const codeQuestion of this.codeQuestions) {
-        if (codeQuestion.id === this.currentQuestion.id) {
-          this.currentQuestion.currentCode = clone(codeQuestion.originalCode)
-        }
-        // 添加完成后的提示
-      }
+    resetLastCommit() {
+      const lastCommitCodes = this.question.lastCommitCodes
+      const lastCommitTuple = lastCommitCodes?.find((val) => {
+        return val.lang === this.currentLang
+      })
+
+      this.currentCode = lastCommitTuple?.code ?? ''
     },
-    prevQuestion() {
-      this.$emit('prevQuestion')
+    resetOriginCode() {
+      const originCodes = this.question.codeTuples ?? []
+      const originTuple =
+        originCodes.find((val) => {
+          return val.lang === this.currentLang
+        }) ?? {}
+      this.currentCode = originTuple.code ?? ''
     },
     nextQuestion() {
       this.$emit('nextQuestion')
-    },
-    onLanguageChange() {}
+    }
   }
 })
 </script>

+ 9 - 4
src/views/exam/components/CountDown.vue

@@ -21,7 +21,7 @@ export default {
   },
   data() {
     return {
-      content: '加载中...',
+      content: '',
       end: '', //  结束时间
       current: '' // 当前时间
     }
@@ -36,9 +36,13 @@ export default {
   },
   methods: {
     startCount() {
-      this.current = this.currentTime
-      this.end = this.endTime
-      this.countDown()
+      if (this.currentTime === 0) {
+        this.content = '加载中...'
+      } else {
+        this.current = this.currentTime
+        this.end = this.endTime
+        this.countDown()
+      }
     },
     countDown() {
       const self = this
@@ -69,6 +73,7 @@ export default {
         } else {
           clearInterval(timer)
           self.content = self.endText
+          self.$emit('timesUp')
         }
       }, 1000)
     }