Explorar o código

判断题和填空题答题优化

Michael Wang %!s(int64=6) %!d(string=hai) anos
pai
achega
5602d29c78

+ 19 - 18
src/features/OnlineExam/Examing/BooleanQuestionView.vue

@@ -2,33 +2,30 @@
   <div class="question-view">
   <div class="question-view">
     <question-body :questionBody="question.body" :examQuestionId="examQuestion.id"></question-body>
     <question-body :questionBody="question.body" :examQuestionId="examQuestion.id"></question-body>
     <div class="ops">
     <div class="ops">
-      <div class="stu-answer"> {{stuAnswer}}</div>
+      <div class="stu-answer">{{stuAnswer}}</div>
       <div class="score">({{question.questionScore}}分)</div>
       <div class="score">({{question.questionScore}}分)</div>
     </div>
     </div>
-    <div @click="answerQuestion(examQuestion.id, '正确')">
+    <div @click="() => answerQuestion(examQuestion.id, '正确')">
       <input type="radio" name="question" value="正确" :checked="stuAnswer === '正确'" />
       <input type="radio" name="question" value="正确" :checked="stuAnswer === '正确'" />
       <span class="question-options">正确</span>
       <span class="question-options">正确</span>
     </div>
     </div>
-    <div @click="answerQuestion(examQuestion.id, '错误')">
+    <div @click="() => answerQuestion(examQuestion.id, '错误')">
       <input type="radio" name="question" value="错误" :checked="stuAnswer === '错误'" />
       <input type="radio" name="question" value="错误" :checked="stuAnswer === '错误'" />
       <span class="question-options">错误</span>
       <span class="question-options">错误</span>
     </div>
     </div>
     <div class="reset">
     <div class="reset">
-      <i-button type="warning" size="large" @click="resetQuestion(examQuestion.id)">重置答案</i-button>
+      <i-button type="warning" size="large" @click="() => answerQuestion(examQuestion.id, null)">重置答案</i-button>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import QuestionBody from "./QuestionBody";
 import QuestionBody from "./QuestionBody";
+import { createNamespacedHelpers } from "vuex";
+const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
 export default {
 export default {
   name: "BooleanQuestionView",
   name: "BooleanQuestionView",
-  data() {
-    return {
-      stuAnswer: this.examQuestion.stuAnswer
-    };
-  },
   props: {
   props: {
     question: Object,
     question: Object,
     examQuestion: Object
     examQuestion: Object
@@ -40,6 +37,7 @@ export default {
     window.removeEventListener("keyup", this.keyup);
     window.removeEventListener("keyup", this.keyup);
   },
   },
   methods: {
   methods: {
+    ...mapMutations(["updateExamQuestionAnswer"]),
     keyup(e) {
     keyup(e) {
       // console.log(e);
       // console.log(e);
       // console.log(document.activeElement.type);
       // console.log(document.activeElement.type);
@@ -59,15 +57,18 @@ export default {
         this.answerQuestion(this.examQuestion.id, this.stuAnswer);
         this.answerQuestion(this.examQuestion.id, this.stuAnswer);
       }
       }
     },
     },
-    answerQuestion: function(examQuestionId, stuAnswer) {
-      this.stuAnswer = stuAnswer;
-      this.$http.put("/api/exam_question/" + examQuestionId, { stuAnswer });
-    },
-    resetQuestion: function(examQuestionId) {
-      this.stuAnswer = null;
-      this.$http.put("/api/exam_question/" + examQuestionId, {
-        stuAnswer: null
-      });
+    async answerQuestion(examQuestionId, stuAnswer) {
+      if (stuAnswer !== this.examQuestion.stuAnswer) {
+        await this.$http.put("/api/exam_question/" + examQuestionId, {
+          stuAnswer
+        });
+        this.updateExamQuestionAnswer({ examQuestionId, stuAnswer });
+      }
+    }
+  },
+  computed: {
+    stuAnswer() {
+      return this.examQuestion.stuAnswer;
     }
     }
   },
   },
   components: {
   components: {

+ 26 - 50
src/features/OnlineExam/Examing/ExamingHome.vue

@@ -1,5 +1,5 @@
 <template>
 <template>
-  <div class="container">
+  <div v-if="exam" class="container">
     <div class="header">
     <div class="header">
       <RemainTime></RemainTime>
       <RemainTime></RemainTime>
       <OverallProgress :exam-question-list="validQuestions"></OverallProgress>
       <OverallProgress :exam-question-list="validQuestions"></OverallProgress>
@@ -29,75 +29,58 @@ import QuestionView from "./QuestionView.vue";
 import ArrowNavView from "./ArrowNavView.vue";
 import ArrowNavView from "./ArrowNavView.vue";
 import QuestionNavView from "./QuestionNavView.vue";
 import QuestionNavView from "./QuestionNavView.vue";
 import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
 import FaceRecognition from "../../../components/FaceRecognition/FaceRecognition";
+import { createNamespacedHelpers } from "vuex";
+const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
 export default {
 export default {
   name: "ExamingHome",
   name: "ExamingHome",
   data() {
   data() {
     return {
     return {
-      exam: null,
-      paperStruct: null,
-      validQuestions: [],
-      examQuestionList: [],
       preExamQuestion: null,
       preExamQuestion: null,
       nextExamQuestion: null,
       nextExamQuestion: null,
       examQuestion: null
       examQuestion: null
     };
     };
   },
   },
-  async mounted() {
+  async created() {
+    await this.initData();
     if (!this.$route.query.examQuestionId) {
     if (!this.$route.query.examQuestionId) {
-      const exam = await this.$http.get(
-        "/api/ecs_exam_work/exam/" + this.$route.params.examId
-      );
-      this.exam = exam.data;
-
-      const paperStruct = await this.$http.get(
-        "/api/exam_question/paper_struct/?exam_record_id=" +
-          this.$route.query.examRecordId
-      );
-      this.paperStruct = paperStruct.data;
-
-      // FIXME: global API processing. mock or not
-      const examQuestionList = await this.$http.get(
-        "/api/exam_question/?exam_record_id=" + this.$route.query.examRecordId
-      );
-      this.examQuestionList = examQuestionList.data;
-      this.validQuestions = this.examQuestionList.filter(
-        q => q.nestedQuestion === false
-      );
       this.$router.push(
       this.$router.push(
         this.$route.fullPath + "&examQuestionId=" + this.examQuestionList[0].id
         this.$route.fullPath + "&examQuestionId=" + this.examQuestionList[0].id
       );
       );
       return;
       return;
     }
     }
-    this.init();
+    this.updateQuestion();
   },
   },
   beforeRouteUpdate(to, from, next) {
   beforeRouteUpdate(to, from, next) {
-    this.init(next);
+    this.updateQuestion(next);
   },
   },
   methods: {
   methods: {
-    init: async function(next) {
+    ...mapMutations(["updateExamState"]),
+    async initData() {
       const exam = await this.$http.get(
       const exam = await this.$http.get(
         "/api/ecs_exam_work/exam/" + this.$route.params.examId
         "/api/ecs_exam_work/exam/" + this.$route.params.examId
       );
       );
-      this.exam = exam.data;
 
 
-      if (!this.paperStruct || this.paperStruct.length == 0) {
-        const paperStruct = await this.$http.get(
-          "/api/exam_question/paper_struct/?exam_record_id=" +
-            this.$route.query.examRecordId
-        );
-        this.paperStruct = paperStruct.data;
-      }
+      const paperStruct = await this.$http.get(
+        "/api/exam_question/paper_struct/?exam_record_id=" +
+          this.$route.query.examRecordId
+      );
 
 
-      // FIXME: global API processing. mock or not
       const examQuestionList = await this.$http.get(
       const examQuestionList = await this.$http.get(
         "/api/exam_question/?exam_record_id=" + this.$route.query.examRecordId
         "/api/exam_question/?exam_record_id=" + this.$route.query.examRecordId
       );
       );
-      this.examQuestionList = examQuestionList.data;
-      this.validQuestions = this.examQuestionList.filter(
+      const validQuestions = examQuestionList.data.filter(
         q => q.nestedQuestion === false
         q => q.nestedQuestion === false
       );
       );
 
 
+      this.updateExamState({
+        exam: exam.data,
+        paperStruct: paperStruct.data,
+        examQuestionList: examQuestionList.data,
+        validQuestions
+      });
+    },
+    updateQuestion: async function(next) {
       // 初始化套题的答案,为回填部分选项做准备
       // 初始化套题的答案,为回填部分选项做准备
       // for (let q of this.examQuestionList) {
       // for (let q of this.examQuestionList) {
       //   if (q.subQuestionList.length > 0) {
       //   if (q.subQuestionList.length > 0) {
@@ -109,6 +92,7 @@ export default {
       // }
       // }
 
 
       next && next();
       next && next();
+      if (!this.exam) return;
       this.examQuestion = this.examQuestionList.find(
       this.examQuestion = this.examQuestionList.find(
         eq => eq.id == this.$route.query.examQuestionId // number == string
         eq => eq.id == this.$route.query.examQuestionId // number == string
       );
       );
@@ -121,6 +105,9 @@ export default {
       this.$router.push("/");
       this.$router.push("/");
     }
     }
   },
   },
+  computed: {
+    ...mapState(["exam", "paperStruct", "examQuestionList", "validQuestions"])
+  },
   components: {
   components: {
     RemainTime,
     RemainTime,
     OverallProgress,
     OverallProgress,
@@ -135,8 +122,6 @@ export default {
 
 
 <style scoped>
 <style scoped>
 .container {
 .container {
-  /* display: flex;
-  flex-direction: row; */
   display: grid;
   display: grid;
   grid-template-areas:
   grid-template-areas:
     "header header"
     "header header"
@@ -149,33 +134,24 @@ export default {
 }
 }
 
 
 .header {
 .header {
-  /* display: flex;
-  flex-direction: row; */
   display: grid;
   display: grid;
   place-items: center;
   place-items: center;
   grid-template-columns: 200px 1fr 300px 100px;
   grid-template-columns: 200px 1fr 300px 100px;
-
   grid-area: header;
   grid-area: header;
-
   height: 80px;
   height: 80px;
   background-color: #f5f5f5;
   background-color: #f5f5f5;
 }
 }
 
 
 .main {
 .main {
   display: grid;
   display: grid;
-
   grid-area: main;
   grid-area: main;
-
   grid-template-rows: 1fr 50px;
   grid-template-rows: 1fr 50px;
 }
 }
 
 
 .side {
 .side {
   display: grid;
   display: grid;
-
   grid-area: side;
   grid-area: side;
-
   grid-template-rows: 1fr 300px;
   grid-template-rows: 1fr 300px;
-
   background-color: #f5f5f5;
   background-color: #f5f5f5;
 }
 }
 
 

+ 33 - 36
src/features/OnlineExam/Examing/FillBlankQuestionView.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
   <div class="question-view">
   <div class="question-view">
-    <question-body :questionBody="question.body" :examQuestionId="examQuestion.id"></question-body>
+    <question-body :questionBody="questionBody" :examQuestionId="examQuestion.id"></question-body>
     <div class="ops">
     <div class="ops">
       <div class="score">({{question.questionScore}}分)</div>
       <div class="score">({{question.questionScore}}分)</div>
     </div>
     </div>
@@ -9,21 +9,22 @@
       <input type="text" name="question" class="input-answer" :value="option" @input="inputAnswer" />
       <input type="text" name="question" class="input-answer" :value="option" @input="inputAnswer" />
     </div>
     </div>
     <div class="reset">
     <div class="reset">
-      <i-button type="warning" size="large" @click="resetQuestion(examQuestion.id)">重置答案</i-button>
+      <i-button type="warning" size="large" @click="() => answerQuestion(examQuestion.id, null)">重置答案</i-button>
     </div>
     </div>
   </div>
   </div>
 </template>
 </template>
 
 
 <script>
 <script>
 import QuestionBody from "./QuestionBody";
 import QuestionBody from "./QuestionBody";
+import { createNamespacedHelpers } from "vuex";
+const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
 export default {
 export default {
   name: "FillBlankQuestionView",
   name: "FillBlankQuestionView",
   data() {
   data() {
     return {
     return {
-      questionNumber: 0,
-      questionBody: "",
-      stuAnswer: ""
+      stuAnswer: "",
+      questionBody: ""
     };
     };
   },
   },
   props: {
   props: {
@@ -31,35 +32,37 @@ export default {
     examQuestion: Object
     examQuestion: Object
   },
   },
   created() {
   created() {
-    const questionNumber = this.question.body.split(/_{5,}/).length - 1;
-
-    const answers = this.examQuestion.stuAnswer
-      ? this.examQuestion.stuAnswer.split("##")
-      : "##".repeat(questionNumber - 1).split("##");
-    let questionBody = this.question.body.replace(
-      /_{5,}/g,
-      () =>
-        "<span style='display: inline-block; min-width: 80px; border-bottom: 1px solid black; text-align: center'>" +
-        (answers.shift() || questionNumber - answers.length) +
-        "</span>"
-    );
-    this.stuAnswer =
-      this.examQuestion.stuAnswer || "##".repeat(questionNumber - 1);
-
-    this.questionNumber = questionNumber;
-    this.questionBody = questionBody;
+    this.prepareData();
+  },
+  beforeUpdate() {
+    this.prepareData();
+    const realAnswer = this.stuAnswer.replace(/##/g, "").trim() || null;
+    if (realAnswer !== this.examQuestion.stuAnswer) {
+      this.answerQuestion(this.examQuestion.id, realAnswer);
+    }
   },
   },
   methods: {
   methods: {
-    answerQuestion: function(examQuestionId, stuAnswer) {
-      this.stuAnswer = stuAnswer;
-      this.$http.put("/api/exam_question/" + examQuestionId, { stuAnswer });
+    ...mapMutations(["updateExamQuestionAnswer"]),
+    prepareData() {
+      const questionNumber = this.question.body.split(/_{5,}/).length - 1;
+
+      this.stuAnswer =
+        this.examQuestion.stuAnswer || "##".repeat(questionNumber - 1);
+
+      const answers = this.stuAnswer.split("##");
+      this.questionBody = this.question.body.replace(
+        /_{5,}/g,
+        () =>
+          "<span style='display: inline-block; min-width: 80px; border-bottom: 1px solid black; text-align: center'>" +
+          (answers.shift() || questionNumber - answers.length) +
+          "</span>"
+      );
     },
     },
-    resetQuestion: function(examQuestionId) {
-      this.stuAnswer = "##".repeat(this.questionNumber - 1);
-      setTimeout(this.inputAnswer, 100);
-      this.$http.put("/api/exam_question/" + examQuestionId, {
-        stuAnswer: null
+    async answerQuestion(examQuestionId, stuAnswer) {
+      await this.$http.put("/api/exam_question/" + examQuestionId, {
+        stuAnswer
       });
       });
+      this.updateExamQuestionAnswer({ examQuestionId, stuAnswer });
     },
     },
     inputAnswer: function() {
     inputAnswer: function() {
       const questionNumber = this.question.body.split(/_{5,}/).length - 1;
       const questionNumber = this.question.body.split(/_{5,}/).length - 1;
@@ -67,7 +70,6 @@ export default {
       document
       document
         .querySelectorAll(".option input")
         .querySelectorAll(".option input")
         .forEach(e => (ans += e.value + "##"));
         .forEach(e => (ans += e.value + "##"));
-      // console.log(ans);
       this.stuAnswer = ans.slice(0, -2);
       this.stuAnswer = ans.slice(0, -2);
       const answers = this.stuAnswer.split("##");
       const answers = this.stuAnswer.split("##");
       this.questionBody = this.question.body.replace(
       this.questionBody = this.question.body.replace(
@@ -79,11 +81,6 @@ export default {
       );
       );
     }
     }
   },
   },
-  watch: {
-    examQuestion: function() {
-      this.stuAnswer = this.examQuestion.stuAnswer || "";
-    }
-  },
   components: {
   components: {
     QuestionBody
     QuestionBody
   }
   }

+ 5 - 5
src/features/OnlineExam/Examing/OverallProgress.vue

@@ -20,14 +20,14 @@ export default {
     progressNum() {
     progressNum() {
       return (
       return (
         100 *
         100 *
-        this.examQuestionList.filter(q => q.stuAnswer).length /
-        this.examQuestionList.length
+        (this.examQuestionList.filter(q => q.stuAnswer !== null).length /
+          this.examQuestionList.length)
       );
       );
     },
     },
     progress: function() {
     progress: function() {
-      return `${this.examQuestionList.filter(q => q.stuAnswer).length} / ${
-        this.examQuestionList.length
-      }`;
+      return `${
+        this.examQuestionList.filter(q => q.stuAnswer !== null).length
+      } / ${this.examQuestionList.length}`;
     }
     }
   }
   }
 };
 };

+ 3 - 3
src/features/OnlineExam/Examing/QuestionFilters.vue

@@ -9,7 +9,7 @@
 
 
 <script>
 <script>
 import { createNamespacedHelpers } from "vuex";
 import { createNamespacedHelpers } from "vuex";
-const { mapState, mapMutations } = createNamespacedHelpers("examHomeModule");
+const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
 export default {
 export default {
   name: "QuestionFilters",
   name: "QuestionFilters",
@@ -29,13 +29,13 @@ export default {
       return this.examQuestionList.length;
       return this.examQuestionList.length;
     },
     },
     answered: function() {
     answered: function() {
-      return this.examQuestionList.filter(q => q.stuAnswer).length;
+      return this.examQuestionList.filter(q => q.stuAnswer !== null).length;
     },
     },
     signed: function() {
     signed: function() {
       return this.examQuestionList.filter(q => q.isSign).length;
       return this.examQuestionList.filter(q => q.isSign).length;
     },
     },
     unanswered: function() {
     unanswered: function() {
-      return this.examQuestionList.filter(q => !q.stuAnswer).length;
+      return this.examQuestionList.filter(q => q.stuAnswer === null).length;
     }
     }
   }
   }
 };
 };

+ 7 - 4
src/features/OnlineExam/Examing/QuestionNavView.vue

@@ -22,7 +22,7 @@
 
 
 <script>
 <script>
 import { createNamespacedHelpers } from "vuex";
 import { createNamespacedHelpers } from "vuex";
-const { mapState, mapMutations } = createNamespacedHelpers("examHomeModule");
+const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 
 export default {
 export default {
   name: "QuestionNavView",
   name: "QuestionNavView",
@@ -53,11 +53,14 @@ export default {
       let q = this.getQuestionNum(index1, index2);
       let q = this.getQuestionNum(index1, index2);
 
 
       if (this.questionFilterType === "ALL") return true;
       if (this.questionFilterType === "ALL") return true;
-      else if (this.questionFilterType === "ANSWERED" && q.stuAnswer) {
+      else if (this.questionFilterType === "ANSWERED" && q.stuAnswer !== null) {
         return true;
         return true;
       } else if (this.questionFilterType === "SIGNED" && q.isSign) {
       } else if (this.questionFilterType === "SIGNED" && q.isSign) {
         return true;
         return true;
-      } else if (this.questionFilterType === "UNANSWERED" && !q.stuAnswer) {
+      } else if (
+        this.questionFilterType === "UNANSWERED" &&
+        q.stuAnswer === null
+      ) {
         return true;
         return true;
       }
       }
       return false;
       return false;
@@ -70,7 +73,7 @@ export default {
         item: true,
         item: true,
         "current-question": isCurrentQuestion,
         "current-question": isCurrentQuestion,
         "star-question": this.getQuestionNum(index1, index2).isSign,
         "star-question": this.getQuestionNum(index1, index2).isSign,
-        "is-answered": this.getQuestionNum(index1, index2).stuAnswer != null
+        "is-answered": this.getQuestionNum(index1, index2).stuAnswer !== null
       };
       };
     }
     }
   },
   },

+ 1 - 1
src/features/OnlineExam/Examing/SingleQuestionView.vue

@@ -5,7 +5,7 @@
       <div class="stu-answer">{{stuAnswer}}</div>
       <div class="stu-answer">{{stuAnswer}}</div>
       <div class="score">({{question.questionScore}}分)</div>
       <div class="score">({{question.questionScore}}分)</div>
     </div>
     </div>
-    <div v-for="(option, index) in question.options" :key="option.id" class="option" @click="answerQuestion(examQuestion.id, optionName[index])">
+    <div v-for="(option, index) in question.options" :key="option.id" class="option" @click="() => answerQuestion(examQuestion.id, optionName[index])">
       <input type="radio" name="question" value="optionName[index]" :checked="stuAnswer === optionName[index]" />
       <input type="radio" name="question" value="optionName[index]" :checked="stuAnswer === optionName[index]" />
       <span style="padding: 0 10px;">{{optionName[index]}}: </span>
       <span style="padding: 0 10px;">{{optionName[index]}}: </span>
       <span class="question-options" v-html="option.content"></span>
       <span class="question-options" v-html="option.content"></span>

+ 28 - 2
src/store.js

@@ -5,7 +5,7 @@ Vue.use(Vuex);
 
 
 const examHomeModule = {
 const examHomeModule = {
   namespaced: true,
   namespaced: true,
-  state: { faceCheckModalOpen: false, questionFilterType: "ALL" },
+  state: { faceCheckModalOpen: false },
   mutations: {
   mutations: {
     toggleFaceCheckModal(state, open) {
     toggleFaceCheckModal(state, open) {
       if (open === undefined) {
       if (open === undefined) {
@@ -13,9 +13,34 @@ const examHomeModule = {
       } else {
       } else {
         state.faceCheckModalOpen = open;
         state.faceCheckModalOpen = open;
       }
       }
+    }
+  },
+  actions: {},
+  getters: {}
+};
+
+const examingHomeModule = {
+  namespaced: true,
+  state: {
+    exam: null,
+    paperStruct: null,
+    examQuestionList: null,
+    validQuestions: null,
+    questionFilterType: "ALL"
+  },
+  mutations: {
+    updateExamState(state, payload) {
+      state = Object.assign(state, payload);
     },
     },
     updateQuestionFilter(state, type) {
     updateQuestionFilter(state, type) {
       state.questionFilterType = type;
       state.questionFilterType = type;
+    },
+    updateExamQuestionAnswer(state, { examQuestionId, stuAnswer }) {
+      state.validQuestions.forEach(eq => {
+        if (eq.id == examQuestionId) {
+          eq.stuAnswer = stuAnswer;
+        }
+      });
     }
     }
   },
   },
   actions: {},
   actions: {},
@@ -35,6 +60,7 @@ export default new Vuex.Store({
   mutations: {},
   mutations: {},
   actions: {},
   actions: {},
   modules: {
   modules: {
-    examHomeModule: examHomeModule
+    examHomeModule,
+    examingHomeModule
   }
   }
 });
 });

+ 1 - 0
src/utils/axios.js

@@ -90,6 +90,7 @@ qmInstance.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"; //标
 
 
 qmInstance.get = cachingGet(qmInstance.get, [
 qmInstance.get = cachingGet(qmInstance.get, [
   /\/api\/exam_question\/question\/\?question_id/,
   /\/api\/exam_question\/question\/\?question_id/,
+  /\/api\/exam_question\/paper_struct\/\?exam_record_id=/,
   /\/api\/ecs_exam_work\/exam\/\d+$/
   /\/api\/ecs_exam_work\/exam\/\d+$/
 ]);
 ]);
 loadProgressBar(qmInstance);
 loadProgressBar(qmInstance);