ChengYuXuan 5 лет назад
Родитель
Сommit
a4df32ea75

+ 8 - 1
src/api/exam.ts

@@ -5,7 +5,8 @@ import {
   ReceivedData,
   Pageable,
   ExamStatus,
-  ID
+  ID,
+  ExamQuestionSerializer
 } from '@/api/types'
 
 export const getAllExamList = (params: Pageable<{ status: ExamStatus }>) => {
@@ -21,3 +22,9 @@ export const getExamById = (examId: ID) => {
 export const createExam = (exam: object) => {
   return axios.post(`${EXAM_MODULE}`)
 }
+
+export const getExamQuestions = (examId: ID) => {
+  return axios.get<ReceivedData<ExamQuestionSerializer>>(
+    `${EXAM_MODULE}/${examId}/question`
+  )
+}

+ 10 - 8
src/api/types.d.ts

@@ -80,12 +80,15 @@ export interface OptionSerializer {
   content?: string
 }
 
+export interface QuestionSerializer {
+  id?: number
+  stem?: string
+}
+
 export type ChoiceAnswer = string
 
-export interface ChoiceQuestionSerializer {
-  id?: number
+export interface ChoiceQuestionSerializer extends QuestionSerializer {
   studentId?: number
-  stem?: string
   single?: boolean
   options?: Array<OptionSerializer>
   lastCommitAnswers?: ChoiceAnswer
@@ -96,16 +99,15 @@ export interface CodeTemplate {
   template?: string
 }
 
-export interface CodeQuestionSerializer {
-  id?: number
+export interface CodeQuestionSerializer extends QuestionSerializer {
   studentId?: number
-  stem?: string
-  codeTemplate?: CodeTemplate
+  codeTemplates?: Array<CodeTemplate>
   lastCommitCode?: string
 }
 
 export interface ExamQuestionSerializer {
   id?: number
-  choiceQuestions?: Array<ChoiceQuestionSerializer>
+  singleChoiceQuestions?: Array<ChoiceQuestionSerializer>
+  multiChoiceQuestions?: Array<ChoiceQuestionSerializer>
   codeQuestions?: Array<CodeQuestionSerializer>
 }

+ 5 - 0
src/mock/api/exam/_examId/question/get.json

@@ -0,0 +1,5 @@
+{
+  "err": 0,
+  "res": "@EXAM_QUESTION_SERIALIZER"
+
+}

+ 76 - 1
src/mock/serializers.js

@@ -42,7 +42,82 @@ const serializersPlugin = (mock) => ({
     limit: 20,
     size: 20,
     total: 20
-  })
+  }),
+
+  SINGLE_CHOICE_QUESTION_SERIALIZER: () =>
+    mock({
+      'id|1-1000': 1,
+      stem: '@CPARAGRAPH',
+      single: true,
+      options: [
+        {
+          label: 'A',
+          content: 'a'
+        },
+        {
+          label: 'B',
+          content: 'ab'
+        },
+        {
+          label: 'C',
+          content: 'abc'
+        },
+        {
+          label: 'D',
+          content: 'abcd'
+        }
+      ]
+    }),
+  MULTI_CHOICE_QUESTION_SERIALIZER: () =>
+    mock({
+      'id|1-1000': 1,
+      stem: '@CPARAGRAPH',
+      single: false,
+      options: [
+        {
+          label: 'A',
+          content: '1'
+        },
+        {
+          label: 'B',
+          content: '12'
+        },
+        {
+          label: 'C',
+          content: '123'
+        },
+        {
+          label: 'D',
+          content: '1234'
+        }
+      ]
+    }),
+  CODE_QUESTION_SERIALIZER: () =>
+    mock({
+      'id|1-1000': 1,
+      stem: '@CPARAGRAPH',
+      codeTemplates: [
+        {
+          lang: 'cpp',
+          template: 'int main(){}'
+        },
+        {
+          lang: 'java',
+          template: 'public class Main{}'
+        },
+        {
+          lang: 'python',
+          template: "if __name__ == '__main__':"
+        }
+      ]
+    }),
+  EXAM_QUESTION_SERIALIZER: () =>
+    mock({
+      'id|1-1000': 1,
+      'singleChoiceQuestions|10': ['@SINGLE_CHOICE_QUESTION_SERIALIZER'],
+      'multiChoiceQuestions|5': ['@MULTI_CHOICE_QUESTION_SERIALIZER'],
+      'codeQuestions|2': ['@CODE_QUESTION_SERIALIZER']
+    })
 })
 
 module.exports = function(random, mock) {

+ 104 - 29
src/views/exam/ExamPane.vue

@@ -35,12 +35,13 @@
               <h2>单选题</h2>
             </div>
             <v-btn
-              v-for="i in questions"
-              :key="i.id"
+              v-for="i in answeredChoices.slice(0, singleChoiceNum)"
+              :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
+              :disabled="i.answered"
             >
-              {{ i.id }}
+              {{ i.index }}
             </v-btn>
           </div>
           <div column class="justify-space-between d-inline">
@@ -48,13 +49,16 @@
               <h2>多选题</h2>
             </div>
             <v-btn
-              v-for="i in 12"
-              :key="i"
+              v-for="i in answeredChoices.slice(
+                singleChoiceNum,
+                singleChoiceNum + multiChoiceNum
+              )"
+              :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
-              :disabled="questions[i].answered"
+              :disabled="i.answered"
             >
-              {{ questions[i - 1].id }}
+              {{ i.index }}
             </v-btn>
           </div>
           <div column class="justify-space-between">
@@ -62,13 +66,13 @@
               <h2>代码题</h2>
             </div>
             <v-btn
-              v-for="i in 4"
-              :key="i"
+              v-for="i in answeredCodes"
+              :key="i.index"
               class="ml-2 mt-2 pa-0 d-inline"
               x-small
-              :disabled="questions[i].answered"
+              :disabled="i.answered"
             >
-              {{ questions[i - 1].id }}
+              {{ i.index }}
             </v-btn>
           </div>
         </div>
@@ -132,10 +136,13 @@
         <code-pane
           v-if="!isChoiceProblem"
           v-on:nextQuestion="nextQuestion"
+          :question="curQuestion"
         ></code-pane>
         <choice-pane
           v-if="isChoiceProblem"
           v-on:nextQuestion="nextQuestion(answer)"
+          :total-question-num="multiChoiceNum"
+          :question="curQuestion"
         ></choice-pane>
       </v-container>
     </v-main>
@@ -147,32 +154,74 @@ import Vue from 'vue'
 import CodePane from '@/views/exam/components/CodePane.vue'
 import ChoicePane from '@/views/exam/components/ChoicePane.vue'
 import CountDown from '@/views/exam/components/CountDown.vue'
-import { ChoiceQuestionSerializer } from '@/api/types'
-
-interface OrderedCQuestion {}
+import {
+  ChoiceQuestionSerializer,
+  CodeQuestionSerializer,
+  ExamQuestionSerializer,
+  QuestionSerializer
+} from '@/api/types'
+import { getExamQuestions } from '@/api/exam'
 
 export default Vue.extend({
   name: 'ExamPane',
+  components: {
+    CountDown,
+    CodePane,
+    ChoicePane
+  },
   data() {
     return {
-      isChoiceProblem: true,
       exitDialog: false,
       submitDialog: false,
-      choiceQuestions: [] as Array<ChoiceQuestionSerializer>,
       submitted: false,
       currentTime: new Date().getTime(),
-      choiceAnswers: [] as Array<String>
+      choiceAnswers: [] as Array<{ num: number; answer: string }>, // TODO codeAnswers
+      examId: 0,
+      examQuestionsObject: {} as ExamQuestionSerializer,
+      singleChoiceQuestions: [] as Array<ChoiceQuestionSerializer>,
+      multiChoiceQuestions: [] as Array<ChoiceQuestionSerializer>,
+      codeQuestions: [] as Array<CodeQuestionSerializer>,
+      allQuestions: [] as Array<QuestionSerializer>,
+      answeredChoices: [] as Array<{ index: number; answered: boolean }>,
+      answeredCodes: [] as Array<{ index: number; answered: boolean }>,
+
+      currentQuestionIndex: 0
     }
   },
   computed: {
-    choiceQuestionAnswered(): Array<Boolean> {
-      return this.choiceAnswers.map((value, index) => value !== '')
+    isChoiceProblem(): boolean {
+      return this.currentQuestionIndex < this.choiceQuestionNum
+    },
+    isSingleChoice(): boolean {
+      return this.currentQuestionIndex < this.singleChoiceNum
+    },
+    singleChoiceNum(): number {
+      return this.singleChoiceQuestions.length
+    },
+    multiChoiceNum(): number {
+      return this.multiChoiceQuestions.length
+    },
+    codeQuestionNum(): number {
+      return this.codeQuestions.length
+    },
+    choiceQuestionNum(): number {
+      return this.singleChoiceNum + this.multiChoiceNum
+    },
+    curQuestion(): QuestionSerializer {
+      return this.allQuestions[this.currentQuestionIndex]
     }
   },
-  components: {
-    CountDown,
-    CodePane,
-    ChoicePane
+  watch: {
+    choiceAnswers: {
+      deep: true,
+      handler: function(val: Array<{ index: number; answer: string }>) {
+        this.answeredChoices = val
+          .slice(0, this.singleChoiceNum + this.multiChoiceNum)
+          .map((value) => {
+            return { index: value.index, answered: value.answer !== '' }
+          })
+      }
+    }
   },
   methods: {
     leaveExam() {
@@ -184,15 +233,41 @@ export default Vue.extend({
       this.$router.back()
     },
     nextQuestion(answer: String) {
-      this.choiceAnswers.push(answer)
+      // this.choiceAnswers.push(answer)
       this.isChoiceProblem = !this.isChoiceProblem
+      this.currentQuestionIndex += 1
+    },
+    fetchData() {
+      this.examId = parseInt(this.$route.params.id)
+      console.log(this.examId)
+      getExamQuestions(this.examId)
+        .then(({ data }) => {
+          this.examQuestionsObject = data.res ?? {}
+          this.singleChoiceQuestions =
+            this.examQuestionsObject?.singleChoiceQuestions ?? []
+          this.multiChoiceQuestions =
+            this.examQuestionsObject?.multiChoiceQuestions ?? []
+          this.codeQuestions = this.examQuestionsObject?.codeQuestions ?? []
+          this.allQuestions = [
+            ...this.singleChoiceQuestions,
+            ...this.multiChoiceQuestions,
+            ...this.codeQuestions
+          ]
+          this.choiceAnswers = [
+            ...this.singleChoiceQuestions,
+            ...this.multiChoiceQuestions
+          ].map((value, index) => {
+            return { num: index + 1, answer: '' }
+          })
+        })
+        .catch(() => {
+          console.log('No data')
+        })
     }
   },
-  beforeMount() {}
-  // beforeRouteLeave(to, from, next) {
-  //   this.exitDialog = true
-  //
-  // }
+  mounted() {
+    this.fetchData()
+  }
 })
 </script>
 

+ 81 - 43
src/views/exam/components/ChoicePane.vue

@@ -1,66 +1,104 @@
 <template>
-  <v-card
-    class="questionCard d-flex flex-column justify-space-between"
-    hover
-    style="cursor: default"
-  >
-    <div class="d-flex">
-      <v-card-title>多选题</v-card-title>
-      <v-chip class="align-self-center" style="background-color: darkgray"
-        >1/20</v-chip
-      >
-    </div>
-    <!-- <v-card-text class="flex pa-10">{{question.description}}</v-card-text>
-    <v-container style="height: max-content" class="mb-10 flex">
-      <v-img max-height="400" contain src="@/assets/logo.svg"></v-img>
-    </v-container> -->
-    <markdown-text :raw="question.description" class="pa-10"></markdown-text>
-    <v-list class="px-10 flex">
-      <v-list-item-group v-model="answer" multiple>
-        <v-divider></v-divider>
-        <template v-for="choice in choices">
-          <v-list-item :key="choice.id" :value="choice.id">
-            <template v-slot:default="{ active }">
-              <v-list-item-action>
-                <v-checkbox hide-details :input-value="active"> </v-checkbox>
-              </v-list-item-action>
-              <v-list-item-content>
-                <v-list-item-title class="text-wrap"
-                  >{{ choice.id }}: {{ choice.text }}</v-list-item-title
-                >
-              </v-list-item-content>
+  <div>
+    <v-card
+      class="questionCard d-flex flex-column justify-space-between"
+      hover
+      style="cursor: default"
+      v-if="question.single"
+    >
+      <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
+        >
+      </div>
+      <markdown-text :raw="question.stem" class="pa-10"></markdown-text>
+      <v-list class="px-10 flex">
+        <v-list-item-group v-model="answer" multiple>
+          <v-divider></v-divider>
+          <div v-if="question.single">
+            <template v-for="choice in choices">
+              <v-list-item :key="choice.label" :value="choice.content">
+                <template v-slot:default="{ active }">
+                  <v-list-item-action>
+                    <v-checkbox
+                      hide-details
+                      :input-value="active"
+                      on-icon="mdi-radiobox-marked"
+                      off-icon="mdi-radiobox-blank"
+                    >
+                    </v-checkbox>
+                  </v-list-item-action>
+                  <v-list-item-content>
+                    <v-list-item-title class="text-wrap"
+                      >{{ choice.label }}:
+                      {{ choice.content }}</v-list-item-title
+                    >
+                  </v-list-item-content>
+                </template>
+              </v-list-item>
+              <v-divider :key="choice.content"></v-divider>
             </template>
-          </v-list-item>
-          <v-divider :key="choice.id + 1"></v-divider>
-        </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>
-      <v-btn color="info" @click="nextQuestion">下一题</v-btn>
-    </div>
-  </v-card>
+          </div>
+          <div v-else>
+            <template v-for="choice in choices">
+              <v-list-item :key="choice.label" :value="choice.label">
+                <template v-slot:default="{ active }">
+                  <v-list-item-action>
+                    <v-checkbox hide-details :input-value="active">
+                    </v-checkbox>
+                  </v-list-item-action>
+                  <v-list-item-content>
+                    <v-list-item-title class="text-wrap"
+                      >{{ choice.label }}:
+                      {{ choice.content }}</v-list-item-title
+                    >
+                  </v-list-item-content>
+                </template>
+              </v-list-item>
+              <v-divider :key="choice.content"></v-divider>
+            </template>
+          </div>
+        </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>
+        <v-btn color="info" @click="nextQuestion">下一题</v-btn>
+      </div>
+    </v-card>
+  </div>
 </template>
 
 <script lang="ts">
 import Vue, { PropType } from 'vue'
 import MarkdownText from '@/views/exam/components/MarkdownText.vue'
 import { ChoiceQuestionSerializer, OptionSerializer } from '@/api/types'
+import { clone } from 'ramda'
 export default Vue.extend({
   name: 'ChoicePane',
   components: {
     MarkdownText
   },
   props: {
-    question: {},
-    type: Object as PropType<ChoiceQuestionSerializer>
+    question: {
+      type: Object as PropType<ChoiceQuestionSerializer>,
+      required: true
+    },
+    totalQuestionNum: {
+      type: Number,
+      required: true
+    }
   },
   data() {
     return {
-      answer: [],
+      answer: [] as Array<String>,
       choices: [] as Array<OptionSerializer>
     }
   },
+  mounted() {
+    this.choices = clone(this.question.options) ?? []
+  },
   methods: {
     nextQuestion() {
       this.$emit('nextQuestion', this.answer.join(''))