|
@@ -1,4 +1,4 @@
|
|
-<script setup lang="tsx">
|
|
|
|
|
|
+<script setup lang="ts">
|
|
import RemainTime from "./RemainTime.vue";
|
|
import RemainTime from "./RemainTime.vue";
|
|
import OverallProgress from "./OverallProgress.vue";
|
|
import OverallProgress from "./OverallProgress.vue";
|
|
import QuestionFilters from "./QuestionFilters.vue";
|
|
import QuestionFilters from "./QuestionFilters.vue";
|
|
@@ -9,26 +9,24 @@ import FaceTracking from "./FaceTracking.vue";
|
|
import FaceId from "./FaceId.vue";
|
|
import FaceId from "./FaceId.vue";
|
|
// import FaceMotion from "./FaceMotion/FaceMotion";
|
|
// import FaceMotion from "./FaceMotion/FaceMotion";
|
|
import FaceRecognition from "../FaceRecognition.vue";
|
|
import FaceRecognition from "../FaceRecognition.vue";
|
|
-import { STRICT_CHECK_HOSTS, WEBSOCKET_FOR_AUDIO } from "@/constants/constants";
|
|
|
|
|
|
+import { STRICT_CHECK_HOSTS } from "@/constants/constants";
|
|
import { httpApp } from "@/plugins/axiosApp";
|
|
import { httpApp } from "@/plugins/axiosApp";
|
|
import { useTimers } from "@/setups/useTimers";
|
|
import { useTimers } from "@/setups/useTimers";
|
|
import { checkMainExe } from "@/utils/nativeMethods";
|
|
import { checkMainExe } from "@/utils/nativeMethods";
|
|
import { showLogout } from "@/utils/utils";
|
|
import { showLogout } from "@/utils/utils";
|
|
-import { onBeforeUpdate, onMounted, watch } from "vue";
|
|
|
|
|
|
+import { onBeforeUpdate, onMounted, onUnmounted, watch } from "vue";
|
|
import { useRoute } from "vue-router";
|
|
import { useRoute } from "vue-router";
|
|
import { store } from "@/store/store";
|
|
import { store } from "@/store/store";
|
|
import { useRemoteAppChecker } from "@/features/UserLogin/useRemoteAppChecker";
|
|
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";
|
|
import { useScreenTop } from "./setups/useScreenTop";
|
|
import { useFaceLive } from "./setups/useFaceLive";
|
|
import { useFaceLive } from "./setups/useFaceLive";
|
|
-import { dimensionLog } from "@/utils/logger";
|
|
|
|
import { useFaceCompare } from "./setups/useFaceCompare";
|
|
import { useFaceCompare } from "./setups/useFaceCompare";
|
|
|
|
+import { initExamData } from "./setups/useInitExamData";
|
|
|
|
+import { useWXSocket } from "./setups/useWXSocket";
|
|
|
|
+import { answerAllQuestions } from "./setups/useAnswerQuestions";
|
|
|
|
+import { useRealSubmitPaper } from "./setups/useSubmitPaper";
|
|
|
|
|
|
-const { startWS } = useWebSocket();
|
|
|
|
-
|
|
|
|
-type PRACTICE_TYPE = "IN_PRACTICE" | "NO_ANSWER";
|
|
|
|
|
|
+const { addTimeout, addInterval } = useTimers();
|
|
|
|
|
|
let loading = $ref(true);
|
|
let loading = $ref(true);
|
|
const route = useRoute();
|
|
const route = useRoute();
|
|
@@ -38,8 +36,11 @@ store.exam.examId = examId;
|
|
store.exam.examRecordDataId = examRecordDataId;
|
|
store.exam.examRecordDataId = examRecordDataId;
|
|
|
|
|
|
useScreenTop(examRecordDataId);
|
|
useScreenTop(examRecordDataId);
|
|
-
|
|
|
|
-let courseName = $ref("");
|
|
|
|
|
|
+useWXSocket();
|
|
|
|
+const { userSubmitPaper, realSubmitPaper } = useRealSubmitPaper(
|
|
|
|
+ examId,
|
|
|
|
+ examRecordDataId
|
|
|
|
+);
|
|
|
|
|
|
onBeforeUpdate(() => {
|
|
onBeforeUpdate(() => {
|
|
_hmt.push(["_trackEvent", "答题页面", "题目切换"]);
|
|
_hmt.push(["_trackEvent", "答题页面", "题目切换"]);
|
|
@@ -148,10 +149,6 @@ watch(
|
|
// // }
|
|
// // }
|
|
// },
|
|
// },
|
|
|
|
|
|
-let pageLoadTimeout = $ref(false);
|
|
|
|
-const { addTimeout, addInterval } = useTimers();
|
|
|
|
-addTimeout(() => (pageLoadTimeout = true), 30 * 1000);
|
|
|
|
-
|
|
|
|
// 10秒检查是否有更改需要提交答案
|
|
// 10秒检查是否有更改需要提交答案
|
|
addInterval(() => answerAllQuestions(), 5 * 1000);
|
|
addInterval(() => answerAllQuestions(), 5 * 1000);
|
|
|
|
|
|
@@ -173,7 +170,7 @@ onMounted(async () => {
|
|
});
|
|
});
|
|
|
|
|
|
try {
|
|
try {
|
|
- await initData();
|
|
|
|
|
|
+ await initExamData(examId, examRecordDataId);
|
|
loading = false;
|
|
loading = false;
|
|
} catch (error) {
|
|
} catch (error) {
|
|
logger({
|
|
logger({
|
|
@@ -214,245 +211,6 @@ onMounted(async () => {
|
|
// "resetExamQuestionDirty",
|
|
// "resetExamQuestionDirty",
|
|
// "updatePicture",
|
|
// "updatePicture",
|
|
// ]),
|
|
// ]),
|
|
-async function initData() {
|
|
|
|
- logger({ cnl: ["server", "local"], pgn: "答题页面", act: "before initData" });
|
|
|
|
- const [
|
|
|
|
- { data: weixinAnswerEnabled },
|
|
|
|
- { data: faceCheckEnabled },
|
|
|
|
- { data: faceLivenessEnabled },
|
|
|
|
- { data: examProp },
|
|
|
|
- { data: exam },
|
|
|
|
- { data: paperStruct },
|
|
|
|
- { data: examQuestionListOrig },
|
|
|
|
- { data: _courseName },
|
|
|
|
- ] = await Promise.all([
|
|
|
|
- httpApp.get<boolean>(
|
|
|
|
- "/api/ecs_exam_work/exam/weixinAnswerEnabled/" + examId,
|
|
|
|
- {
|
|
|
|
- "axios-retry": { retries: 4 },
|
|
|
|
- noErrorMessage: true,
|
|
|
|
- }
|
|
|
|
- ),
|
|
|
|
- httpApp.get<boolean>("/api/ecs_exam_work/exam/faceCheckEnabled/" + examId, {
|
|
|
|
- "axios-retry": { retries: 4 },
|
|
|
|
- noErrorMessage: true,
|
|
|
|
- }),
|
|
|
|
- httpApp.get<boolean>(
|
|
|
|
- "/api/ecs_exam_work/exam/identificationOfLivingEnabled/" + examId,
|
|
|
|
- { "axios-retry": { retries: 4 }, noErrorMessage: true }
|
|
|
|
- ),
|
|
|
|
- // 实际上后台都是字符串 {PRACTICE_TYPE: string | null; FREEZE_TIME: string; SNAPSHOT_INTERVAL: string; }
|
|
|
|
- httpApp.get<{
|
|
|
|
- PRACTICE_TYPE: string | null;
|
|
|
|
- FREEZE_TIME: number | null;
|
|
|
|
- SNAPSHOT_INTERVAL: number;
|
|
|
|
- }>(
|
|
|
|
- "/api/ecs_exam_work/exam/getExamPropertyFromCacheByStudentSession/" +
|
|
|
|
- examId +
|
|
|
|
- `/SNAPSHOT_INTERVAL,PRACTICE_TYPE,FREEZE_TIME`,
|
|
|
|
- { "axios-retry": { retries: 4 }, noErrorMessage: true }
|
|
|
|
- ),
|
|
|
|
- httpApp.get<Store["exam"]>("/api/ecs_exam_work/exam/" + examId, {
|
|
|
|
- "axios-retry": { retries: 4 },
|
|
|
|
- noErrorMessage: true,
|
|
|
|
- }),
|
|
|
|
- httpApp.get<PaperStruct>(
|
|
|
|
- "/api/ecs_oe_student/examRecordPaperStruct/getExamRecordPaperStruct?examRecordDataId=" +
|
|
|
|
- examRecordDataId,
|
|
|
|
- { "axios-retry": { retries: 4 }, noErrorMessage: true }
|
|
|
|
- ),
|
|
|
|
- httpApp.get<ExamQuestion[]>(
|
|
|
|
- "/api/ecs_oe_student/examQuestion/findExamQuestionList",
|
|
|
|
- { "axios-retry": { retries: 4 }, noErrorMessage: true }
|
|
|
|
- ),
|
|
|
|
- httpApp.get<string>(
|
|
|
|
- "/api/ecs_oe_student/examControl/courseName/" + examRecordDataId,
|
|
|
|
- { "axios-retry": { retries: 4 }, noErrorMessage: true }
|
|
|
|
- ),
|
|
|
|
- ]);
|
|
|
|
- courseName = _courseName;
|
|
|
|
-
|
|
|
|
- let examQuestionList = examQuestionListOrig;
|
|
|
|
-
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server", "local"],
|
|
|
|
- pgn: "答题页面",
|
|
|
|
- dtl: `end${typeof Object.fromEntries === "function" ? " " : " "}initData`,
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- dimensionLog("答题页面");
|
|
|
|
-
|
|
|
|
- if (exam.examType === "PRACTICE") {
|
|
|
|
- exam.practiceType = examProp.PRACTICE_TYPE as PRACTICE_TYPE;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- exam.freezeTime = JSON.parse("" + examProp.FREEZE_TIME);
|
|
|
|
- examProp.SNAPSHOT_INTERVAL = JSON.parse("" + examProp.SNAPSHOT_INTERVAL);
|
|
|
|
-
|
|
|
|
- exam.WEIXIN_ANSWER_ENABLED = weixinAnswerEnabled;
|
|
|
|
-
|
|
|
|
- store.exam.faceCheckEnabled = faceCheckEnabled;
|
|
|
|
- store.exam.faceLivenessEnabled = faceLivenessEnabled;
|
|
|
|
-
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server", "local"],
|
|
|
|
- pgn: "答题页面",
|
|
|
|
- ext: {
|
|
|
|
- examRecordDataId: examRecordDataId,
|
|
|
|
- faceCheckEnabled: faceCheckEnabled,
|
|
|
|
- faceLivenessEnabled: faceLivenessEnabled,
|
|
|
|
- WEIXIN_ANSWER_ENABLED: exam.WEIXIN_ANSWER_ENABLED,
|
|
|
|
- SNAPSHOT_INTERVAL: examProp.SNAPSHOT_INTERVAL,
|
|
|
|
- PRACTICE_TYPE: examProp.PRACTICE_TYPE,
|
|
|
|
- FREEZE_TIME: examProp.FREEZE_TIME,
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // parentQuestionBody
|
|
|
|
- // questionUnitWrapperList
|
|
|
|
- // questionBody => from examQuestionList
|
|
|
|
- // questionUnitList =>
|
|
|
|
- // studentAnswer
|
|
|
|
- // rightAnswer
|
|
|
|
-
|
|
|
|
- // init subNumber
|
|
|
|
- let questionId: string | null = null;
|
|
|
|
- let i = 1;
|
|
|
|
-
|
|
|
|
- examQuestionList = examQuestionList.map((eq) => {
|
|
|
|
- if (questionId == eq.questionId) {
|
|
|
|
- eq.subNumber = i++;
|
|
|
|
- } else {
|
|
|
|
- i = 1;
|
|
|
|
- questionId = eq.questionId;
|
|
|
|
- eq.subNumber = i++;
|
|
|
|
- }
|
|
|
|
- return eq;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- let groupOrder = 1;
|
|
|
|
- let mainNumber = 0;
|
|
|
|
- examQuestionList = examQuestionList.map((eq) => {
|
|
|
|
- if (mainNumber == eq.mainNumber) {
|
|
|
|
- eq.inGroupOrder = groupOrder++;
|
|
|
|
- } else {
|
|
|
|
- mainNumber = eq.mainNumber;
|
|
|
|
- groupOrder = 1;
|
|
|
|
- eq.inGroupOrder = groupOrder++;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const questionWrapperList =
|
|
|
|
- paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1]
|
|
|
|
- .questionWrapperList;
|
|
|
|
- const groupName =
|
|
|
|
- paperStruct.defaultPaper.questionGroupList[eq.mainNumber - 1].groupName;
|
|
|
|
- const groupTotal = questionWrapperList.reduce(
|
|
|
|
- (accumulator, questionWrapper) =>
|
|
|
|
- accumulator + questionWrapper.questionUnitWrapperList.length,
|
|
|
|
- 0
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- eq.groupName = groupName;
|
|
|
|
- eq.groupTotal = groupTotal;
|
|
|
|
- return eq;
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- store.exam.examQuestionList = examQuestionList.map((eq) => {
|
|
|
|
- const paperStructQuestion = paperStruct.defaultPaper.questionGroupList[
|
|
|
|
- eq.mainNumber - 1
|
|
|
|
- ].questionWrapperList.find((q) => q.questionId === eq.questionId);
|
|
|
|
- return Object.assign(eq, {
|
|
|
|
- limitedPlayTimes: paperStructQuestion!.limitedPlayTimes,
|
|
|
|
- });
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- Object.assign(store.exam, exam);
|
|
|
|
- store.exam.paperStruct = paperStruct;
|
|
|
|
- // TODO: 此处类型待优化
|
|
|
|
- store.exam.allAudioPlayTimes =
|
|
|
|
- JSON.parse(
|
|
|
|
- store.exam.examQuestionList[0].audioPlayTimes as unknown as string
|
|
|
|
- ) || [];
|
|
|
|
-
|
|
|
|
- // this.updateExamState({
|
|
|
|
- // exam: exam,
|
|
|
|
- // paperStruct: paperStruct,
|
|
|
|
- // examQuestionList: examQuestionList,
|
|
|
|
- // allAudioPlayTimes: JSON.parse(examQuestionList[0].audioPlayTimes) || [],
|
|
|
|
- // questionAnswerFileUrl: [],
|
|
|
|
- // pictureAnswer: {},
|
|
|
|
- // });
|
|
|
|
- // console.log(examQuestionList);
|
|
|
|
- // console.log(examQuestionList.find(v => v.answerType === "SINGLE_AUDIO"));
|
|
|
|
-
|
|
|
|
- if (exam.WEIXIN_ANSWER_ENABLED) {
|
|
|
|
- // init data
|
|
|
|
- store.exam.questionAnswerFileUrl = [];
|
|
|
|
- startWS(
|
|
|
|
- WEBSOCKET_FOR_AUDIO + `?key=${store.user.key}&token=${store.user.token}`,
|
|
|
|
- onAudioAnswer,
|
|
|
|
- "微信小程序作答socket"
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-function onAudioAnswer(event: MessageEvent<string>) {
|
|
|
|
- let res: {
|
|
|
|
- eventType: string;
|
|
|
|
- isSuccess: boolean;
|
|
|
|
- errorMessage: string;
|
|
|
|
- data: { order: number; fileUrl: string; transferFileType: string };
|
|
|
|
- };
|
|
|
|
- try {
|
|
|
|
- res = JSON.parse(event.data).content;
|
|
|
|
- } catch (error) {
|
|
|
|
- logger({ cnl: ["server"], act: "JSON.parse出错", possibleError: error });
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (!res) {
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server"],
|
|
|
|
- act: "onAudioAnswer",
|
|
|
|
- dtl: "ws message format error",
|
|
|
|
- ext: { event: JSON.stringify(event) },
|
|
|
|
- });
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- if (res.eventType && res.eventType !== "HEARTBEAT" && !res.isSuccess) {
|
|
|
|
- $message.error(res.errorMessage, { duration: 10, closable: true });
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server"],
|
|
|
|
- act: "onAudioAnswer",
|
|
|
|
- dtl: "error from server",
|
|
|
|
- stk: res.errorMessage,
|
|
|
|
- });
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- switch (res.eventType) {
|
|
|
|
- case "HEARTBEAT":
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server"],
|
|
|
|
- lvl: "debug",
|
|
|
|
- act: "ws heartbeat response from server",
|
|
|
|
- });
|
|
|
|
- break;
|
|
|
|
- case "SCAN_QR_CODE":
|
|
|
|
- logger({ cnl: ["server"], act: "二维码被扫描" });
|
|
|
|
- store.setQuestionQrCodeScanned({ order: res.data.order });
|
|
|
|
- break;
|
|
|
|
- case "GET_FILE_ANSWER":
|
|
|
|
- logger({ cnl: ["server"], act: "获得音频地址" });
|
|
|
|
- store.setQuestionFileAnswerUrl(res.data);
|
|
|
|
- break;
|
|
|
|
- case "SYSTEM_ERROR":
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server"],
|
|
|
|
- act: "ws get error",
|
|
|
|
- ejn: JSON.stringify(res),
|
|
|
|
- });
|
|
|
|
- break;
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
|
|
let { snapId, doSnap, showSnapResult } = useFaceCompare();
|
|
let { snapId, doSnap, showSnapResult } = useFaceCompare();
|
|
let { showFaceId } = useFaceLive(doSnap);
|
|
let { showFaceId } = useFaceLive(doSnap);
|
|
@@ -465,10 +223,23 @@ function onCompareResult({ hasError, fileName }: CompareResult) {
|
|
addInterval(doSnap, 60 * 1000);
|
|
addInterval(doSnap, 60 * 1000);
|
|
} else {
|
|
} else {
|
|
cmpResMap.set(fileName, false);
|
|
cmpResMap.set(fileName, false);
|
|
- showSnapResult(fileName, examRecordDataId, cmpResMap);
|
|
|
|
|
|
+ void showSnapResult(fileName, examRecordDataId, cmpResMap);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+onUnmounted(() => {
|
|
|
|
+ const allFinished = [...cmpResMap].every((v) => v[1]);
|
|
|
|
+ const remained = [...cmpResMap].filter((v) => !v[1]);
|
|
|
|
+ logger({
|
|
|
|
+ cnl: ["server"],
|
|
|
|
+ act: "交卷前检测抓拍照片数量",
|
|
|
|
+ dtl: allFinished ? "完全检测" : "不完全检测",
|
|
|
|
+ ext: {
|
|
|
|
+ remainCount: remained.length,
|
|
|
|
+ },
|
|
|
|
+ });
|
|
|
|
+});
|
|
|
|
+
|
|
// async function updateQuestion(next) {
|
|
// async function updateQuestion(next) {
|
|
// // 初始化套题的答案,为回填部分选项做准备
|
|
// // 初始化套题的答案,为回填部分选项做准备
|
|
// // for (let q of this.examQuestionList) {
|
|
// // for (let q of this.examQuestionList) {
|
|
@@ -484,131 +255,6 @@ function onCompareResult({ hasError, fileName }: CompareResult) {
|
|
// if (!this.exam) return;
|
|
// if (!this.exam) return;
|
|
// }
|
|
// }
|
|
|
|
|
|
-function resetExamQuestionDirty() {
|
|
|
|
- store.exam.examQuestionList = store.exam.examQuestionList.map((eq) => {
|
|
|
|
- return Object.assign({}, eq, { dirty: false });
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-type Answer = {
|
|
|
|
- order: number;
|
|
|
|
- studentAnswer: string;
|
|
|
|
- audioPlayTimes: { audioName: string; times: number }[];
|
|
|
|
- isSign: boolean;
|
|
|
|
-};
|
|
|
|
-async function answerAllQuestions(ignoreDirty?: boolean): Promise<boolean> {
|
|
|
|
- const answers: Answer[] = store.exam.examQuestionList
|
|
|
|
- .filter((eq) => (ignoreDirty ? true : eq.dirty))
|
|
|
|
- .filter((eq) => eq.getQuestionContent)
|
|
|
|
- .map((eq) => {
|
|
|
|
- return Object.assign(
|
|
|
|
- {
|
|
|
|
- order: eq.order,
|
|
|
|
- studentAnswer: eq.studentAnswer,
|
|
|
|
- },
|
|
|
|
- eq.audioPlayTimes && { audioPlayTimes: eq.audioPlayTimes },
|
|
|
|
- eq.isSign && { isSign: eq.isSign }
|
|
|
|
- ) as Answer;
|
|
|
|
- });
|
|
|
|
- if (answers.length > 0) {
|
|
|
|
- try {
|
|
|
|
- await httpApp.post(
|
|
|
|
- "/api/ecs_oe_student/examQuestion/submitQuestionAnswer",
|
|
|
|
- answers
|
|
|
|
- );
|
|
|
|
- resetExamQuestionDirty();
|
|
|
|
- } catch (error) {
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server", "local"],
|
|
|
|
- pgu: "AUTO",
|
|
|
|
- act: "提交答案失败",
|
|
|
|
- possibleError: error,
|
|
|
|
- });
|
|
|
|
- $message.error("提交答案失败");
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // 提交成功,返回true,供最后提交时判断。自动提交失败,不暂停。
|
|
|
|
- return true;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-async function submitPaper() {
|
|
|
|
- logger({ cnl: ["server", "local", "console"], act: "学生点击交卷" });
|
|
|
|
- try {
|
|
|
|
- // 交卷前强制提交所有答案
|
|
|
|
- const ret = await answerAllQuestions(true);
|
|
|
|
- if (!ret) {
|
|
|
|
- // 提交答案失败,停止交卷逻辑。
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- } catch (error) {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (
|
|
|
|
- store.exam.freezeTime &&
|
|
|
|
- store.exam.remainTime >
|
|
|
|
- (store.exam.duration - store.exam.freezeTime) * 60 * 1000
|
|
|
|
- ) {
|
|
|
|
- $message.info(`考试开始${store.exam.freezeTime}分钟后才允许交卷。`);
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const answered = store.exam.examQuestionList.filter(
|
|
|
|
- (q) => q.studentAnswer !== null
|
|
|
|
- ).length;
|
|
|
|
- const unanswered = store.exam.examQuestionList.filter(
|
|
|
|
- (q) => q.studentAnswer === null
|
|
|
|
- ).length;
|
|
|
|
- const signed = store.exam.examQuestionList.filter((q) => q.isSign).length;
|
|
|
|
- const showConfirmTime = Date.now();
|
|
|
|
- $dialog.info({
|
|
|
|
- title: "确认交卷",
|
|
|
|
- content: () => (
|
|
|
|
- <div>
|
|
|
|
- <p>已答题目:{answered}</p>
|
|
|
|
- <p>未答题目:{unanswered}</p>
|
|
|
|
- <p>标记题目:{signed}</p>
|
|
|
|
- </div>
|
|
|
|
- ),
|
|
|
|
- positiveText: "确定",
|
|
|
|
- onPositiveClick: () => {
|
|
|
|
- void realSubmitPaper();
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-function realSubmitPaper() {
|
|
|
|
- store.increaseGlobalMaskCount("realSubmitPaper");
|
|
|
|
- store.spinMessage = "正在交卷,请耐心等待...";
|
|
|
|
- logger({ cnl: ["server"], act: "正在交卷,请耐心等待..." });
|
|
|
|
- if (store.exam.faceCheckEnabled) {
|
|
|
|
- logger({ cnl: ["server"], act: "交卷前抓拍" });
|
|
|
|
- doSnap();
|
|
|
|
- }
|
|
|
|
- // 给抓拍照片多5秒处理时间
|
|
|
|
- // 和下行注释的sleep语句不是一样的。sleep之后还可以执行。加上clearTimeout则可拒绝。
|
|
|
|
- // await new Promise(resolve => setTimeout(() => resolve(), delay * 1000));
|
|
|
|
- addTimeout(() => {
|
|
|
|
- store.decreaseGlobalMaskCount("realSubmitPaper");
|
|
|
|
- store.spinMessage = "";
|
|
|
|
- const allFinished = [...cmpResMap].every((v) => v[1]);
|
|
|
|
- const remained = [...cmpResMap].filter((v) => !v[1]);
|
|
|
|
- logger({
|
|
|
|
- cnl: ["server"],
|
|
|
|
- act: "交卷前检测抓拍照片数量",
|
|
|
|
- dtl: allFinished ? "完全检测" : "不完全检测",
|
|
|
|
- ext: {
|
|
|
|
- remainCount: remained.length,
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
- void router.push({
|
|
|
|
- name: "SubmitPaper",
|
|
|
|
- params: { examId, examRecordDataId },
|
|
|
|
- });
|
|
|
|
- }, 5 * 1000);
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
function shouldSubmitPaper() {
|
|
function shouldSubmitPaper() {
|
|
logger({ cnl: ["server"], act: "时间到自动交卷" });
|
|
logger({ cnl: ["server"], act: "时间到自动交卷" });
|
|
void realSubmitPaper();
|
|
void realSubmitPaper();
|
|
@@ -620,6 +266,9 @@ function shouldSubmitPaper() {
|
|
// )
|
|
// )
|
|
// );
|
|
// );
|
|
|
|
|
|
|
|
+let pageLoadTimeout = $ref(false);
|
|
|
|
+addTimeout(() => (pageLoadTimeout = true), 30 * 1000);
|
|
|
|
+
|
|
function reloadPage() {
|
|
function reloadPage() {
|
|
logger({
|
|
logger({
|
|
cnl: ["server", "local"],
|
|
cnl: ["server", "local"],
|
|
@@ -634,9 +283,6 @@ const {
|
|
disableLoginBtnBecauseRemoteApp: disableExamingBecauseRemoteApp,
|
|
disableLoginBtnBecauseRemoteApp: disableExamingBecauseRemoteApp,
|
|
checkRemoteAppTxt: checkRemoteApp,
|
|
checkRemoteAppTxt: checkRemoteApp,
|
|
} = useRemoteAppChecker();
|
|
} = useRemoteAppChecker();
|
|
-console.log({
|
|
|
|
- disableExamingBecauseRemoteApp: disableExamingBecauseRemoteApp.value,
|
|
|
|
-});
|
|
|
|
|
|
|
|
function checkRemoteAppClicked() {
|
|
function checkRemoteAppClicked() {
|
|
logger({ cnl: ["server"], pgu: "AUTO", act: "点击确认已关闭远程桌面软件" });
|
|
logger({ cnl: ["server"], pgu: "AUTO", act: "点击确认已关闭远程桌面软件" });
|
|
@@ -652,7 +298,7 @@ addInterval(() => checkRemoteApp(), 3 * 60 * 1000);
|
|
<div class="header">
|
|
<div class="header">
|
|
<RemainTime @onEndtime="shouldSubmitPaper"></RemainTime>
|
|
<RemainTime @onEndtime="shouldSubmitPaper"></RemainTime>
|
|
<div style="display: flex; flex-direction: column">
|
|
<div style="display: flex; flex-direction: column">
|
|
- <div style="margin-bottom: 12px">{{ courseName }}</div>
|
|
|
|
|
|
+ <div style="margin-bottom: 12px">{{ store.exam.courseName }}</div>
|
|
<OverallProgress></OverallProgress>
|
|
<OverallProgress></OverallProgress>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div>
|
|
@@ -660,7 +306,7 @@ addInterval(() => checkRemoteApp(), 3 * 60 * 1000);
|
|
{{ store.user.studentCodeList.join(",") }}
|
|
{{ store.user.studentCodeList.join(",") }}
|
|
</div>
|
|
</div>
|
|
<QuestionFilters></QuestionFilters>
|
|
<QuestionFilters></QuestionFilters>
|
|
- <n-button type="success" @click="submitPaper">交卷</n-button>
|
|
|
|
|
|
+ <n-button type="success" @click="userSubmitPaper">交卷</n-button>
|
|
</div>
|
|
</div>
|
|
<div id="examing-home-question" class="main">
|
|
<div id="examing-home-question" class="main">
|
|
<!-- <QuestionView :examQuestion="examQuestion()"></QuestionView> -->
|
|
<!-- <QuestionView :examQuestion="examQuestion()"></QuestionView> -->
|
|
@@ -677,7 +323,7 @@ addInterval(() => checkRemoteApp(), 3 * 60 * 1000);
|
|
:showRecognizeButton="false"
|
|
:showRecognizeButton="false"
|
|
:examRecordDataId="examRecordDataId"
|
|
:examRecordDataId="examRecordDataId"
|
|
:snapId="snapId"
|
|
:snapId="snapId"
|
|
- @on-async-recognize-result="onCompareResult"
|
|
|
|
|
|
+ @onAsyncRecognizeResult="onCompareResult"
|
|
/>
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|