Explorar el Código

切屏次数控制

Michael Wang hace 3 años
padre
commit
b8fecaa7f9

+ 1 - 0
package.json

@@ -43,6 +43,7 @@
     "@typescript-eslint/parser": "^5.17.0",
     "@vitejs/plugin-vue": "^2.3.1",
     "autoprefixer": "^10.4.4",
+    "electron": "1.7.16",
     "eslint": "^8.12.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-vue": "^8.5.0",

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 589 - 2
pnpm-lock.yaml


+ 18 - 10
src/features/OnlineExam/Examing/ExamingHome.vue

@@ -14,13 +14,14 @@ import { httpApp } from "@/plugins/axiosApp";
 import { useTimers } from "@/setups/useTimers";
 import { checkMainExe } from "@/utils/nativeMethods";
 import { showLogout } from "@/utils/utils";
-import { onBeforeUpdate, onMounted } from "vue";
+import { onBeforeUpdate, onMounted, watch } from "vue";
 import { useRoute } from "vue-router";
 import { store } from "@/store/store";
 import { useRemoteAppChecker } from "@/features/UserLogin/useRemoteAppChecker";
 import { ExamQuestion, PaperStruct, Store } from "@/types/student-client";
 import router from "@/router";
 import { useWebSocket } from "@/setups/useWebSocket";
+import { useScreenTop } from "./setups/useScreenTop";
 
 const { startWS } = useWebSocket();
 
@@ -28,8 +29,10 @@ type PRACTICE_TYPE = "IN_PRACTICE" | "NO_ANSWER";
 
 let loading = $ref(true);
 const route = useRoute();
-const examId = route.params.examId;
-const examRecordDataId = route.params.examRecordDataId;
+const examId = +route.params.examId;
+const examRecordDataId = +route.params.examRecordDataId;
+
+useScreenTop(examRecordDataId);
 
 let courseName = $ref("");
 
@@ -38,6 +41,17 @@ onBeforeUpdate(() => {
   void answerAllQuestions();
 });
 
+watch(
+  () => store.exam.isExceededSwitchCount,
+  () => {
+    logger({ cnl: ["server"], act: "切屏超出次数自动交卷" });
+    void router.push({
+      name: "SubmitPaper",
+      params: { examId, examRecordDataId },
+    });
+  }
+);
+
 // computed: {
 //   ...mapState([
 //     "exam",
@@ -48,7 +62,6 @@ onBeforeUpdate(() => {
 //     "remainTime",
 //     "questionAnswerFileUrl",
 //     "uploadModalVisible",
-//     "exceedSwitchCount",
 //   ]),
 //   previousQuestionOrder: (vm) => {
 //     if (vm.examQuestion().order > 1) {
@@ -126,11 +139,7 @@ onBeforeUpdate(() => {
 //       }
 //     }
 //   },
-//   exceedSwitchCount(val) {
-//     if (val) {
-//       this.logger({ action: "切屏超出次数自动交卷" });
-//       this.realSubmitPaper();
-//     }
+
 //   },
 //   // examQuestionList(val, oldVal) {
 //   //   // console.log(val, oldVal);
@@ -221,7 +230,6 @@ onMounted(async () => {
 //     pictureAnswer: {},
 //     snapNow: false,
 //     snapProcessingCount: 0,
-//     exceedSwitchCount: false,
 //   });
 //   // TODO: 是否是个错误点?this.$Modal 不存在?
 //   this.$Modal.remove();

+ 89 - 0
src/features/OnlineExam/Examing/setups/useScreenTop.ts

@@ -0,0 +1,89 @@
+import { httpApp } from "@/plugins/axiosApp";
+import { store } from "@/store/store";
+import { onBeforeUnmount, onMounted } from "vue";
+
+export function useScreenTop(examRecordDataId: string | number) {
+  let lastBlurTimeStamp = 0;
+  function blurHandler() {
+    if (Date.now() - lastBlurTimeStamp < 1000) {
+      // 小于1秒的切屏不计算在内,快捷键alt+tab容易连续触发两次
+      return;
+    }
+    lastBlurTimeStamp = Date.now();
+
+    logger({
+      cnl: ["server"],
+      act: "切屏处理预备",
+      dtl: "学生端不是置顶状态",
+      pgu: "AUTO",
+    });
+    $message.warning("考试过程请勿尝试切屏,可能影响考试成绩");
+    void httpApp
+      .post(
+        "/api/ecs_oe_student/examControl/switchScreen?examRecordDataId=" +
+          examRecordDataId
+      )
+      .then((res) => {
+        if (
+          store.QECSConfig.PREVENT_CHEATING_CONFIG.includes(
+            "RECORD_SWITCH_SCREEN"
+          )
+        ) {
+          logger({
+            cnl: ["server"],
+            act: "切屏处理结果",
+            dtl: "学生端不是置顶状态",
+            pgu: "AUTO",
+            ext: { res: JSON.stringify(res.data) },
+          });
+          if (res.data) {
+            // console.log(res.data);
+            const { switchScreenCount, maxSwitchScreenCount } = res.data;
+            if (
+              typeof switchScreenCount === "number" &&
+              typeof maxSwitchScreenCount === "number"
+            ) {
+              if (switchScreenCount > maxSwitchScreenCount) {
+                store.exam.isExceededSwitchCount = true;
+              }
+            }
+          }
+        }
+      })
+      .catch((e) =>
+        logger({ cnl: ["server"], act: "切屏处理请求失败", possibleError: e })
+      );
+  }
+
+  function toggleSwitchCount(switchState: "on" | "off") {
+    if (!store.QECSConfig.PREVENT_CHEATING_CONFIG.includes("FULL_SCREEN_TOP")) {
+      return;
+    }
+    if (typeof window.nodeRequire == "undefined") {
+      logger({
+        cnl: ["server"],
+        act: "非考生端,不检测置顶状态",
+        ext: { switchState },
+      });
+      return;
+    }
+
+    logger({
+      cnl: ["server"],
+      act: "考生端,设置检测置顶状态",
+      ext: { switchState },
+    });
+    const remote: typeof import("electron").remote =
+      window.nodeRequire("electron").remote;
+    const mainWindow = remote.getCurrentWindow();
+
+    if (switchState === "on") {
+      mainWindow.on("blur", blurHandler);
+    } else {
+      mainWindow.removeListener("blur", blurHandler);
+    }
+  }
+
+  onMounted(() => toggleSwitchCount("on"));
+  onBeforeUnmount(() => toggleSwitchCount("off"));
+}

+ 6 - 1
src/types/student-client.d.ts

@@ -41,7 +41,8 @@ export type Store = {
         "FULL_SCREEN_TOP",
         "DISABLE_REMOTE_ASSISTANCE",
         "DISABLE_VIRTUAL_CAMERA",
-        "DISABLE_MULTISCREEN"
+        "DISABLE_MULTISCREEN",
+        "RECORD_SWITCH_SCREEN"
       ]
     >;
     /** 学校是否有定制logo !!!需对json进行转换!!! */
@@ -166,12 +167,16 @@ export type Store = {
     /** 试题过滤类型 */
     questionFilterType: "ALL" | "ANSWERED" | "SIGNED" | "UNANSWERED";
     allAudioPlayTimes: { audioName: string; times: number }[];
+    /** 某试题的二维码被扫码识别了 */
     questionQrCodeScanned: { order: number };
+    /** 试题的文件作答地址 */
     questionAnswerFileUrl: {
       order: number;
       fileUrl: string;
       transferFileType: string;
     }[];
+    /** 是否超过了切屏次数 */
+    isExceededSwitchCount: boolean;
   };
   // /** 考试中的状态 */
   // examing: {};

+ 3 - 6
src/utils/nativeMethods.ts

@@ -39,18 +39,15 @@ export function execLocal(exeName: string): Promise<void> {
           });
           // 如果相对路径没找到,则通过绝对路径来执行
           if (!exeName.includes(":") && err) {
-            const remote = window.nodeRequire("electron").remote;
-            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
+            const remote: typeof import("electron").remote =
+              window.nodeRequire("electron").remote;
             const fs: typeof import("fs") = remote.require("fs");
 
-            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
             const path: typeof import("path") = remote.require("path");
 
             const [exePath, exeParams] = exeName.split(" ");
-            // eslint-disable-next-line @typescript-eslint/no-unsafe-call
             const absPath = path.join(
-              // eslint-disable-next-line @typescript-eslint/no-unsafe-call
-              remote.app.getAppPath() as string,
+              remote.app.getAppPath(),
               "../../",
               exePath
             );

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio