Browse Source

add eslint "plugin:vue/recommended"

Michael Wang 5 years ago
parent
commit
2f1ef59e2a
48 changed files with 1120 additions and 993 deletions
  1. 2 1
      .eslintrc.js
  2. 12 12
      src/components/FaceRecognition/FaceRecognition.vue
  3. 17 17
      src/components/MainLayout/MainLayout.vue
  4. 10 10
      src/components/MainLayout/SiteMessagePopup.vue
  5. 70 70
      src/features/Login/Login.vue
  6. 3 3
      src/features/OfflineExam/OfflineExamHome.vue
  7. 16 11
      src/features/OfflineExam/OfflineExamList.vue
  8. 8 3
      src/features/OfflineExam/OfflineExamUpload.vue
  9. 111 111
      src/features/OnlineExam/CheckComputer.vue
  10. 4 4
      src/features/OnlineExam/Examing/ArrowNavView.vue
  11. 34 24
      src/features/OnlineExam/Examing/BooleanQuestionView.vue
  12. 15 15
      src/features/OnlineExam/Examing/ExamPaper.vue
  13. 11 10
      src/features/OnlineExam/Examing/ExamingEnd.vue
  14. 112 112
      src/features/OnlineExam/Examing/ExamingHome.vue
  15. 1 1
      src/features/OnlineExam/Examing/FaceId.vue
  16. 65 55
      src/features/OnlineExam/Examing/FillBlankQuestionView.vue
  17. 60 50
      src/features/OnlineExam/Examing/MultipleQuestionView.vue
  18. 8 4
      src/features/OnlineExam/Examing/OverallProgress.vue
  19. 18 18
      src/features/OnlineExam/Examing/QuestionAudio.vue
  20. 23 18
      src/features/OnlineExam/Examing/QuestionBody.vue
  21. 11 7
      src/features/OnlineExam/Examing/QuestionFilters.vue
  22. 6 1
      src/features/OnlineExam/Examing/QuestionIndex.vue
  23. 10 6
      src/features/OnlineExam/Examing/QuestionNavView.vue
  24. 30 22
      src/features/OnlineExam/Examing/QuestionView.vue
  25. 25 15
      src/features/OnlineExam/Examing/QuestionViewSingle.vue
  26. 13 13
      src/features/OnlineExam/Examing/RemainTime.vue
  27. 63 53
      src/features/OnlineExam/Examing/SingleQuestionView.vue
  28. 143 133
      src/features/OnlineExam/Examing/TextQuestionView.vue
  29. 60 50
      src/features/OnlineExam/Examing/UploadPhotos.vue
  30. 13 8
      src/features/OnlineExam/OnlineExamFaceCheckModal.vue
  31. 8 7
      src/features/OnlineExam/OnlineExamHome.vue
  32. 25 20
      src/features/OnlineExam/OnlineExamList.vue
  33. 8 8
      src/features/OnlineExam/OnlineExamOverview.vue
  34. 2 2
      src/features/OnlineExam/OnlineExamResultList.vue
  35. 5 5
      src/features/OnlineExam/PhoneVerifyForDD.vue
  36. 9 9
      src/features/OnlinePractice/OnlinePracticeHome.vue
  37. 10 6
      src/features/OnlinePractice/OnlinePracticeList.vue
  38. 13 13
      src/features/OnlinePractice/OnlinePracticeRecordDetail.vue
  39. 25 25
      src/features/OnlinePractice/OnlinePracticeRecordList.vue
  40. 3 3
      src/features/Password/Password.vue
  41. 9 9
      src/features/SiteMessage/SiteMessageDetail.vue
  42. 10 10
      src/features/SiteMessage/SiteMessageHome.vue
  43. 1 1
      src/views/NotFoundComponent.vue
  44. 1 1
      tests/vue/child.vue
  45. 7 7
      tests/vue/event.vue
  46. 2 2
      tests/vue/myinput.vue
  47. 4 4
      tests/vue/props.vue
  48. 4 4
      tests/vue/useMyinput.vue

+ 2 - 1
.eslintrc.js

@@ -3,12 +3,13 @@ module.exports = {
   env: {
     node: true,
   },
-  extends: ["plugin:vue/essential", "@vue/prettier"],
+  extends: ["plugin:vue/recommended", "@vue/prettier"],
   rules: {
     // "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
     "no-console": "off",
     "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
     "vue/no-parsing-error": [2, { "x-invalid-end-tag": false }],
+    "vue/no-v-html": "off",
   },
   parserOptions: {
     parser: "babel-eslint",

+ 12 - 12
src/components/FaceRecognition/FaceRecognition.vue

@@ -13,8 +13,8 @@
     >
       <button
         :class="['verify-button', disableSnap && 'disable-verify-button']"
-        @click="snap"
         :disabled="disableSnap"
+        @click="snap"
       >
         {{ msg }}
       </button>
@@ -30,17 +30,18 @@ const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 export default {
   name: "FaceRecognition",
-  data() {
-    return { disableSnap: true, msg: "开始识别" };
-  },
   props: {
-    width: String,
-    height: String,
+    width: { type: String, default: "400" },
+    height: { type: String, default: "300" },
     showRecognizeButton: Boolean,
     closeCamera: Boolean, // optional
   },
-  async mounted() {
-    this.openCamera();
+  data() {
+    return { disableSnap: true, msg: "开始识别" };
+  },
+  computed: {
+    ...globalMapState(["user"]),
+    ...mapState(["snapNow"]),
   },
   watch: {
     snapNow(val) {
@@ -76,6 +77,9 @@ export default {
       }
     },
   },
+  async mounted() {
+    this.openCamera();
+  },
   beforeDestroy() {
     if (this.$refs.video.srcObject) {
       this.$refs.video.srcObject.getTracks().forEach(function(track) {
@@ -481,10 +485,6 @@ export default {
       }
     },
   },
-  computed: {
-    ...globalMapState(["user"]),
-    ...mapState(["snapNow"]),
-  },
 };
 </script>
 

+ 17 - 17
src/components/MainLayout/MainLayout.vue

@@ -49,8 +49,8 @@
               <div>{{ user.orgName }}</div>
             </div>
             <div
-              style="grid-column: span 2; place-self: center; width: 100%; padding-top: 10px"
               v-if="!isEpcc"
+              style="grid-column: span 2; place-self: center; width: 100%; padding-top: 10px"
             >
               <i-button class="qm-primary-button" long @click="goChangePwd">
                 修改密码
@@ -61,14 +61,14 @@
       </Poptip>
       <span style="margin: auto 20px">|</span>
       <a
-        @click="() => this.logout('?LogoutReason=正常退出')"
         class="qm-primary-text"
         style="display: inline-block; margin-right: 20px; text-align: center;"
+        @click="() => logout('?LogoutReason=正常退出')"
         >退出登录</a
       >
     </header>
     <transition name="fade" appear>
-      <main class="content" :key="$route.path">
+      <main :key="$route.path" class="content">
         <slot></slot>
       </main>
     </transition>
@@ -117,22 +117,15 @@ import SiteMessagePopup from "./SiteMessagePopup.vue";
 
 export default {
   name: "MainLayout",
+  components: {
+    qrcode: VueQrcode,
+    SiteMessagePopup,
+  },
   data() {
     return {
       appDownloadUrl: "fetching...",
     };
   },
-  methods: {
-    goChangePwd() {
-      this.$router.push("/password");
-    },
-  },
-  async created() {
-    const r = await this.$http.get(
-      "/api/ecs_core/systemProperty/APP_DOWNLOAD_URL"
-    );
-    this.appDownloadUrl = r.data;
-  },
   computed: {
     ...mapState(["user", "siteMessages", "QECSConfig"]),
     messageUnread() {
@@ -162,9 +155,16 @@ export default {
         : require("./qm-logo.png");
     },
   },
-  components: {
-    qrcode: VueQrcode,
-    SiteMessagePopup,
+  async created() {
+    const r = await this.$http.get(
+      "/api/ecs_core/systemProperty/APP_DOWNLOAD_URL"
+    );
+    this.appDownloadUrl = r.data;
+  },
+  methods: {
+    goChangePwd() {
+      this.$router.push("/password");
+    },
   },
 };
 </script>

+ 10 - 10
src/components/MainLayout/SiteMessagePopup.vue

@@ -26,8 +26,8 @@
           详情 >>>
         </router-link>
         <span
-          @click="ignoreMessage"
           style="display: inline-block; margin-left: 20px; cursor: pointer;"
+          @click="ignoreMessage"
           >忽略</span
         >
       </div>
@@ -51,15 +51,6 @@ export default {
       ignoreMessageIds,
     };
   },
-  methods: {
-    ignoreMessage() {
-      this.ignoreMessageIds.push(this.unreadMessage.id);
-      window.sessionStorage.setItem(
-        "ignoreMessageIds",
-        JSON.stringify(this.ignoreMessageIds)
-      );
-    },
-  },
   computed: {
     ...mapState(["siteMessages"]),
     unreadMessage() {
@@ -80,6 +71,15 @@ export default {
       return text.slice(0, 90) + (text.length > 90 ? "..." : "");
     },
   },
+  methods: {
+    ignoreMessage() {
+      this.ignoreMessageIds.push(this.unreadMessage.id);
+      window.sessionStorage.setItem(
+        "ignoreMessageIds",
+        JSON.stringify(this.ignoreMessageIds)
+      );
+    },
+  },
 };
 </script>
 

+ 70 - 70
src/features/Login/Login.vue

@@ -3,9 +3,9 @@
     <header class="header">
       <div class="school-logo">
         <img
+          v-show="logoPath"
           class="logo-size"
-          v-show="this.logoPath"
-          :src="this.logoPath"
+          :src="logoPath"
           alt="school logo"
           style="background: linear-gradient(to bottom, #38f6f5 0%, #8efdf4 100%);"
           @load="e => (e.target.style = '')"
@@ -31,8 +31,8 @@
               loginType === 'STUDENT_CODE' && 'active-type',
               allowLoginType.length === 1 && 'single-login-type',
             ]"
-            @click="loginType = 'STUDENT_CODE'"
             style="border-top-left-radius: 6px"
+            @click="loginType = 'STUDENT_CODE'"
           >
             {{ QECSConfig.STUDENT_CODE_LOGIN_ALIAS }}
           </a>
@@ -44,8 +44,8 @@
               loginType !== 'STUDENT_CODE' && 'active-type',
               allowLoginType.length === 1 && 'single-login-type',
             ]"
-            @click="loginType = 'STUDENT_IDENTITY_NUMBER'"
             style="border-top-right-radius: 6px"
+            @click="loginType = 'STUDENT_IDENTITY_NUMBER'"
           >
             {{ QECSConfig.IDENTITY_NUMBER_LOGIN_ALIAS }}
           </a>
@@ -67,21 +67,21 @@
               style="margin-bottom:35px;height:42px"
             >
               <i-input
+                v-model="loginForm.accountValue"
                 type="text"
                 size="large"
-                v-model="loginForm.accountValue"
               >
-                <i-icon type="ios-person" slot="prepend"></i-icon>
+                <i-icon slot="prepend" type="ios-person"></i-icon>
               </i-input>
             </i-form-item>
             <i-form-item prop="password" style="margin-bottom:35px;height:42px">
               <i-input
+                v-model="loginForm.password"
                 type="password"
                 size="large"
-                v-model="loginForm.password"
                 @on-enter="login"
               >
-                <i-icon type="ios-lock" slot="prepend"></i-icon>
+                <i-icon slot="prepend" type="ios-lock"></i-icon>
               </i-input>
             </i-form-item>
             <i-form-item style="position: relative">
@@ -131,6 +131,9 @@ import nativeExe from "@/utils/nativeExe";
 
 export default {
   name: "Login",
+  components: {
+    DevTools,
+  },
   data() {
     return {
       // LOGIN_ID_DOMAINS: ["cugr.ecs.qmth.com.cn", "cugbr.ecs.qmth.com.cn"],
@@ -168,6 +171,65 @@ export default {
       VUE_APP_GIT_REPO_VERSION: process.env.VUE_APP_GIT_REPO_VERSION,
     };
   },
+  computed: {
+    logoPath() {
+      return this.QECSConfig.LOGO_FILE_URL
+        ? this.QECSConfig.LOGO_FILE_URL + "!/progressive/true/format/webp"
+        : "";
+    },
+    productName() {
+      return this.QECSConfig.OE_STUDENT_SYS_NAME || "远程教育网络考试";
+    },
+    allowLoginType() {
+      return (
+        (this.QECSConfig.LOGIN_TYPE && this.QECSConfig.LOGIN_TYPE.split(",")) ||
+        []
+      );
+    },
+    schoolDomain() {
+      const domain = this.domainInUrl;
+      if (!domain || !domain.includes("qmth.com.cn")) {
+        this.$Message.error({
+          content: "机构地址出错,请关闭程序后再登录。",
+          duration: 15,
+          closable: true,
+        });
+      }
+      return domain;
+    },
+    isEPCC() {
+      return this.schoolDomain === "iepcc-ps.ecs.qmth.com.cn";
+    },
+    usernameInputPlaceholder() {
+      if (this.loginType === "STUDENT_CODE") {
+        return "请输入学号";
+      } else {
+        return "请输入身份证号";
+      }
+    },
+    passwordInputPlaceholder() {
+      if (this.loginType === "STUDENT_CODE") {
+        return "初始密码为身份证号后6位";
+      } else {
+        return "初始密码为身份证号后6位";
+      }
+    },
+    disableLoginBtn() {
+      // console.log(
+      //   this.isElectron,
+      //   this.disableLoginBtnBecauseRemoteApp,
+      //   this.disableLoginBtnBecauseVCam,
+      //   this.disableLoginBtnBecauseNoRemoteAppChecker
+      // );
+      return (
+        (this.isElectron &&
+          (this.disableLoginBtnBecauseRemoteApp ||
+            this.disableLoginBtnBecauseVCam ||
+            this.disableLoginBtnBecauseNoRemoteAppChecker)) ||
+        this.disableLoginBtnBecauseRefreshServiceWorker
+      );
+    },
+  },
   async mounted() {
     // this.testServiceWorker();
 
@@ -707,68 +769,6 @@ export default {
       }
     },
   },
-  computed: {
-    logoPath() {
-      return this.QECSConfig.LOGO_FILE_URL
-        ? this.QECSConfig.LOGO_FILE_URL + "!/progressive/true/format/webp"
-        : "";
-    },
-    productName() {
-      return this.QECSConfig.OE_STUDENT_SYS_NAME || "远程教育网络考试";
-    },
-    allowLoginType() {
-      return (
-        (this.QECSConfig.LOGIN_TYPE && this.QECSConfig.LOGIN_TYPE.split(",")) ||
-        []
-      );
-    },
-    schoolDomain() {
-      const domain = this.domainInUrl;
-      if (!domain || !domain.includes("qmth.com.cn")) {
-        this.$Message.error({
-          content: "机构地址出错,请关闭程序后再登录。",
-          duration: 15,
-          closable: true,
-        });
-      }
-      return domain;
-    },
-    isEPCC() {
-      return this.schoolDomain === "iepcc-ps.ecs.qmth.com.cn";
-    },
-    usernameInputPlaceholder() {
-      if (this.loginType === "STUDENT_CODE") {
-        return "请输入学号";
-      } else {
-        return "请输入身份证号";
-      }
-    },
-    passwordInputPlaceholder() {
-      if (this.loginType === "STUDENT_CODE") {
-        return "初始密码为身份证号后6位";
-      } else {
-        return "初始密码为身份证号后6位";
-      }
-    },
-    disableLoginBtn() {
-      // console.log(
-      //   this.isElectron,
-      //   this.disableLoginBtnBecauseRemoteApp,
-      //   this.disableLoginBtnBecauseVCam,
-      //   this.disableLoginBtnBecauseNoRemoteAppChecker
-      // );
-      return (
-        (this.isElectron &&
-          (this.disableLoginBtnBecauseRemoteApp ||
-            this.disableLoginBtnBecauseVCam ||
-            this.disableLoginBtnBecauseNoRemoteAppChecker)) ||
-        this.disableLoginBtnBecauseRefreshServiceWorker
-      );
-    },
-  },
-  components: {
-    DevTools,
-  },
 };
 </script>
 

+ 3 - 3
src/features/OfflineExam/OfflineExamHome.vue

@@ -22,6 +22,9 @@ import EcsOfflineList from "./OfflineExamList.vue";
 
 export default {
   name: "OfflineExamHome",
+  components: {
+    "ecs-offline-list": EcsOfflineList,
+  },
   data() {
     return {
       courses: [],
@@ -59,9 +62,6 @@ export default {
       }));
     },
   },
-  components: {
-    "ecs-offline-list": EcsOfflineList,
-  },
 };
 </script>
 

+ 16 - 11
src/features/OfflineExam/OfflineExamList.vue

@@ -21,9 +21,9 @@
           <td>
             <div v-if="course.offlineFileUrl">
               <a
-                @click="() => downloadOfflineFile(course.offlineFileUrl)"
                 :href="course.offlineFileUrl"
                 download
+                @click="() => downloadOfflineFile(course.offlineFileUrl)"
               >
                 <i-icon type="ios-cloud-download"></i-icon>下载作答
               </a>
@@ -40,11 +40,11 @@
                   查看试卷
                 </i-button>
                 <a
-                  class="qm-primary-button"
                   v-show="!disableDownloadPaperBtn"
-                  @click="() => tempDisableBtnAndDownloadPaper(course)"
+                  class="qm-primary-button"
                   href="#"
                   download
+                  @click="() => tempDisableBtnAndDownloadPaper(course)"
                 >
                   下载试卷
                 </a>
@@ -109,13 +109,24 @@ import OfflineExamUpload from "./OfflineExamUpload.vue";
 
 export default {
   name: "EcsOfflineList",
+  components: {
+    "ecs-offline-exam-upload": OfflineExamUpload,
+  },
+  props: {
+    courses: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
   data() {
     return {
       disableDownloadPaperBtn: false,
     };
   },
-  props: {
-    courses: Array,
+  computed: {
+    ...globalMapState(["user", "timeDifference"]),
   },
   methods: {
     async enterExam(course) {
@@ -162,12 +173,6 @@ export default {
         this.user.token;
     },
   },
-  components: {
-    "ecs-offline-exam-upload": OfflineExamUpload,
-  },
-  computed: {
-    ...globalMapState(["user", "timeDifference"]),
-  },
 };
 </script>
 

+ 8 - 3
src/features/OfflineExam/OfflineExamUpload.vue

@@ -38,6 +38,14 @@
 <script>
 export default {
   name: "EcsOfflineUpload",
+  props: {
+    course: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       headers: {
@@ -51,9 +59,6 @@ export default {
       uploadFileAccept: "",
     };
   },
-  props: {
-    course: Object,
-  },
   async created() {
     const res = await this.$http.get(
       "/api/ecs_exam_work/exam/examOrgPropertyFromCache4StudentSession/" +

+ 111 - 111
src/features/OnlineExam/CheckComputer.vue

@@ -104,13 +104,13 @@
               <td>
                 <div v-if="time.clockRateStateResolved">
                   <Icon
-                    class="pass-check"
                     v-if="time.clockRateStatus"
+                    class="pass-check"
                     type="md-checkmark"
                   />
                   <Icon
-                    class="fail-cross"
                     v-else
+                    class="fail-cross"
                     title="请更换电脑"
                     type="md-close"
                   />
@@ -263,11 +263,11 @@
           <div style="margin-left: 30px; display: flex; ">
             <Button
               type="warning"
+              title="或者听不到声音"
               @click="
                 sound.playedStatusResolved = true;
                 sound.playedStatus = false;
               "
-              title="或者听不到声音"
             >
               不能播放声音
             </Button>
@@ -371,7 +371,7 @@
                   v-if="wechat.qrScanned"
                   style="margin-top: 30px; font-size: 30px;"
                 >
-                  {{ this.wechat.studentAnswer ? "已上传" : "已扫描" }}
+                  {{ wechat.studentAnswer ? "已上传" : "已扫描" }}
                   <Icon type="md-checkmark" />
                 </div>
               </div>
@@ -386,11 +386,11 @@
         >
           <span class="audio-answer-line-height">上传文件:</span>
           <audio
+            v-if="wechat.studentAnswer"
             class="audio-answer-line-height"
-            v-if="this.wechat.studentAnswer"
             controls
             controlsList="nodownload"
-            :src="this.wechat.studentAnswer"
+            :src="wechat.studentAnswer"
           />
           <span v-else class="audio-answer-line-height">未上传文件</span>
         </div>
@@ -398,22 +398,22 @@
         <div style=" margin-top: 30px; display: flex; margin-bottom: 30px;">
           <Button
             type="warning"
+            title="扫码不成功"
             @click="
               wechat.qrScannedResolved = true;
               wechat.qrScanned = false;
             "
-            title="扫码不成功"
           >
             不能正确扫描二维码
           </Button>
           <div style="width: 30px; height: 30px;"></div>
           <Button
             type="warning"
+            title="上传不成功"
             @click="
               wechat.uploadResolved = true;
               wechat.uploadStatus = false;
             "
-            title="上传不成功"
           >
             上传不成功
           </Button>
@@ -604,18 +604,18 @@
     </div>
 
     <div style="margin-top: 30px; text-align: center;">
-      <Button type="primary" @click="previous" :disabled="current === 0">
+      <Button type="primary" :disabled="current === 0" @click="previous">
         上一步
       </Button>
       <div style="width: 30px; height: 1px; display: inline-block;"></div>
-      <Button type="primary" @click="next" :disabled="current === 5">
+      <Button type="primary" :disabled="current === 5" @click="next">
         下一步
       </Button>
       <div style="width: 30px; height: 1px; display: inline-block;"></div>
       <Button
+        v-if="current === 5"
         type="primary"
         @click="() => this.$emit('on-close')"
-        v-if="current === 5"
       >
         进入考试
       </Button>
@@ -642,7 +642,11 @@ const CLOCK_RATE_TIMEOUT = 10;
 //   moment.duration(-new Date().getTimezoneOffset(), "minutes").format("hh:mm")
 // );
 export default {
-  name: "check-computer",
+  name: "CheckComputer",
+  components: {
+    qrcode: VueQrcode,
+    PulseLoader,
+  },
   data() {
     return {
       current: 0,
@@ -685,6 +689,101 @@ export default {
       },
     };
   },
+  computed: {
+    ...mapState([
+      "questionQrCode",
+      "questionQrCodeScanned",
+      "questionAnswerFileUrl",
+    ]),
+    timeCurrent() {
+      return moment(this.nowDate)
+        .utcOffset("+08:00")
+        .format("YYYY-MM-DD HH:mm:ssZZ");
+    },
+    step1Status() {
+      return this.network.downlinkStatus && this.network.rrtStatus;
+    },
+    step2StatusResolved() {
+      return this.time.clockRateStateResolved;
+    },
+    step2Status() {
+      return this.time.timeZoneStatus && this.time.clockRateStatus;
+    },
+    step3StatusResolved() {
+      return this.camera.identityResolved && this.camera.openCameraResolved;
+    },
+    step3Status() {
+      return this.camera.identityStatus && this.camera.openCameraStatus;
+    },
+    step4StatusResolved() {
+      return this.sound.downloadResolved && this.sound.playedStatusResolved;
+    },
+    step4Status() {
+      return this.sound.downloadStatus && this.sound.playedStatus;
+    },
+    step5StatusResolved() {
+      return this.wechat.qrScannedResolved && this.wechat.uploadResolved;
+    },
+    step5Status() {
+      return this.wechat.qrScanned && this.wechat.uploadStatus;
+    },
+  },
+  watch: {
+    questionQrCode(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      this.wechat.qrValue = value.qrCode;
+      this.wechat.examRecordDataId = decodeURIComponent(value.qrCode).match(
+        /&examRecordDataId=(\d+)&/
+      )[1];
+    },
+    questionQrCodeScanned() {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      this.wechat.qrScanned = true;
+      this.wechat.qrScannedResolved = true;
+    },
+    questionAnswerFileUrl(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      const examRecordDataId = this.wechat.examRecordDataId;
+      for (const q of value) {
+        if (!q.saved) {
+          let acknowledgeStatus = "CONFIRMED";
+
+          this.$http
+            .post(
+              "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
+              {
+                examRecordDataId,
+                filePath: q.fileUrl,
+                order: q.order,
+                acknowledgeStatus,
+              }
+            )
+            .then(() => {
+              this.wechat.studentAnswer = q.fileUrl;
+              this.wechat.uploadResolved = true;
+              this.wechat.uploadStatus = true;
+              q.saved = true;
+              if (acknowledgeStatus === "CONFIRMED")
+                this.$Message.info({
+                  content: "小程序作答已更新",
+                  duration: 5,
+                  closable: true,
+                });
+            })
+            .catch(() => {
+              this.$Message.error({
+                content: "更新小程序答案失败!",
+                duration: 15,
+                closable: true,
+              });
+            });
+        }
+      }
+    },
+  },
   async created() {
     this.getNowInterval = setInterval(() => {
       this.nowDate = Date.now();
@@ -820,105 +919,6 @@ export default {
       this.camera.openCameraResolved = true;
     },
   },
-  computed: {
-    ...mapState([
-      "questionQrCode",
-      "questionQrCodeScanned",
-      "questionAnswerFileUrl",
-    ]),
-    timeCurrent() {
-      return moment(this.nowDate)
-        .utcOffset("+08:00")
-        .format("YYYY-MM-DD HH:mm:ssZZ");
-    },
-    step1Status() {
-      return this.network.downlinkStatus && this.network.rrtStatus;
-    },
-    step2StatusResolved() {
-      return this.time.clockRateStateResolved;
-    },
-    step2Status() {
-      return this.time.timeZoneStatus && this.time.clockRateStatus;
-    },
-    step3StatusResolved() {
-      return this.camera.identityResolved && this.camera.openCameraResolved;
-    },
-    step3Status() {
-      return this.camera.identityStatus && this.camera.openCameraStatus;
-    },
-    step4StatusResolved() {
-      return this.sound.downloadResolved && this.sound.playedStatusResolved;
-    },
-    step4Status() {
-      return this.sound.downloadStatus && this.sound.playedStatus;
-    },
-    step5StatusResolved() {
-      return this.wechat.qrScannedResolved && this.wechat.uploadResolved;
-    },
-    step5Status() {
-      return this.wechat.qrScanned && this.wechat.uploadStatus;
-    },
-  },
-  watch: {
-    questionQrCode(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      this.wechat.qrValue = value.qrCode;
-      this.wechat.examRecordDataId = decodeURIComponent(value.qrCode).match(
-        /&examRecordDataId=(\d+)&/
-      )[1];
-    },
-    questionQrCodeScanned() {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      this.wechat.qrScanned = true;
-      this.wechat.qrScannedResolved = true;
-    },
-    questionAnswerFileUrl(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      const examRecordDataId = this.wechat.examRecordDataId;
-      for (const q of value) {
-        if (!q.saved) {
-          let acknowledgeStatus = "CONFIRMED";
-
-          this.$http
-            .post(
-              "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
-              {
-                examRecordDataId,
-                filePath: q.fileUrl,
-                order: q.order,
-                acknowledgeStatus,
-              }
-            )
-            .then(() => {
-              this.wechat.studentAnswer = q.fileUrl;
-              this.wechat.uploadResolved = true;
-              this.wechat.uploadStatus = true;
-              q.saved = true;
-              if (acknowledgeStatus === "CONFIRMED")
-                this.$Message.info({
-                  content: "小程序作答已更新",
-                  duration: 5,
-                  closable: true,
-                });
-            })
-            .catch(() => {
-              this.$Message.error({
-                content: "更新小程序答案失败!",
-                duration: 15,
-                closable: true,
-              });
-            });
-        }
-      }
-    },
-  },
-  components: {
-    qrcode: VueQrcode,
-    PulseLoader,
-  },
 };
 </script>
 

+ 4 - 4
src/features/OnlineExam/Examing/ArrowNavView.vue

@@ -39,13 +39,13 @@
 <script>
 export default {
   name: "ArrowNavView",
+  props: {
+    previousQuestionOrder: { type: Number, default: 0 },
+    nextQuestionOrder: { type: Number, default: 0 },
+  },
   data() {
     return {};
   },
-  props: {
-    previousQuestionOrder: Number,
-    nextQuestionOrder: Number,
-  },
   created: function() {
     window.addEventListener("keyup", this.keyup);
   },

+ 34 - 24
src/features/OnlineExam/Examing/BooleanQuestionView.vue

@@ -1,8 +1,8 @@
 <template>
   <div v-if="isSyncState" class="question-view">
     <question-body
-      :questionBody="question.body"
-      :examQuestion="examQuestion"
+      :question-body="question.body"
+      :exam-question="examQuestion"
     ></question-body>
     <div class="ops">
       <div class="stu-answer">
@@ -11,8 +11,8 @@
       <div class="score">({{ examQuestion.questionScore }}分)</div>
     </div>
     <div
-      @click="() => answerQuestion('true')"
       :class="[studentAnswer === 'true' && 'row-selected', 'option']"
+      @click="() => answerQuestion('true')"
     >
       <input
         type="radio"
@@ -24,8 +24,8 @@
       <span class="question-options">正确</span>
     </div>
     <div
-      @click="() => answerQuestion('false')"
       :class="[studentAnswer === 'false' && 'row-selected', 'option']"
+      @click="() => answerQuestion('false')"
     >
       <input
         type="radio"
@@ -66,14 +66,41 @@ const { mapMutations, mapGetters } = createNamespacedHelpers(
 
 export default {
   name: "BooleanQuestionView",
+  components: {
+    QuestionBody,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       isShowAnswer: false,
     };
   },
-  props: {
-    question: Object,
-    examQuestion: Object,
+  computed: {
+    isSyncState() {
+      return this.examQuestion.order == this.$route.params.order;
+    },
+    studentAnswer: vm => vm.examQuestion.studentAnswer,
+  },
+  watch: {
+    // examQuestion: function() {
+    //   this.studentAnswer = this.examQuestion.studentAnswer;
+    // },
+    // question(question) {
+    //   this.questionBody = question.body;
+    // }
   },
   created: function() {
     window.addEventListener("keyup", this.keyup);
@@ -119,23 +146,6 @@ export default {
       this.isShowAnswer = !this.isShowAnswer;
     },
   },
-  watch: {
-    // examQuestion: function() {
-    //   this.studentAnswer = this.examQuestion.studentAnswer;
-    // },
-    // question(question) {
-    //   this.questionBody = question.body;
-    // }
-  },
-  computed: {
-    isSyncState() {
-      return this.examQuestion.order == this.$route.params.order;
-    },
-    studentAnswer: vm => vm.examQuestion.studentAnswer,
-  },
-  components: {
-    QuestionBody,
-  },
 };
 </script>
 

+ 15 - 15
src/features/OnlineExam/Examing/ExamPaper.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="exam" class="container" :key="exam.id">
+  <div v-if="exam" :key="exam.id" class="container">
     <div class="main">
       <div v-for="(qG, index) in questionGroupList" :key="index">
         <div>
@@ -14,20 +14,20 @@
         >
           <div v-html="restoreAudio(questionWrapper.body)"></div>
           <div
-            v-for="(questionUnit, index) in questionWrapper.questionUnitList"
-            :key="index"
+            v-for="(questionUnit, index2) in questionWrapper.questionUnitList"
+            :key="index2"
           >
             <div class="flex">
-              <div>{{ questionWrapper.eqs[index].order }}、</div>
+              <div>{{ questionWrapper.eqs[index2].order }}、</div>
               <div v-html="restoreAudio(questionUnit.body)"></div>
             </div>
             <div
-              v-for="(optionOrder, index) in questionWrapper.eqs[index]
+              v-for="(optionOrder, index3) in questionWrapper.eqs[index2]
                 .optionPermutation"
-              :key="index"
+              :key="index3"
             >
               <div class="flex">
-                <div>{{ indexToABCD(index) }}、</div>
+                <div>{{ indexToABCD(index3) }}、</div>
                 <div
                   v-html="
                     restoreAudio(
@@ -38,14 +38,14 @@
               </div>
             </div>
 
-            <div class="flex" v-if="practiceType !== 'NO_ANSWER'">
+            <div v-if="practiceType !== 'NO_ANSWER'" class="flex">
               正确答案:
               <div
                 v-html="
                   rightAnswerToABCD(
                     questionUnit.questionType,
                     questionUnit.rightAnswer,
-                    questionWrapper.eqs[index].optionPermutation
+                    questionWrapper.eqs[index2].optionPermutation
                   )
                 "
               ></div>
@@ -56,8 +56,8 @@
                 v-html="
                   rightAnswerToABCD(
                     questionUnit.questionType,
-                    questionWrapper.eqs[index].studentAnswer,
-                    questionWrapper.eqs[index].optionPermutation,
+                    questionWrapper.eqs[index2].studentAnswer,
+                    questionWrapper.eqs[index2].optionPermutation,
                     questionUnit.rightAnswer
                   )
                 "
@@ -76,6 +76,10 @@ const optionName = "ABCDEFGHIJ".split("");
 
 export default {
   name: "ExamPaper",
+  props: {
+    examId: { type: Number, default: 0 },
+    examRecordDataId: { type: Number, default: 0 },
+  },
   data() {
     return {
       exam: null,
@@ -84,10 +88,6 @@ export default {
       practiceType: null,
     };
   },
-  props: {
-    examId: Number,
-    examRecordDataId: Number,
-  },
   async created() {
     await this.initData();
   },

+ 11 - 10
src/features/OnlineExam/Examing/ExamingEnd.vue

@@ -1,43 +1,43 @@
 <template>
-  <div class="container" id="exam-end" v-if="afterExamRemark !== null">
+  <div v-if="afterExamRemark !== null" id="exam-end" class="container">
     <div class="instructions">
       <h1 class="">考试已结束</h1>
       <div><img class="user-avatar" :src="user.photoPath" alt="无底照" /></div>
       <div
-        class="qm-big-text score-text"
         v-if="!examResult && getResultTimes <= 10"
+        class="qm-big-text score-text"
       >
         考试结果计算中...
       </div>
       <div
+        v-if="!examResult && getResultTimes > 10"
         class="qm-big-text score-text"
         style="font-size: 20px;"
-        v-if="!examResult && getResultTimes > 10"
       >
         请稍后在待考列表中查看客观题得分。
       </div>
       <div
-        class="qm-big-text score-text"
         v-if="showObjectScore && (examResult && !examResult.isWarn)"
+        class="qm-big-text score-text"
       >
         客观题得分:
         <span style="color: red">{{ examResult.objectiveScore }}</span>
       </div>
       <div
-        class="qm-big-text score-text"
         v-if="
           exam &&
             exam.examType !== 'ONLINE' &&
             showObjectScore &&
             !examResult.isWarn
         "
+        class="qm-big-text score-text"
       >
         客观题正确率:
         <span style="color: red">{{ examResult.objectiveAccuracy }}%</span>
       </div>
       <div
-        class="qm-big-text score-text"
         v-if="examResult && examResult.isWarn"
+        class="qm-big-text score-text"
       >
         客观题得分: 成绩待审核
       </div>
@@ -86,6 +86,10 @@ export default {
       getResultTimes: 0,
     };
   },
+  computed: {
+    ...globalMapState(["user"]),
+    // ...mapState(["exam"])
+  },
   async mounted() {
     window._hmt.push(["_trackEvent", "考试结束页面", "进入页面"]);
     const examRecordDataId = this.$route.params.examRecordDataId;
@@ -194,10 +198,7 @@ export default {
       });
     });
   },
-  computed: {
-    ...globalMapState(["user"]),
-    // ...mapState(["exam"])
-  },
+
   methods: {
     ...mapMutations(["updateExamState"]),
   },

+ 112 - 112
src/features/OnlineExam/Examing/ExamingHome.vue

@@ -1,5 +1,5 @@
 <template>
-  <div v-if="exam && examQuestion()" class="container" :key="exam.id">
+  <div v-if="exam && examQuestion()" :key="exam.id" class="container">
     <div class="header">
       <RemainTime></RemainTime>
       <OverallProgress :exam-question-list="examQuestionList"></OverallProgress>
@@ -10,23 +10,23 @@
       <QuestionFilters :exam-question-list="examQuestionList"></QuestionFilters>
       <i-button class="qm-primary-button" @click="submitPaper">交卷</i-button>
     </div>
-    <div class="main" id="examing-home-question">
+    <div id="examing-home-question" class="main">
       <QuestionView :exam-question="examQuestion()"></QuestionView>
       <ArrowNavView
-        :previousQuestionOrder="previousQuestionOrder"
-        :nextQuestionOrder="nextQuestionOrder"
+        :previous-question-order="previousQuestionOrder"
+        :next-question-order="nextQuestionOrder"
       ></ArrowNavView>
     </div>
     <div :class="['side', 'side-row-size']">
       <div :class="['question-nav', !faceEnable && 'question-nav-long']">
-        <QuestionNavView :paperStruct="paperStruct" />
+        <QuestionNavView :paper-struct="paperStruct" />
       </div>
       <div v-if="faceEnable" class="camera">
         <FaceRecognition
           v-if="faceEnable"
           width="400"
           height="300"
-          :showRecognizeButton="false"
+          :show-recognize-button="false"
         />
       </div>
     </div>
@@ -44,7 +44,7 @@
   </div>
   <div v-else>
     正在等待数据返回...
-    <i-button class="qm-primary-button" v-if="timeouted" @click="reloadPage">
+    <i-button v-if="timeouted" class="qm-primary-button" @click="reloadPage">
       重试
     </i-button>
   </div>
@@ -73,6 +73,17 @@ if (process.env.VUE_APP_CAN_UPLOAD_PHOTOS_FOR_TEST === "true") {
 }
 export default {
   name: "ExamingHome",
+  components: {
+    RemainTime,
+    OverallProgress,
+    QuestionFilters,
+    QuestionView,
+    ArrowNavView,
+    QuestionNavView,
+    FaceRecognition,
+    FaceId,
+    FaceTracking,
+  },
   data() {
     return {
       showFaceId: false,
@@ -81,6 +92,100 @@ export default {
       PRODUCTION: process.env.NODE_ENV === "production",
     };
   },
+  computed: {
+    ...mapState([
+      "exam",
+      "paperStruct",
+      "examQuestionList",
+      "snapNow",
+      "snapProcessingCount",
+      "shouldSubmitPaper",
+      "remainTime",
+      "questionAnswerFileUrl",
+      "uploadModalVisible",
+    ]),
+    previousQuestionOrder: vm => {
+      if (vm.examQuestion().order > 1) {
+        return vm.examQuestion().order - 1;
+      } else {
+        return null;
+      }
+    },
+    nextQuestionOrder: vm => {
+      if (vm.examQuestion().order < vm.examQuestionList.length) {
+        return vm.examQuestion().order + 1;
+      } else {
+        return null;
+      }
+    },
+  },
+  watch: {
+    $route: function() {
+      this.examQuestion();
+    },
+    shouldSubmitPaper() {
+      this.realSubmitPaper();
+    },
+    questionAnswerFileUrl(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      const examRecordDataId = this.$route.params.examRecordDataId;
+      const that = this;
+      for (const q of value) {
+        if (!q.saved) {
+          let acknowledgeStatus = "CONFIRMED";
+
+          // 目前只针对音频题有丢弃的可能
+          if (
+            q.transferFileType === "PIC" &&
+            (q.order != this.$route.params.order || !this.uploadModalVisible)
+          ) {
+            acknowledgeStatus = "DISCARDED";
+          }
+          this.$http
+            .post(
+              "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
+              {
+                examRecordDataId,
+                filePath: q.fileUrl,
+                order: q.order,
+                acknowledgeStatus,
+              }
+            )
+            .then(() => {
+              if (q.transferFileType === "AUDIO") {
+                that.updateExamQuestion({
+                  order: q.order,
+                  studentAnswer: q.fileUrl,
+                });
+              } else if (
+                acknowledgeStatus === "CONFIRMED" &&
+                q.transferFileType === "PIC"
+              ) {
+                that.updatePicture(q);
+              }
+              q.saved = true;
+              if (acknowledgeStatus === "CONFIRMED")
+                this.$Message.info({
+                  content: "小程序作答已更新",
+                  duration: 5,
+                  closable: true,
+                });
+            })
+            .catch(() => {
+              this.$Message.error({
+                content: "更新小程序答案失败!",
+                duration: 15,
+                closable: true,
+              });
+            });
+        }
+      }
+    },
+    // examQuestionList(val, oldVal) {
+    //   // console.log(val, oldVal);
+    // }
+  },
   async created() {
     this.timeoutTimeout = setTimeout(() => (this.timeouted = true), 30 * 1000);
 
@@ -607,111 +712,6 @@ export default {
       window.location.reload();
     },
   },
-  computed: {
-    ...mapState([
-      "exam",
-      "paperStruct",
-      "examQuestionList",
-      "snapNow",
-      "snapProcessingCount",
-      "shouldSubmitPaper",
-      "remainTime",
-      "questionAnswerFileUrl",
-      "uploadModalVisible",
-    ]),
-    previousQuestionOrder: vm => {
-      if (vm.examQuestion().order > 1) {
-        return vm.examQuestion().order - 1;
-      } else {
-        return null;
-      }
-    },
-    nextQuestionOrder: vm => {
-      if (vm.examQuestion().order < vm.examQuestionList.length) {
-        return vm.examQuestion().order + 1;
-      } else {
-        return null;
-      }
-    },
-  },
-  watch: {
-    $route: function() {
-      this.examQuestion();
-    },
-    shouldSubmitPaper() {
-      this.realSubmitPaper();
-    },
-    questionAnswerFileUrl(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      const examRecordDataId = this.$route.params.examRecordDataId;
-      const that = this;
-      for (const q of value) {
-        if (!q.saved) {
-          let acknowledgeStatus = "CONFIRMED";
-
-          // 目前只针对音频题有丢弃的可能
-          if (
-            q.transferFileType === "PIC" &&
-            (q.order != this.$route.params.order || !this.uploadModalVisible)
-          ) {
-            acknowledgeStatus = "DISCARDED";
-          }
-          this.$http
-            .post(
-              "/api/ecs_oe_student/examControl/saveUploadedFileAcknowledgeStatus",
-              {
-                examRecordDataId,
-                filePath: q.fileUrl,
-                order: q.order,
-                acknowledgeStatus,
-              }
-            )
-            .then(() => {
-              if (q.transferFileType === "AUDIO") {
-                that.updateExamQuestion({
-                  order: q.order,
-                  studentAnswer: q.fileUrl,
-                });
-              } else if (
-                acknowledgeStatus === "CONFIRMED" &&
-                q.transferFileType === "PIC"
-              ) {
-                that.updatePicture(q);
-              }
-              q.saved = true;
-              if (acknowledgeStatus === "CONFIRMED")
-                this.$Message.info({
-                  content: "小程序作答已更新",
-                  duration: 5,
-                  closable: true,
-                });
-            })
-            .catch(() => {
-              this.$Message.error({
-                content: "更新小程序答案失败!",
-                duration: 15,
-                closable: true,
-              });
-            });
-        }
-      }
-    },
-    // examQuestionList(val, oldVal) {
-    //   // console.log(val, oldVal);
-    // }
-  },
-  components: {
-    RemainTime,
-    OverallProgress,
-    QuestionFilters,
-    QuestionView,
-    ArrowNavView,
-    QuestionNavView,
-    FaceRecognition,
-    FaceId,
-    FaceTracking,
-  },
 };
 </script>
 

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

@@ -38,8 +38,8 @@
         </button>
       </div>
       <webview
-        id="myFrame"
         v-show="showIframe"
+        id="myFrame"
         :preload="electronDir + 'manipulateFaceID.js'"
         style="position:absolute;width:100%;height:710px;"
       ></webview>

+ 65 - 55
src/features/OnlineExam/Examing/FillBlankQuestionView.vue

@@ -1,8 +1,8 @@
 <template>
   <div v-if="isSyncState" class="question-view">
     <question-body
-      :questionBody="questionBody"
-      :examQuestion="examQuestion"
+      :question-body="questionBody"
+      :exam-question="examQuestion"
     ></question-body>
     <div class="ops">
       <div class="score">({{ examQuestion.questionScore }}分)</div>
@@ -37,7 +37,7 @@
       </span>
       <div v-if="examShouldShowAnswer() && isShowAnswer">
         正确答案:
-        <div v-html="rightAnswerTransform" class="right-answer-section"></div>
+        <div class="right-answer-section" v-html="rightAnswerTransform"></div>
       </div>
     </div>
   </div>
@@ -60,6 +60,23 @@ const { mapMutations, mapGetters } = createNamespacedHelpers(
 
 export default {
   name: "FillBlankQuestionView",
+  components: {
+    QuestionBody,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       studentAnswer: "",
@@ -67,9 +84,51 @@ export default {
       isShowAnswer: false,
     };
   },
-  props: {
-    question: Object,
-    examQuestion: Object,
+  computed: {
+    isSyncState() {
+      return this.examQuestion.order == this.$route.params.order;
+    },
+    rightAnswerTransform() {
+      // if (
+      //   this.question.rightAnswer &&
+      //   !this.question.rightAnswer.join("").includes("##")
+      // ) {
+      //   return this.question.rightAnswer;
+      // }
+      return (
+        this.question.rightAnswer &&
+        this.question.rightAnswer
+          .join("")
+          .split("##")
+          .map((v, i) => `${i + 1}、${v}<br>`)
+          .join("")
+      );
+    },
+  },
+  watch: {
+    // examQuestion: function() {
+    //   this.prepareData();
+    // },
+    question(question) {
+      this.questionBody = question.body;
+      this.prepareData();
+    },
+    studentAnswer() {
+      let realAnswer = null;
+      if (this.studentAnswer && this.studentAnswer.replace(/##/g, "").trim()) {
+        // 如果有实际内容
+        realAnswer = this.studentAnswer
+          .replace(/</gi, "&lt;")
+          .replace(/>/gi, "&gt;");
+        // console.log("answers to store:", realAnswer);
+      }
+      if (realAnswer !== this.examQuestion.studentAnswer) {
+        this.updateExamQuestion({
+          order: this.examQuestion.order,
+          studentAnswer: realAnswer,
+        });
+      }
+    },
   },
   created() {
     this.prepareData();
@@ -127,55 +186,6 @@ export default {
       this.isShowAnswer = !this.isShowAnswer;
     },
   },
-  watch: {
-    // examQuestion: function() {
-    //   this.prepareData();
-    // },
-    question(question) {
-      this.questionBody = question.body;
-      this.prepareData();
-    },
-    studentAnswer() {
-      let realAnswer = null;
-      if (this.studentAnswer && this.studentAnswer.replace(/##/g, "").trim()) {
-        // 如果有实际内容
-        realAnswer = this.studentAnswer
-          .replace(/</gi, "&lt;")
-          .replace(/>/gi, "&gt;");
-        // console.log("answers to store:", realAnswer);
-      }
-      if (realAnswer !== this.examQuestion.studentAnswer) {
-        this.updateExamQuestion({
-          order: this.examQuestion.order,
-          studentAnswer: realAnswer,
-        });
-      }
-    },
-  },
-  computed: {
-    isSyncState() {
-      return this.examQuestion.order == this.$route.params.order;
-    },
-    rightAnswerTransform() {
-      // if (
-      //   this.question.rightAnswer &&
-      //   !this.question.rightAnswer.join("").includes("##")
-      // ) {
-      //   return this.question.rightAnswer;
-      // }
-      return (
-        this.question.rightAnswer &&
-        this.question.rightAnswer
-          .join("")
-          .split("##")
-          .map((v, i) => `${i + 1}、${v}<br>`)
-          .join("")
-      );
-    },
-  },
-  components: {
-    QuestionBody,
-  },
 };
 </script>
 

+ 60 - 50
src/features/OnlineExam/Examing/MultipleQuestionView.vue

@@ -4,8 +4,8 @@
     class="question-view"
   >
     <question-body
-      :questionBody="question.body"
-      :examQuestion="examQuestion"
+      :question-body="question.body"
+      :exam-question="examQuestion"
     ></question-body>
     <div class="ops">
       <div class="stu-answer">{{ oldIndexToABCD }}</div>
@@ -29,10 +29,10 @@
           style="margin-top: 4px"
         />
         <span style="padding: 0 10px;">{{ optionName[index] }}: </span>
-        <div class="question-options" v-if="option.value">
+        <div v-if="option.value" class="question-options">
           <question-body
-            :questionBody="option.value.body"
-            :examQuestion="examQuestion"
+            :question-body="option.value.body"
+            :exam-question="examQuestion"
           ></question-body>
         </div>
       </div>
@@ -68,6 +68,23 @@ const { mapMutations, mapGetters } = createNamespacedHelpers(
 const optionName = "ABCDEFGHIJ".split("");
 export default {
   name: "MultipleQuestionView",
+  components: {
+    QuestionBody,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       questionBody: this.question.body,
@@ -76,9 +93,44 @@ export default {
       isShowAnswer: false,
     };
   },
-  props: {
-    question: Object,
-    examQuestion: Object,
+  computed: {
+    isSyncState() {
+      return this.examQuestion.order == this.$route.params.order;
+    },
+    newQuestionOptions() {
+      return this.question.questionOptionList.map((v, i) => {
+        return {
+          value: this.question.questionOptionList[
+            this.examQuestion.optionPermutation[i]
+          ],
+          oldIndex: "" + this.examQuestion.optionPermutation[i],
+          name: optionName[i],
+        };
+      });
+    },
+    oldIndexToABCD() {
+      return (
+        this.examQuestion.studentAnswer &&
+        this.newQuestionOptions
+          .filter(v => this.examQuestion.studentAnswer.includes(v.oldIndex))
+          .map(v => v.name)
+          .join("")
+      );
+    },
+    rightAnswerTransform() {
+      return (
+        this.question.rightAnswer &&
+        this.newQuestionOptions
+          .filter(v => this.question.rightAnswer.includes(v.oldIndex))
+          .map(v => v.name)
+          .join("")
+      );
+    },
+  },
+  watch: {
+    examQuestion: function() {
+      this.studentAnswer = this.examQuestion.studentAnswer || "";
+    },
   },
   created: function() {
     window.addEventListener("keyup", this.keyup);
@@ -156,48 +208,6 @@ export default {
       this.isShowAnswer = !this.isShowAnswer;
     },
   },
-  watch: {
-    examQuestion: function() {
-      this.studentAnswer = this.examQuestion.studentAnswer || "";
-    },
-  },
-  computed: {
-    isSyncState() {
-      return this.examQuestion.order == this.$route.params.order;
-    },
-    newQuestionOptions() {
-      return this.question.questionOptionList.map((v, i) => {
-        return {
-          value: this.question.questionOptionList[
-            this.examQuestion.optionPermutation[i]
-          ],
-          oldIndex: "" + this.examQuestion.optionPermutation[i],
-          name: optionName[i],
-        };
-      });
-    },
-    oldIndexToABCD() {
-      return (
-        this.examQuestion.studentAnswer &&
-        this.newQuestionOptions
-          .filter(v => this.examQuestion.studentAnswer.includes(v.oldIndex))
-          .map(v => v.name)
-          .join("")
-      );
-    },
-    rightAnswerTransform() {
-      return (
-        this.question.rightAnswer &&
-        this.newQuestionOptions
-          .filter(v => this.question.rightAnswer.includes(v.oldIndex))
-          .map(v => v.name)
-          .join("")
-      );
-    },
-  },
-  components: {
-    QuestionBody,
-  },
 };
 </script>
 

+ 8 - 4
src/features/OnlineExam/Examing/OverallProgress.vue

@@ -15,13 +15,17 @@
 <script>
 export default {
   name: "OverallProgress",
+  props: {
+    examQuestionList: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
   data() {
     return {};
   },
-  props: {
-    examQuestionList: Array,
-  },
-  async mounted() {},
   computed: {
     progressNum() {
       return (

+ 18 - 18
src/features/OnlineExam/Examing/QuestionAudio.vue

@@ -2,14 +2,14 @@
   <span>
     <audio
       v-show="shouldShowAudio"
+      :key="src"
       controls
       preload="auto"
       controlsList="nodownload"
-      :key="src"
       :name="name"
       :src="src"
-      v-on="$listeners"
       style="width: 290px"
+      v-on="$listeners"
     ></audio>
 
     <span v-if="!shouldShowAudio" style="color: blueviolet;">
@@ -37,6 +37,22 @@ export default {
       downloadPercent: 0,
     };
   },
+  computed: {
+    shouldShowAudio() {
+      let chromeVersion = window.navigator.userAgent
+        .split(" ")
+        .find(v => v.startsWith("Chrome/"));
+      if (chromeVersion) {
+        chromeVersion = +chromeVersion.replace("Chrome/", "").split(".")[0];
+      }
+      return (
+        chromeVersion < 76 ||
+        this.done ||
+        !navigator.serviceWorker ||
+        !navigator.serviceWorker.controller
+      );
+    },
+  },
   async created() {
     this.cacheInterval = setInterval(() => {
       if (!this.done) {
@@ -60,21 +76,5 @@ export default {
     clearInterval(this.cacheInterval);
     navigator.serviceWorker.removeEventListener("message", this.handleMSG);
   },
-  computed: {
-    shouldShowAudio() {
-      let chromeVersion = window.navigator.userAgent
-        .split(" ")
-        .find(v => v.startsWith("Chrome/"));
-      if (chromeVersion) {
-        chromeVersion = +chromeVersion.replace("Chrome/", "").split(".")[0];
-      }
-      return (
-        chromeVersion < 76 ||
-        this.done ||
-        !navigator.serviceWorker ||
-        !navigator.serviceWorker.controller
-      );
-    },
-  },
 };
 </script>

+ 23 - 18
src/features/OnlineExam/Examing/QuestionBody.vue

@@ -1,8 +1,8 @@
 <template>
   <div
     v-if="questionDetail"
-    class="question-body"
     :key="examQuestion.questionId"
+    class="question-body"
   >
     <div v-html="questionDetail.text"></div>
     <!-- <div v-html="questionDetail.audio"></div> -->
@@ -42,6 +42,18 @@ const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 export default {
   name: "QuestionBody",
+  components: {
+    QuestionAudio,
+  },
+  props: {
+    questionBody: { type: String, default: "" },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       questionDetail: null,
@@ -49,9 +61,16 @@ export default {
       audioInPlay: new Set(),
     };
   },
-  props: {
-    questionBody: String,
-    examQuestion: Object,
+  computed: {
+    ...mapState(["allAudioPlayTimes"]),
+  },
+  watch: {
+    questionBody() {
+      this.parseQuestion();
+    },
+    examQuestion() {
+      this.parseQuestion();
+    },
   },
   mounted() {
     // console.log(
@@ -239,20 +258,6 @@ export default {
       );
     },
   },
-  computed: {
-    ...mapState(["allAudioPlayTimes"]),
-  },
-  watch: {
-    questionBody() {
-      this.parseQuestion();
-    },
-    examQuestion() {
-      this.parseQuestion();
-    },
-  },
-  components: {
-    QuestionAudio,
-  },
 };
 </script>
 

+ 11 - 7
src/features/OnlineExam/Examing/QuestionFilters.vue

@@ -33,15 +33,16 @@ const { mapState, mapMutations } = createNamespacedHelpers("examingHomeModule");
 
 export default {
   name: "QuestionFilters",
-  data() {
-    return {};
-  },
   props: {
-    examQuestionList: Array,
+    examQuestionList: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
   },
-  async mounted() {},
-  methods: {
-    ...mapMutations(["updateQuestionFilter"]),
+  data() {
+    return {};
   },
   computed: {
     ...mapState(["questionFilterType"]),
@@ -58,6 +59,9 @@ export default {
       return this.examQuestionList.filter(q => q.studentAnswer === null).length;
     },
   },
+  methods: {
+    ...mapMutations(["updateQuestionFilter"]),
+  },
 };
 </script>
 

+ 6 - 1
src/features/OnlineExam/Examing/QuestionIndex.vue

@@ -11,7 +11,12 @@
 export default {
   name: "QuestionIndex",
   props: {
-    examQuestion: Object,
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
   },
   computed: {
     sectionChinese() {

+ 10 - 6
src/features/OnlineExam/Examing/QuestionNavView.vue

@@ -38,13 +38,20 @@ const { mapState } = createNamespacedHelpers("examingHomeModule");
 
 export default {
   name: "QuestionNavView",
+  props: {
+    paperStruct: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {};
   },
-  props: {
-    paperStruct: Object,
+  computed: {
+    ...mapState(["questionFilterType", "examQuestionList"]),
   },
-  mounted() {},
   methods: {
     getQuestionNum: function(section, index) {
       if (
@@ -109,9 +116,6 @@ export default {
       return this.examQuestionList.filter(q => q.mainNumber === section + 1);
     },
   },
-  computed: {
-    ...mapState(["questionFilterType", "examQuestionList"]),
-  },
 };
 </script>
 

+ 30 - 22
src/features/OnlineExam/Examing/QuestionView.vue

@@ -9,7 +9,7 @@
       />
       <question-index
         v-if="examQuestion.getQuestionContent"
-        :examQuestion="examQuestion"
+        :exam-question="examQuestion"
       />
     </div>
     <split-pane
@@ -21,23 +21,26 @@
       <template slot="paneL">
         <div v-if="parentQuestionBody" class="question-view parent-question">
           <question-body
-            :questionBody="parentQuestionBody"
-            :examQuestion="examQuestion"
-            style="margin-bottom: 20px"
             :key="examQuestion.questionId"
+            :question-body="parentQuestionBody"
+            :exam-question="examQuestion"
+            style="margin-bottom: 20px"
           ></question-body>
           <!-- <div class="hr" /> -->
         </div>
       </template>
       <template slot="paneR">
-        <QuestionViewSingle :question="question" :examQuestion="examQuestion" />
+        <QuestionViewSingle
+          :question="question"
+          :exam-question="examQuestion"
+        />
       </template>
     </split-pane>
 
     <QuestionViewSingle
       v-if="!parentQuestionBody"
       :question="question"
-      :examQuestion="examQuestion"
+      :exam-question="examQuestion"
     />
 
     <div v-if="!examQuestion.getQuestionContent">试题获取中...</div>
@@ -54,6 +57,20 @@ import splitPane from "vue-splitpane";
 
 export default {
   name: "QuestionView",
+  components: {
+    QuestionIndex,
+    QuestionBody,
+    "split-pane": splitPane,
+    QuestionViewSingle,
+  },
+  props: {
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       parentQuestionBody: null,
@@ -61,8 +78,13 @@ export default {
       parentPaneHeight: 50,
     };
   },
-  props: {
-    examQuestion: Object,
+  computed: {
+    ...mapState(["examQuestionList"]),
+  },
+  watch: {
+    $route: function() {
+      this.updateQuestion();
+    },
   },
   created() {
     this.updateQuestion();
@@ -210,20 +232,6 @@ export default {
       });
     },
   },
-  computed: {
-    ...mapState(["examQuestionList"]),
-  },
-  watch: {
-    $route: function() {
-      this.updateQuestion();
-    },
-  },
-  components: {
-    QuestionIndex,
-    QuestionBody,
-    "split-pane": splitPane,
-    QuestionViewSingle,
-  },
 };
 </script>
 

+ 25 - 15
src/features/OnlineExam/Examing/QuestionViewSingle.vue

@@ -2,8 +2,8 @@
   <transition name="fade">
     <div
       v-show="question && examQuestion && examQuestion.getQuestionContent"
-      class="question-view"
       :key="examQuestion.order"
+      class="question-view"
     >
       <div style="margin-bottom: -45px;">{{ examQuestion.groupOrder }}、</div>
       <template
@@ -14,9 +14,9 @@
         "
       >
         <single-question-view
-          :question="question"
-          :examQuestion="examQuestion"
           :key="examQuestion.order"
+          :question="question"
+          :exam-question="examQuestion"
         />
       </template>
       <template
@@ -27,9 +27,9 @@
         "
       >
         <multiple-question-view
-          :question="question"
-          :examQuestion="examQuestion"
           :key="examQuestion.order"
+          :question="question"
+          :exam-question="examQuestion"
         />
       </template>
       <template
@@ -40,9 +40,9 @@
         "
       >
         <boolean-question-view
-          :question="question"
-          :examQuestion="examQuestion"
           :key="examQuestion.order"
+          :question="question"
+          :exam-question="examQuestion"
         />
       </template>
       <template
@@ -53,9 +53,9 @@
         "
       >
         <fill-blank-question-view
-          :question="question"
-          :examQuestion="examQuestion"
           :key="examQuestion.order"
+          :question="question"
+          :exam-question="examQuestion"
         />
       </template>
       <template
@@ -66,9 +66,9 @@
         "
       >
         <text-question-view
-          :question="question"
-          :examQuestion="examQuestion"
           :key="examQuestion.order"
+          :question="question"
+          :exam-question="examQuestion"
         />
       </template>
     </div>
@@ -84,10 +84,6 @@ import TextQuestionView from "./TextQuestionView";
 
 export default {
   name: "QuestionViewSingle",
-  props: {
-    question: Object,
-    examQuestion: Object,
-  },
   components: {
     SingleQuestionView,
     MultipleQuestionView,
@@ -95,6 +91,20 @@ export default {
     FillBlankQuestionView,
     TextQuestionView,
   },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
 };
 </script>
 

+ 13 - 13
src/features/OnlineExam/Examing/RemainTime.vue

@@ -14,6 +14,19 @@ export default {
       remainTime: null,
     };
   },
+  computed: {
+    remainTimeFormatted: function() {
+      return moment.utc(this.remainTime).format("HH:mm:ss");
+    },
+  },
+  watch: {
+    remainTime(val) {
+      if (val !== null && val === 0) {
+        this.setShouldSubmitPaper();
+      }
+      this.updateRemainTime(val);
+    },
+  },
   async mounted() {
     this.heartbeatErrorNum = 0;
     this.getRemainTimeFromServer();
@@ -116,18 +129,5 @@ export default {
       clearTimeout(this.retryHeartbeatTimeout);
     },
   },
-  computed: {
-    remainTimeFormatted: function() {
-      return moment.utc(this.remainTime).format("HH:mm:ss");
-    },
-  },
-  watch: {
-    remainTime(val) {
-      if (val !== null && val === 0) {
-        this.setShouldSubmitPaper();
-      }
-      this.updateRemainTime(val);
-    },
-  },
 };
 </script>

+ 63 - 53
src/features/OnlineExam/Examing/SingleQuestionView.vue

@@ -4,8 +4,8 @@
     class="question-view"
   >
     <question-body
-      :questionBody="questionBody"
-      :examQuestion="examQuestion"
+      :question-body="questionBody"
+      :exam-question="examQuestion"
     ></question-body>
     <div class="ops">
       <div class="stu-answer">{{ oldIndexToABCD }}</div>
@@ -35,10 +35,10 @@
           />
         </div>
         <span style="padding: 0 10px;">{{ optionName[index] }}: </span>
-        <div class="question-options" v-if="option.value">
+        <div v-if="option.value" class="question-options">
           <question-body
-            :questionBody="option.value.body"
-            :examQuestion="examQuestion"
+            :question-body="option.value.body"
+            :exam-question="examQuestion"
           ></question-body>
         </div>
       </div>
@@ -74,6 +74,23 @@ const { mapMutations, mapGetters } = createNamespacedHelpers(
 const optionName = "ABCDEFGHIJ".split("");
 export default {
   name: "SingleQuestionView",
+  components: {
+    QuestionBody,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       questionBody: this.question.body,
@@ -82,9 +99,47 @@ export default {
       isShowAnswer: false,
     };
   },
-  props: {
-    question: Object,
-    examQuestion: Object,
+  computed: {
+    isSyncState() {
+      return this.examQuestion.order == this.$route.params.order;
+    },
+    newQuestionOptions() {
+      return this.question.questionOptionList.map((v, i) => {
+        return {
+          value: this.question.questionOptionList[
+            this.examQuestion.optionPermutation[i]
+          ],
+          oldIndex: "" + this.examQuestion.optionPermutation[i],
+          name: optionName[i],
+        };
+      });
+    },
+    oldIndexToABCD() {
+      return (
+        this.examQuestion.studentAnswer &&
+        this.newQuestionOptions
+          .filter(v => this.examQuestion.studentAnswer.includes(v.oldIndex))
+          .map(v => v.name)
+          .join("")
+      );
+    },
+    rightAnswerTransform() {
+      return (
+        this.question.rightAnswer &&
+        this.newQuestionOptions
+          .filter(v => this.question.rightAnswer.includes(v.oldIndex))
+          .map(v => v.name)
+          .join("")
+      );
+    },
+  },
+  watch: {
+    examQuestion: function(examQuestion) {
+      this.studentAnswer = examQuestion.studentAnswer;
+    },
+    question(question) {
+      this.questionBody = question.body;
+    },
   },
   created: function() {
     window.addEventListener("keyup", this.keyup);
@@ -135,51 +190,6 @@ export default {
       this.isShowAnswer = !this.isShowAnswer;
     },
   },
-  watch: {
-    examQuestion: function(examQuestion) {
-      this.studentAnswer = examQuestion.studentAnswer;
-    },
-    question(question) {
-      this.questionBody = question.body;
-    },
-  },
-  computed: {
-    isSyncState() {
-      return this.examQuestion.order == this.$route.params.order;
-    },
-    newQuestionOptions() {
-      return this.question.questionOptionList.map((v, i) => {
-        return {
-          value: this.question.questionOptionList[
-            this.examQuestion.optionPermutation[i]
-          ],
-          oldIndex: "" + this.examQuestion.optionPermutation[i],
-          name: optionName[i],
-        };
-      });
-    },
-    oldIndexToABCD() {
-      return (
-        this.examQuestion.studentAnswer &&
-        this.newQuestionOptions
-          .filter(v => this.examQuestion.studentAnswer.includes(v.oldIndex))
-          .map(v => v.name)
-          .join("")
-      );
-    },
-    rightAnswerTransform() {
-      return (
-        this.question.rightAnswer &&
-        this.newQuestionOptions
-          .filter(v => this.question.rightAnswer.includes(v.oldIndex))
-          .map(v => v.name)
-          .join("")
-      );
-    },
-  },
-  components: {
-    QuestionBody,
-  },
 };
 </script>
 

+ 143 - 133
src/features/OnlineExam/Examing/TextQuestionView.vue

@@ -1,8 +1,8 @@
 <template>
   <div v-if="isSyncState" :key="answerDivKey" class="question-view">
     <question-body
-      :questionBody="question.body"
-      :examQuestion="examQuestion"
+      :question-body="question.body"
+      :exam-question="examQuestion"
     ></question-body>
     <div class="ops">
       <div class="score">({{ examQuestion.questionScore }}分)</div>
@@ -53,12 +53,12 @@
           ref="answerDiv"
           ondragstart="return false"
           ondrop="return false"
-          @keydown="disableCtrl"
           :contenteditable="true"
-          v-html="originalStudentAnswer"
+          class="stu-answer"
+          @keydown="disableCtrl"
           @input="$event => textInput($event)"
           @blur="$event => textInput($event)"
-          class="stu-answer"
+          v-html="originalStudentAnswer"
         ></div>
         <div
           style="margin-top: -25px; margin-bottom: 25px; width: 100%; max-width: 500px;"
@@ -84,7 +84,7 @@
                 }},并上传文件。
               </div>
               <div v-if="qrScanned" style="margin-top: 30px; font-size: 30px;">
-                {{ this.examQuestion.studentAnswer ? "已上传" : "已扫描" }}
+                {{ examQuestion.studentAnswer ? "已上传" : "已扫描" }}
                 <Icon type="md-checkmark" />
               </div>
             </div>
@@ -98,11 +98,11 @@
         >
           <span class="audio-answer-line-height">答案:</span>
           <audio
+            v-if="examQuestion.studentAnswer"
             class="audio-answer-line-height"
-            v-if="this.examQuestion.studentAnswer"
             controls
             controlsList="nodownload"
-            :src="this.examQuestion.studentAnswer"
+            :src="examQuestion.studentAnswer"
           />
           <span v-else class="audio-answer-line-height">未上传文件</span>
         </div>
@@ -110,17 +110,17 @@
 
       <div v-if="canAttachPhotos" style="padding-top: 1px;">
         <UploadPhotos
-          :defaultList="
+          :default-list="
             photoAnswers.map(v => {
               return v;
             })
           "
+          :qr-value="qrValue"
+          :exam-question="examQuestion"
+          style="margin-top: 20px; width: 350px;"
           @on-photo-added="photoAdded"
           @on-photo-removed="photoRemoved"
           @on-photos-reseted="photosReseted"
-          :qrValue="qrValue"
-          :examQuestion="examQuestion"
-          style="margin-top: 20px; width: 350px;"
         />
       </div>
       <div class="reset" style="padding-top: 20px;">
@@ -164,6 +164,25 @@ if (process.env.VUE_APP_CAN_UPLOAD_PHOTOS_FOR_TEST === "true") {
 }
 export default {
   name: "TextQuestionView",
+  components: {
+    QuestionBody,
+    UploadPhotos,
+    qrcode: VueQrcode,
+  },
+  props: {
+    question: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       studentAnswer: this.examQuestion.studentAnswer,
@@ -174,9 +193,118 @@ export default {
       qrScanned: false,
     };
   },
-  props: {
-    question: Object,
-    examQuestion: Object,
+  computed: {
+    ...mapState(["questionQrCode", "questionQrCodeScanned"]),
+    isSyncState() {
+      return this.examQuestion.order == this.$route.params.order;
+    },
+    rightAnswerTransform() {
+      return this.question.rightAnswer.join("");
+    },
+    answerWordCount() {
+      if (this.studentAnswer && this.$refs.answerDiv) {
+        return this.$refs.answerDiv.innerText.replace(/\s+/g, "").length;
+      } else {
+        const ele = document.createElement("div");
+        ele.innerHTML = this.studentAnswer;
+
+        return ele.innerText.replace(/\s+/g, "").length;
+      }
+    },
+    isAudioAnswerType() {
+      return this.examQuestion.answerType === "SINGLE_AUDIO";
+    },
+    shouldFetchQrCode() {
+      const shouldFetch =
+        this.examQuestion.answerType === "SINGLE_AUDIO" ||
+        DOMAINS_CAN_UPLOAD_PHOTOS.includes(this.$store.state.user.schoolDomain);
+
+      return shouldFetch;
+    },
+    canAttachPhotos() {
+      return (
+        (this.$store.state.user.schoolDomain === "csu.ecs.qmth.com.cn" ||
+          DOMAINS_CAN_UPLOAD_PHOTOS.includes(
+            this.$store.state.user.schoolDomain
+          )) &&
+        !this.isAudioAnswerType
+      );
+    },
+    photoAnswers: {
+      get() {
+        if (!this.studentAnswer) return [];
+        const ele = document.createElement("div");
+        ele.innerHTML = this.studentAnswer;
+        const imgs = ele.querySelectorAll(".photo-answer");
+        // if()
+        return [...imgs].map(e => e.src.replace("!/both/200x200", ""));
+      },
+      set(pSrcs) {
+        let imageStr = pSrcs.map(
+          v =>
+            `<a href='${v}' target='_blank' ><img class='photo-answer' src='${v +
+              "!/both/200x200"}' /></a>`
+        );
+        const ele = document.createElement("div");
+        ele.innerHTML = this.studentAnswer || "";
+        const pEle = ele.querySelectorAll(".photo-answers-block");
+        if (pEle) [...pEle].forEach(v => v.remove());
+        // console.log(ele.innerHTML);
+
+        if (!ele.innerHTML && pSrcs.length === 0) {
+          // 完全为空则重置答案
+          this.studentAnswer = null;
+          // 更新answerDiv的内容
+          this.originalStudentAnswer = null;
+        } else {
+          this.studentAnswer =
+            ele.innerHTML +
+            `<div class='photo-answers-block'>${imageStr.join("")}</div>`;
+          // 更新answerDiv的内容
+          this.originalStudentAnswer = this.studentAnswer;
+        }
+      },
+    },
+  },
+  watch: {
+    examQuestion() {
+      // console.log(this.examQuestion.studentAnswer);
+      this.studentAnswer = this.examQuestion.studentAnswer;
+    },
+    questionQrCode(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      if (value.order === this.examQuestion.order) {
+        this.qrValue = value.qrCode;
+      }
+    },
+    questionQrCodeScanned(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      if (value.order === this.examQuestion.order) {
+        this.qrScanned = true;
+      }
+    },
+    studentAnswer() {
+      let realAnswer = null;
+      if (this.studentAnswer) {
+        // 如果有实际内容
+        realAnswer = this.studentAnswer
+          .replace(/<sup><\/sup>/gi, "")
+          .replace(/<sub><\/sub>/gi, "")
+          .replace(/<script/gi, "&lt;script")
+          .replace(/script>/gi, "script&gt;");
+        // .replace(/</gi, "&lt;")
+        // .replace(/>/gi, "&gt;")
+        // .replace(/&lt;div&gt;&lt;br&gt;&lt;\/div&gt;/gi, "<div><br></div>");
+      }
+      if (realAnswer !== this.examQuestion.studentAnswer) {
+        this.updateExamQuestion({
+          order: this.examQuestion.order,
+          studentAnswer: realAnswer,
+        });
+      }
+    },
   },
   created() {
     this.fetchQRCode();
@@ -278,124 +406,6 @@ export default {
       this.photoAnswers = [...urls];
     },
   },
-  watch: {
-    examQuestion() {
-      // console.log(this.examQuestion.studentAnswer);
-      this.studentAnswer = this.examQuestion.studentAnswer;
-    },
-    questionQrCode(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      if (value.order === this.examQuestion.order) {
-        this.qrValue = value.qrCode;
-      }
-    },
-    questionQrCodeScanned(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      if (value.order === this.examQuestion.order) {
-        this.qrScanned = true;
-      }
-    },
-    studentAnswer() {
-      let realAnswer = null;
-      if (this.studentAnswer) {
-        // 如果有实际内容
-        realAnswer = this.studentAnswer
-          .replace(/<sup><\/sup>/gi, "")
-          .replace(/<sub><\/sub>/gi, "")
-          .replace(/<script/gi, "&lt;script")
-          .replace(/script>/gi, "script&gt;");
-        // .replace(/</gi, "&lt;")
-        // .replace(/>/gi, "&gt;")
-        // .replace(/&lt;div&gt;&lt;br&gt;&lt;\/div&gt;/gi, "<div><br></div>");
-      }
-      if (realAnswer !== this.examQuestion.studentAnswer) {
-        this.updateExamQuestion({
-          order: this.examQuestion.order,
-          studentAnswer: realAnswer,
-        });
-      }
-    },
-  },
-  computed: {
-    ...mapState(["questionQrCode", "questionQrCodeScanned"]),
-    isSyncState() {
-      return this.examQuestion.order == this.$route.params.order;
-    },
-    rightAnswerTransform() {
-      return this.question.rightAnswer.join("");
-    },
-    answerWordCount() {
-      if (this.studentAnswer && this.$refs.answerDiv) {
-        return this.$refs.answerDiv.innerText.replace(/\s+/g, "").length;
-      } else {
-        const ele = document.createElement("div");
-        ele.innerHTML = this.studentAnswer;
-
-        return ele.innerText.replace(/\s+/g, "").length;
-      }
-    },
-    isAudioAnswerType() {
-      return this.examQuestion.answerType === "SINGLE_AUDIO";
-    },
-    shouldFetchQrCode() {
-      const shouldFetch =
-        this.examQuestion.answerType === "SINGLE_AUDIO" ||
-        DOMAINS_CAN_UPLOAD_PHOTOS.includes(this.$store.state.user.schoolDomain);
-
-      return shouldFetch;
-    },
-    canAttachPhotos() {
-      return (
-        (this.$store.state.user.schoolDomain === "csu.ecs.qmth.com.cn" ||
-          DOMAINS_CAN_UPLOAD_PHOTOS.includes(
-            this.$store.state.user.schoolDomain
-          )) &&
-        !this.isAudioAnswerType
-      );
-    },
-    photoAnswers: {
-      get() {
-        if (!this.studentAnswer) return [];
-        const ele = document.createElement("div");
-        ele.innerHTML = this.studentAnswer;
-        const imgs = ele.querySelectorAll(".photo-answer");
-        // if()
-        return [...imgs].map(e => e.src.replace("!/both/200x200", ""));
-      },
-      set(pSrcs) {
-        let imageStr = pSrcs.map(
-          v =>
-            `<a href='${v}' target='_blank' ><img class='photo-answer' src='${v +
-              "!/both/200x200"}' /></a>`
-        );
-        const ele = document.createElement("div");
-        ele.innerHTML = this.studentAnswer || "";
-        const pEle = ele.querySelectorAll(".photo-answers-block");
-        if (pEle) [...pEle].forEach(v => v.remove());
-        // console.log(ele.innerHTML);
-
-        if (!ele.innerHTML && pSrcs.length === 0) {
-          // 完全为空则重置答案
-          this.studentAnswer = null;
-          // 更新answerDiv的内容
-          this.originalStudentAnswer = null;
-        } else {
-          this.studentAnswer =
-            ele.innerHTML +
-            `<div class='photo-answers-block'>${imageStr.join("")}</div>`;
-          // 更新answerDiv的内容
-          this.originalStudentAnswer = this.studentAnswer;
-        }
-      },
-    },
-  },
-  components: {
-    QuestionBody,
-    UploadPhotos,
-    qrcode: VueQrcode,
-  },
 };
 </script>
 

+ 60 - 50
src/features/OnlineExam/Examing/UploadPhotos.vue

@@ -3,13 +3,13 @@
     <draggable
       v-model="uploadList"
       :move="dragMove"
-      @update="uploadListSort"
       class="upload-images"
+      @update="uploadListSort"
     >
       <div
-        class="demo-upload-list"
         v-for="(item, index) in uploadList"
         :key="item"
+        class="demo-upload-list"
       >
         <img :src="item" />
         <div class="demo-upload-list-cover">
@@ -41,8 +41,8 @@
     </div>
 
     <Modal
-      title="上传图片"
       v-model="uploadModalVisible"
+      title="上传图片"
       mask
       footer-hide
       :mask-closable="false"
@@ -75,14 +75,14 @@
 
         <draggable
           v-model="totalList"
+          class="total-images"
           @start="drag = true"
           @end="drag = false"
-          class="total-images"
         >
           <div
-            class="demo-upload-list"
             v-for="(item, index) in totalList"
             :key="item"
+            class="demo-upload-list"
           >
             <img :src="item" />
             <div class="demo-upload-list-cover">
@@ -124,11 +124,25 @@ import Viewer from "viewerjs";
 
 export default {
   name: "UploadPhotos",
+  components: {
+    qrcode: VueQrcode,
+    draggable,
+  },
   // props: ["defaultList", "qrValue"],
   props: {
-    examQuestion: Object,
-    defaultList: Array,
-    qrValue: String,
+    examQuestion: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+    defaultList: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+    qrValue: { type: String, default: "" },
   },
   data() {
     return {
@@ -146,6 +160,44 @@ export default {
       drag: false,
     };
   },
+  computed: {
+    ...mapState(["questionQrCodeScanned", "pictureAnswer"]),
+  },
+  watch: {
+    defaultList: {
+      handler: function update() {
+        this.uploadList = this.defaultList;
+      },
+      deep: true,
+    },
+    questionQrCodeScanned(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      // console.log("watch", value);
+      if (value.order === this.examQuestion.order) {
+        this.qrScanned = true;
+      }
+    },
+    pictureAnswer(value) {
+      // console.log(this.examQuestion.studentAnswer);
+      console.log("watch", value);
+      this.uploaded = true;
+      if (value.order === this.examQuestion.order) {
+        this.totalList.push(...[...new Set(value.fileUrl.split(","))]);
+      }
+    },
+    uploadModalVisible(val) {
+      this.updateUploadModalVisible(val);
+    },
+  },
+  mounted() {
+    this.uploadList = this.defaultList;
+    // this.uploadList.push(
+    //   ...[
+    //     "https://o5wwk8baw.qnssl.com/a42bdcc1178e62b4694c830f028db5c0/avatar",
+    //     "https://o5wwk8baw.qnssl.com/bc7521e033abdd1e92222d733590f104/avatar",
+    //   ]
+    // );
+  },
   methods: {
     ...mapMutations(["updateUploadModalVisible"]),
     handleView(imagesClass, index) {
@@ -241,48 +293,6 @@ export default {
       return !e.dragged.className.includes("plus");
     },
   },
-  mounted() {
-    this.uploadList = this.defaultList;
-    // this.uploadList.push(
-    //   ...[
-    //     "https://o5wwk8baw.qnssl.com/a42bdcc1178e62b4694c830f028db5c0/avatar",
-    //     "https://o5wwk8baw.qnssl.com/bc7521e033abdd1e92222d733590f104/avatar",
-    //   ]
-    // );
-  },
-  computed: {
-    ...mapState(["questionQrCodeScanned", "pictureAnswer"]),
-  },
-  watch: {
-    defaultList: {
-      handler: function update() {
-        this.uploadList = this.defaultList;
-      },
-      deep: true,
-    },
-    questionQrCodeScanned(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      // console.log("watch", value);
-      if (value.order === this.examQuestion.order) {
-        this.qrScanned = true;
-      }
-    },
-    pictureAnswer(value) {
-      // console.log(this.examQuestion.studentAnswer);
-      console.log("watch", value);
-      this.uploaded = true;
-      if (value.order === this.examQuestion.order) {
-        this.totalList.push(...[...new Set(value.fileUrl.split(","))]);
-      }
-    },
-    uploadModalVisible(val) {
-      this.updateUploadModalVisible(val);
-    },
-  },
-  components: {
-    qrcode: VueQrcode,
-    draggable,
-  },
 };
 </script>
 <style scoped>

+ 13 - 8
src/features/OnlineExam/OnlineExamFaceCheckModal.vue

@@ -48,7 +48,7 @@
           v-if="faceCheckModalOpen"
           width="400"
           height="300"
-          :showRecognizeButton="true"
+          :show-recognize-button="true"
           :close-camera="closeCamera"
           @on-recognize-result="getFaceRecognitionResult"
         >
@@ -75,16 +75,24 @@ const { mapState, mapMutations } = createNamespacedHelpers("examHomeModule");
 
 export default {
   name: "OnlineExamFaceCheckModal",
+  components: {
+    FaceRecognition,
+  },
+  props: {
+    open: { type: Boolean, default: false },
+    course: {
+      type: Object,
+      default() {
+        return {};
+      },
+    },
+  },
   data() {
     return {
       userPhoto: this.$store.state.user.photoPath,
       closeCamera: false,
     };
   },
-  props: {
-    open: Boolean,
-    course: Object,
-  },
   computed: {
     ...mapState(["faceCheckModalOpen"]),
   },
@@ -153,9 +161,6 @@ export default {
       );
     },
   },
-  components: {
-    FaceRecognition,
-  },
 };
 </script>
 

+ 8 - 7
src/features/OnlineExam/OnlineExamHome.vue

@@ -23,6 +23,10 @@ import { mapMutations, mapState } from "vuex";
 
 export default {
   name: "OnlineExamHome",
+  components: {
+    "ecs-online-list": EcsOnlineList,
+    PhoneVerifyForDD,
+  },
   data() {
     return {
       previousUrl: "",
@@ -30,6 +34,9 @@ export default {
       courses: [],
     };
   },
+  computed: {
+    ...mapState(["user", "siteMessagesTimeStamp"]),
+  },
   beforeRouteEnter(to, from, next) {
     next(vm => {
       vm.previousUrl = from.path;
@@ -128,16 +135,10 @@ export default {
   beforeDestroy() {
     clearInterval(this.interval);
   },
-  computed: {
-    ...mapState(["user", "siteMessagesTimeStamp"]),
-  },
+
   methods: {
     ...mapMutations(["updateSiteMessages"]),
   },
-  components: {
-    "ecs-online-list": EcsOnlineList,
-    PhoneVerifyForDD,
-  },
 };
 </script>
 

+ 25 - 20
src/features/OnlineExam/OnlineExamList.vue

@@ -38,10 +38,10 @@
               <i-poptip
                 v-if="!isEpcc"
                 :trigger="course.isObjScoreView ? 'hover' : 'click'"
-                @on-popper-show="cid = course.courseId"
-                @on-popper-hide="cid = null"
                 placement="left"
                 class="online-exam-list-override-poptip"
+                @on-popper-show="cid = course.courseId"
+                @on-popper-hide="cid = null"
               >
                 <i-button
                   class="qm-primary-button qm-primary-button-padding-fix"
@@ -52,8 +52,8 @@
                 </i-button>
                 <ecs-online-exam-result-list
                   slot="content"
-                  :popperShow="cid === course.courseId"
-                  :examStudentId="course.examStudentId"
+                  :popper-show="cid === course.courseId"
+                  :exam-student-id="course.examStudentId"
                 ></ecs-online-exam-result-list>
               </i-poptip>
             </div>
@@ -62,15 +62,15 @@
       </tbody>
     </table>
 
-    <Spin size="large" fix v-if="spinShow">{{ processingMessage }}</Spin>
+    <Spin v-if="spinShow" size="large" fix>{{ processingMessage }}</Spin>
     <OnlineExamFaceCheckModal
       :open="faceCheckModalOpen"
       :course="selectedCourse"
     ></OnlineExamFaceCheckModal>
 
     <Modal
-      v-model="shouldShowCheckEnvModal"
       ref="checkEnvModal"
+      v-model="shouldShowCheckEnvModal"
       title="环境检测"
       footer-hide
       width="800"
@@ -96,6 +96,19 @@ import CheckComputer from "./CheckComputer";
 
 export default {
   name: "EcsOnlineList",
+  components: {
+    "ecs-online-exam-result-list": OnlineExamResultList,
+    OnlineExamFaceCheckModal,
+    CheckComputer,
+  },
+  props: {
+    courses: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
   data() {
     return {
       now: new Date(),
@@ -106,8 +119,12 @@ export default {
       shouldShowCheckEnvModal: false,
     };
   },
-  props: {
-    courses: Array,
+  computed: {
+    ...globalMapState(["user", "timeDifference"]),
+    ...mapState(["faceCheckModalOpen"]),
+    isEpcc() {
+      return this.user.schoolDomain === "iepcc-ps.ecs.qmth.com.cn";
+    },
   },
   created() {
     this.getNow();
@@ -316,13 +333,6 @@ export default {
       this.enterExam(this.selectedCourse, true);
     },
   },
-  computed: {
-    ...globalMapState(["user", "timeDifference"]),
-    ...mapState(["faceCheckModalOpen"]),
-    isEpcc() {
-      return this.user.schoolDomain === "iepcc-ps.ecs.qmth.com.cn";
-    },
-  },
   // watch: {
   //   courses(value) {
   //     if (value.length > 0) {
@@ -338,11 +348,6 @@ export default {
   //     }
   //   },
   // },
-  components: {
-    "ecs-online-exam-result-list": OnlineExamResultList,
-    OnlineExamFaceCheckModal,
-    CheckComputer,
-  },
 };
 </script>
 

+ 8 - 8
src/features/OnlineExam/OnlineExamOverview.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="container" id="exam-overview" v-if="startInfo && paperStruct">
+  <div v-if="startInfo && paperStruct" id="exam-overview" class="container">
     <div class="instructions">
       <h1 class="">考试说明</h1>
       <div style="text-align: left;  padding-bottom: 20px">
@@ -9,8 +9,8 @@
       <i-button
         class="qm-primary-button"
         :disabled="isForceRead"
-        @click="goToPaper"
         style="display: inline-block; width: 100%;"
+        @click="goToPaper"
       >
         {{ isForceRead ? "强制阅读" : "开始答题" }} (倒计时:{{
           remainTimeFormatted
@@ -25,10 +25,10 @@
       <br />
       <ul class="list-group">
         <li
-          class="list-group-item"
           v-for="(questionsGroup, index) in paperStruct.defaultPaper
             .questionGroupList"
           :key="questionsGroup.gruopName"
+          class="list-group-item"
         >
           {{ index + 1 }}、{{ questionsGroup.groupName }}
           <small class="pull-right">
@@ -64,6 +64,11 @@ export default {
       isForceRead: true,
     };
   },
+  computed: {
+    remainTimeFormatted: function() {
+      return moment.utc(this.remainTime * 1000).format("HH:mm:ss");
+    },
+  },
   async mounted() {
     window._hmt.push(["_trackEvent", "在线考试概览页面", "进入页面"]);
     this.intervalId = setInterval(() => {
@@ -114,11 +119,6 @@ export default {
   beforeDestroy() {
     clearInterval(this.intervalId);
   },
-  computed: {
-    remainTimeFormatted: function() {
-      return moment.utc(this.remainTime * 1000).format("HH:mm:ss");
-    },
-  },
   methods: {
     goToPaper: function() {
       this.$router.replace(

+ 2 - 2
src/features/OnlineExam/OnlineExamResultList.vue

@@ -31,8 +31,8 @@
 export default {
   name: "EcsOnlineExamResultList",
   props: {
-    examStudentId: Number,
-    popperShow: Boolean,
+    examStudentId: { type: Number, default: 0 },
+    popperShow: { type: Boolean, default: false },
   },
   data() {
     return {

+ 5 - 5
src/features/OnlineExam/PhoneVerifyForDD.vue

@@ -16,8 +16,8 @@
       </p>
       <p>
         输入验证码: &nbsp;&nbsp;<input
-          size="6"
           v-model="code"
+          size="6"
           placeholder="验证码"
           type="number"
         />
@@ -42,7 +42,7 @@
         <i-button
           style="margin: 0; margin-right: 5px"
           class="qm-primary-button"
-          @click="() => this.logout('?LogoutReason=验证预留手机号')"
+          @click="() => logout('?LogoutReason=验证预留手机号')"
         >
           退出系统
         </i-button>
@@ -66,6 +66,9 @@ export default {
       btnText: "发送验证码",
     };
   },
+  computed: {
+    ...globalMapState(["user"]),
+  },
   async mounted() {
     if (
       ["cugr.ecs.qmth.com.cn", "test.cugr.qmth.com.cn"].includes(
@@ -117,9 +120,6 @@ export default {
       }
     },
   },
-  computed: {
-    ...globalMapState(["user"]),
-  },
 };
 </script>
 

+ 9 - 9
src/features/OnlinePractice/OnlinePracticeHome.vue

@@ -14,13 +14,13 @@
         <Select
           v-model="examId"
           style="width:200px"
-          @on-change="fetchList"
           filterable
+          @on-change="fetchList"
         >
           <Option
             v-for="item in examList"
-            :value="item.id"
             :key="item.id"
+            :value="item.id"
             :label="item.name"
           >
           </Option>
@@ -29,7 +29,7 @@
 
       <OnlinePracticeList
         :courses="courses"
-        :examList="examList"
+        :exam-list="examList"
       ></OnlinePracticeList>
     </div>
   </main-layout>
@@ -41,6 +41,9 @@ import { mapState as globalMapState } from "vuex";
 
 export default {
   name: "OnlinePracticeHome",
+  components: {
+    OnlinePracticeList,
+  },
   data() {
     return {
       examId: null,
@@ -48,6 +51,9 @@ export default {
       courses: [],
     };
   },
+  computed: {
+    ...globalMapState(["user"]),
+  },
   async mounted() {
     window._hmt.push(["_trackEvent", "在线练习列表页面", "进入页面"]);
     if (this.$route.query.examId) {
@@ -103,12 +109,6 @@ export default {
       }
     },
   },
-  computed: {
-    ...globalMapState(["user"]),
-  },
-  components: {
-    OnlinePracticeList,
-  },
 };
 </script>
 

+ 10 - 6
src/features/OnlinePractice/OnlinePracticeList.vue

@@ -57,12 +57,19 @@ const { mapMutations } = createNamespacedHelpers("examHomeModule");
 
 export default {
   name: "OnlinePracticeList",
+  props: {
+    courses: {
+      type: Array,
+      default() {
+        return [];
+      },
+    },
+  },
   data() {
     return { now: new Date(), selectedCourse: null };
   },
-  props: {
-    examList: Array,
-    courses: Array,
+  computed: {
+    ...globalMapState(["user", "timeDifference"]),
   },
   created() {
     this.getNow();
@@ -104,9 +111,6 @@ export default {
       );
     },
   },
-  computed: {
-    ...globalMapState(["user", "timeDifference"]),
-  },
 };
 </script>
 

+ 13 - 13
src/features/OnlinePractice/OnlinePracticeRecordDetail.vue

@@ -81,8 +81,8 @@
 
     <ExamPaper
       v-if="shouldShowPaper"
-      :examId="examId"
-      :examRecordDataId="examRecordDataId"
+      :exam-id="examId"
+      :exam-record-data-id="examRecordDataId"
     />
   </main-layout>
 </template>
@@ -96,6 +96,9 @@ import ExamPaper from "../OnlineExam/Examing/ExamPaper.vue";
 
 export default {
   name: "OnlinePracticeRecordDetail",
+  components: {
+    ExamPaper,
+  },
   data() {
     return {
       examRecordResult: [],
@@ -103,6 +106,14 @@ export default {
       disableGoBack: this.$route.query.disableGoBack,
     };
   },
+  computed: {
+    examRecordDataId() {
+      return this.$route.query.examRecordDataId - 0;
+    },
+    examId() {
+      return this.$route.params.examId - 0;
+    },
+  },
   async created() {
     try {
       const res = await this.$http.get(
@@ -131,17 +142,6 @@ export default {
       this.$router.back();
     },
   },
-  computed: {
-    examRecordDataId() {
-      return this.$route.query.examRecordDataId - 0;
-    },
-    examId() {
-      return this.$route.params.examId - 0;
-    },
-  },
-  components: {
-    ExamPaper,
-  },
 };
 </script>
 

+ 25 - 25
src/features/OnlinePractice/OnlinePracticeRecordList.vue

@@ -82,31 +82,6 @@ export default {
   data() {
     return { recordList: [] };
   },
-  async created() {
-    try {
-      const res = await this.$http.get(
-        "/api/ecs_oe_student/practice/queryPracticeRecordList?examStudentId=" +
-          this.$route.query.examStudentId
-      );
-      this.recordList = (res.data || []).reverse();
-    } catch (error) {
-      this.$Message.error({
-        content: "查询练习记录列表出错!",
-        duration: 15,
-        closable: true,
-      });
-    }
-  },
-  methods: {
-    async enterPracticeRecordDetail(course) {
-      this.$router.push(
-        `/online-practice/exam/${this.$route.params.examId}/detail?examRecordDataId=${course.id}`
-      );
-    },
-    formatTime(ms) {
-      return (ms && moment.utc(ms).format("HH:mm:ss")) || "";
-    },
-  },
   computed: {
     examName() {
       return this.$route.query.examName;
@@ -132,6 +107,31 @@ export default {
       ).toFixed(2);
     },
   },
+  async created() {
+    try {
+      const res = await this.$http.get(
+        "/api/ecs_oe_student/practice/queryPracticeRecordList?examStudentId=" +
+          this.$route.query.examStudentId
+      );
+      this.recordList = (res.data || []).reverse();
+    } catch (error) {
+      this.$Message.error({
+        content: "查询练习记录列表出错!",
+        duration: 15,
+        closable: true,
+      });
+    }
+  },
+  methods: {
+    async enterPracticeRecordDetail(course) {
+      this.$router.push(
+        `/online-practice/exam/${this.$route.params.examId}/detail?examRecordDataId=${course.id}`
+      );
+    },
+    formatTime(ms) {
+      return (ms && moment.utc(ms).format("HH:mm:ss")) || "";
+    },
+  },
 };
 </script>
 

+ 3 - 3
src/features/Password/Password.vue

@@ -12,23 +12,23 @@
       <i-form ref="form" :model="form" :rules="rules" style="width: 320px">
         <i-form-item label="" prop="oldPassword">
           <i-input
+            v-model="form.oldPassword"
             type="password"
             placeholder="请输入旧密码"
-            v-model="form.oldPassword"
           ></i-input>
         </i-form-item>
         <i-form-item label="" prop="newPassword">
           <i-input
+            v-model="form.newPassword"
             type="password"
             placeholder="请输入新密码(6到18位的数字或字母)"
-            v-model="form.newPassword"
           ></i-input>
         </i-form-item>
         <i-form-item label="" prop="newPasswordAgain">
           <i-input
+            v-model="form.newPasswordAgain"
             type="password"
             placeholder="请再次输入新密码"
-            v-model="form.newPasswordAgain"
           ></i-input>
         </i-form-item>
         <i-form-item style="text-align: left">

+ 9 - 9
src/features/SiteMessage/SiteMessageDetail.vue

@@ -39,6 +39,15 @@ import { mapState, mapMutations } from "vuex";
 
 export default {
   name: "SiteMessageDetail",
+  computed: {
+    ...mapState(["user", "siteMessages", "siteMessagesTimeStamp"]),
+    message() {
+      if (this.siteMessages) {
+        return this.siteMessages.find(v => v.id === +this.$route.params.id);
+      }
+      return null;
+    },
+  },
   async mounted() {
     await this.getList();
     await this.prepareData();
@@ -80,15 +89,6 @@ export default {
       this.$router.push("/site-message" + location.search);
     },
   },
-  computed: {
-    ...mapState(["user", "siteMessages", "siteMessagesTimeStamp"]),
-    message() {
-      if (this.siteMessages) {
-        return this.siteMessages.find(v => v.id === +this.$route.params.id);
-      }
-      return null;
-    },
-  },
 };
 </script>
 

+ 10 - 10
src/features/SiteMessage/SiteMessageHome.vue

@@ -23,8 +23,8 @@
         *仅保留近1年的公告通知。
       </div>
       <Table
-        border
         ref="selection"
+        border
         :columns="columns"
         :data="siteMessagesComputed"
       ></Table>
@@ -83,6 +83,15 @@ export default {
       pageSize: 10,
     };
   },
+  computed: {
+    ...mapState(["user", "siteMessages", "siteMessagesTimeStamp"]),
+    siteMessagesComputed() {
+      return this.siteMessages.slice(
+        (this.page - 1) * this.pageSize,
+        this.page * this.pageSize
+      );
+    },
+  },
   async mounted() {
     window._hmt.push(["_trackEvent", "站内消息列表页面", "进入页面"]);
     await this.getList();
@@ -130,15 +139,6 @@ export default {
       await this.getList();
     },
   },
-  computed: {
-    ...mapState(["user", "siteMessages", "siteMessagesTimeStamp"]),
-    siteMessagesComputed() {
-      return this.siteMessages.slice(
-        (this.page - 1) * this.pageSize,
-        this.page * this.pageSize
-      );
-    },
-  },
 };
 </script>
 

+ 1 - 1
src/views/NotFoundComponent.vue

@@ -16,7 +16,7 @@
 
 <script>
 export default {
-  name: "page404",
+  name: "Page404",
   data() {
     return { domain: localStorage.getItem("domain") };
   },

+ 1 - 1
tests/vue/child.vue

@@ -11,7 +11,7 @@
 export default {
   name: "Child",
   props: {
-    passToChild: String,
+    passToChild: { type: String, default: "" },
   },
   data() {
     return {

+ 7 - 7
tests/vue/event.vue

@@ -9,15 +9,15 @@
 
     <div>
       选择你的理想:(use v-model, :model doesn't work)
-      <input type="checkbox" v-model="dreams" value="sleep" /> sleeep
-      <input type="checkbox" v-model="dreams" value="eat" /> eeat
+      <input v-model="dreams" type="checkbox" value="sleep" /> sleeep
+      <input v-model="dreams" type="checkbox" value="eat" /> eeat
     </div>
 
     <div>
       你有多高?
-      <input type="radio" name="height" v-model="height" value="-1.7cm" />
+      <input v-model="height" type="radio" name="height" value="-1.7cm" />
       低于一米七
-      <input type="radio" name="height" v-model="height" value="+1.7cm" />
+      <input v-model="height" type="radio" name="height" value="+1.7cm" />
       高于一米七
     </div>
   </div>
@@ -27,6 +27,9 @@
 import Child from "./child";
 export default {
   name: "Event",
+  components: {
+    Child,
+  },
   data() {
     return {
       name: "michael",
@@ -41,8 +44,5 @@ export default {
       this.globalCount++;
     },
   },
-  components: {
-    Child,
-  },
 };
 </script>

+ 2 - 2
tests/vue/myinput.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
     <!-- 能保证 v-model 可以在最底层组件使用。通过将底层的事件往上传,而不是在中间层修改 -->
-    <input v-bind="$attrs" v-bind:value="value" v-on="inputListeners" />
+    <input v-bind="$attrs" :value="value" v-on="inputListeners" />
 
     <!-- 这个会报错: mutate props -->
     <!-- <input v-model="value"> -->
@@ -10,7 +10,7 @@
 
 <script>
 export default {
-  name: "myinput",
+  name: "Myinput",
   props: {
     value: {
       type: Number,

+ 4 - 4
tests/vue/props.vue

@@ -1,7 +1,7 @@
 <template>
   <div>
     <div>props tests</div>
-    <Child :passToChild="passToChild" />
+    <Child :pass-to-child="passToChild" />
   </div>
 </template>
 
@@ -9,14 +9,14 @@
 import Child from "./child";
 export default {
   name: "PropsTest",
+  components: {
+    Child,
+  },
   data() {
     return { passToChild: "Give to child " };
   },
   created() {
     setInterval(() => (this.passToChild = "Give to child " + Date.now()), 1000);
   },
-  components: {
-    Child,
-  },
 };
 </script>

+ 4 - 4
tests/vue/useMyinput.vue

@@ -9,7 +9,10 @@
 <script>
 import myinput from "./myinput";
 export default {
-  name: "useMyinput",
+  name: "UseMyinput",
+  components: {
+    myinput,
+  },
   data() {
     return {
       val: 3,
@@ -21,8 +24,5 @@ export default {
       console.log(e);
     },
   },
-  components: {
-    myinput,
-  },
 };
 </script>