Explorar o código

东北大学需求

刘洋 hai 6 meses
pai
achega
7b38ec999b

+ 2 - 0
components.d.ts

@@ -7,6 +7,8 @@ export {}
 
 declare module '@vue/runtime-core' {
   export interface GlobalComponents {
+    AAlert: typeof import('ant-design-vue/es')['Alert']
+    Abnormal: typeof import('./src/components/Abnormal.vue')['default']
     AButton: typeof import('ant-design-vue/es')['Button']
     ACol: typeof import('ant-design-vue/es')['Col']
     AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "stmms-web",
-  "version": "1.5.0",
+  "version": "1.5.1",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "stmms-web",
-      "version": "1.5.0",
+      "version": "1.5.1",
       "dependencies": {
         "@ant-design/icons-vue": "^6.1.0",
         "animate.css": "^4.1.1",

+ 59 - 0
src/components/Abnormal.vue

@@ -0,0 +1,59 @@
+<template>
+  <div v-if="store.currentTask?.collationLabelCode" class="abnormal">
+    <div class="collapse-btn" :class="{ rotate: !show }" @click="show = !show">
+      <UpOutlined style="font-size: 12px" />
+    </div>
+    <Transition
+      enterActiveClass="animate__animated animate__slideInDown"
+      leaveActiveClass="animate__animated animate__slideOutUp"
+    >
+      <a-alert v-if="show" :message="result" banner />
+    </Transition>
+  </div>
+</template>
+<script name="Abnormal" lang="ts" setup>
+import { computed, ref } from "vue";
+import { store } from "@/store/store";
+
+import { UpOutlined } from "@ant-design/icons-vue";
+const show = ref(true);
+const result = computed(() => {
+  let collationLabelList = store.setting?.collationLabelList || [];
+  let name =
+    collationLabelList.find(
+      (item: any) => item.code == store.currentTask?.collationLabelCode
+    )?.name || "";
+  return `试卷整理异常信息:${store.currentTask?.secretNumber || ""} - ${name}`;
+});
+</script>
+<style lang="less" scoped>
+.abnormal {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1;
+  :deep(.ant-alert-warning) {
+    padding-left: 30px;
+  }
+  .collapse-btn {
+    position: absolute;
+    left: 5px;
+    top: 8px;
+    z-index: 1;
+    width: 20px;
+    height: 20px;
+    border-radius: 50%;
+    background: #fffbe6;
+    color: #faad14;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    cursor: pointer;
+    border: 1px solid #faad14;
+    transition: all 0.5s;
+    &.rotate {
+      transform: rotate(180deg);
+    }
+  }
+}
+</style>

+ 5 - 5
src/devLoginParams.ts

@@ -100,10 +100,10 @@
 // export const LOGIN_CONFIG = {
 //   isAdmin: false,
 //   forceChange: true,
-//   loginName: "417-431-2-1",
+//   loginName: "444-444-1-1",
 //   password: "123456",
-//   examId: "431",
-//   markerId: "4295",
+//   examId: "391",
+//   markerId: "4187",
 // };
 
 /** 225 管理员 */
@@ -111,9 +111,9 @@
 export const LOGIN_CONFIG = {
   isAdmin: true,
   forceChange: true,
-  loginName: "admin1",
+  loginName: "admin031",
   password: "123456",
-  examId: "23",
+  examId: "391",
   markerId: null,
 };
 

+ 10 - 2
src/features/arbitrate/Arbitrate.vue

@@ -106,8 +106,15 @@ async function updateSetting() {
     subjectCode,
     groupNumber
   );
-  const { examType, fileServer, subject, userName, splitConfig, enableSplit } =
-    settingRes.data;
+  const {
+    examType,
+    fileServer,
+    subject,
+    userName,
+    splitConfig,
+    enableSplit,
+    collationLabelList,
+  } = settingRes.data;
   store.initSetting({
     examType,
     fileServer,
@@ -115,6 +122,7 @@ async function updateSetting() {
     userName,
     splitConfig,
     enableSplit,
+    collationLabelList,
   });
   /*****************************根据本地临时会话存储的mode内容********************************* */
   let arbitrateLocalMode = sessionStorage.getItem("arbitrate_local_mode");

+ 10 - 2
src/features/library/inspect/LibraryInspect.vue

@@ -73,8 +73,15 @@ async function updateClearTask() {
 async function updateSetting() {
   const settingRes = await getAdminPageSetting(subjectCode);
 
-  const { examType, fileServer, subject, userName, splitConfig, enableSplit } =
-    settingRes.data;
+  const {
+    examType,
+    fileServer,
+    subject,
+    userName,
+    splitConfig,
+    enableSplit,
+    collationLabelList,
+  } = settingRes.data;
   store.initSetting({
     examType,
     fileServer,
@@ -82,6 +89,7 @@ async function updateSetting() {
     userName,
     splitConfig,
     enableSplit,
+    collationLabelList,
   });
 
   if (store.setting.subject?.paperUrl && store.isMultiMedia) {

+ 10 - 2
src/features/library/libraryTrack/LibraryTrack.vue

@@ -30,8 +30,15 @@ let subjectCode = route.query.subjectCode;
 
 async function updateSetting() {
   const settingRes = await getAdminPageSetting(subjectCode as string);
-  const { examType, fileServer, subject, userName, splitConfig, enableSplit } =
-    settingRes.data;
+  const {
+    examType,
+    fileServer,
+    subject,
+    userName,
+    splitConfig,
+    enableSplit,
+    collationLabelList,
+  } = settingRes.data;
   store.initSetting({
     examType,
     fileServer,
@@ -39,6 +46,7 @@ async function updateSetting() {
     userName,
     splitConfig,
     enableSplit,
+    collationLabelList,
   });
 
   if (store.setting.subject?.paperUrl && store.isMultiMedia) {

+ 10 - 2
src/features/library/quality/Quality.vue

@@ -42,8 +42,15 @@ const { subjectCode, markerId, markerScore, noArbitrate } = route.query as {
 
 async function updateSetting() {
   const settingRes = await getAdminPageSetting(subjectCode);
-  const { examType, fileServer, subject, userName, splitConfig, enableSplit } =
-    settingRes.data;
+  const {
+    examType,
+    fileServer,
+    subject,
+    userName,
+    splitConfig,
+    enableSplit,
+    collationLabelList,
+  } = settingRes.data;
   store.initSetting({
     examType,
     fileServer,
@@ -51,6 +58,7 @@ async function updateSetting() {
     userName,
     splitConfig,
     enableSplit,
+    collationLabelList,
   });
 
   if (store.setting.subject?.paperUrl && store.isMultiMedia) {

+ 6 - 0
src/features/mark/CommonMarkBody.vue

@@ -3,6 +3,7 @@
     ref="dragContainer"
     class="mark-body-container tw-flex-auto tw-p-2 tw-pt-0 tw-relative"
   >
+    <Abnormal />
     <div
       v-if="!store.currentTask"
       class="tw-text-center empty-task tw-flex tw-flex-col tw-place-items-center tw-justify-center"
@@ -102,6 +103,7 @@ import Viewer from "viewerjs";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 import { message } from "ant-design-vue";
 import EventBus from "@/plugins/eventBus";
+import Abnormal from "@/components/Abnormal.vue";
 type MakeTrack = (
   event: MouseEvent,
   item: SliceImage,
@@ -112,9 +114,13 @@ type MakeTrack = (
 const {
   hasMarkResultToRender = false,
   makeTrack = () => console.debug("非评卷界面makeTrack没有意义"),
+  secretNumber = "",
+  msg = "",
 } = defineProps<{
   hasMarkResultToRender?: boolean;
   makeTrack?: MakeTrack;
+  secretNumber?: string;
+  msg?: string;
 }>();
 
 const emit = defineEmits(["error"]);

+ 51 - 4
src/features/mark/MarkBoardTrack.vue

@@ -73,6 +73,7 @@
           shape="round"
           size="middle"
           style="height: 76px; border-radius: 10px; padding: 12px"
+          :disabled="disabledArbitrateType"
           @click="submit"
         >
           提交
@@ -255,7 +256,10 @@
           v-for="(s, i) in questionScoreSteps.slice(1)"
           :key="i"
           class="single-score tw-cursor-pointer tw-font-bold"
-          :class="isCurrentScore(s) && 'current-score'"
+          :class="{
+            'current-score': isCurrentScore(s),
+            'limit-disable': limitDisable(),
+          }"
           @click="chooseScore(s)"
         >
           {{ s }}
@@ -265,7 +269,10 @@
           <template #title>作答错误,或只书写题号等情况</template>
           <div
             class="single-score tw-cursor-pointer tw-font-bold"
-            :class="Object.is(store.currentScore, 0) && 'current-score'"
+            :class="{
+              'current-score': Object.is(store.currentScore, 0),
+              'limit-disable': limitDisable(),
+            }"
             @click="chooseScore(0)"
           >
             0
@@ -275,7 +282,10 @@
           <template #title>作答为空、未作答、或完全没找到作答</template>
           <div
             class="single-score tw-cursor-pointer tw-font-bold"
-            :class="Object.is(store.currentScore, -0) && 'current-score'"
+            :class="{
+              'current-score': Object.is(store.currentScore, -0),
+              'limit-disable': limitDisable(),
+            }"
             @click="chooseScore(-0)"
           >
@@ -326,10 +336,16 @@ import { addFocusTrack, removeFocusTrack } from "./use/focusTracks";
 import EventBus from "@/plugins/eventBus";
 import { cloneDeep } from "lodash-es";
 import { useRoute } from "vue-router";
+import { message } from "ant-design-vue";
 
 const route = useRoute();
 // const curQuestionIndex = ref<number>(0);
-
+const disabledArbitrateType = computed(() => {
+  return (
+    route.path === "/admin/exam/arbitrate/start" &&
+    store.currentTask?.arbitrateType === "QUESTION"
+  );
+});
 const props = defineProps<{ modal?: boolean; arbitrateIndex?: string }>();
 const activeIndex = computed(() => {
   return (
@@ -516,7 +532,34 @@ const curQuestionIndex = computed(() => {
 function isCurrentScore(score: number) {
   return store.currentScore === score;
 }
+function limitDisable() {
+  let curQuestion = store.currentTask?.questionList[curQuestionIndex.value];
+  let arr = (store.currentTask?.markResult?.trackList || []).filter(
+    (track: any) =>
+      track.mainNumber == curQuestion?.mainNumber &&
+      track.subNumber == curQuestion?.subNumber
+  );
+  return (
+    store.isTrackMode &&
+    Number(curQuestion?.trackCount) > 0 &&
+    arr.length == Number(curQuestion?.trackCount)
+  );
+}
+watch(
+  () => store.currentTask?.markResult?.trackList,
+  () => {
+    if (limitDisable()) {
+      store.currentScore = undefined;
+    }
+  },
+  { deep: true }
+);
 function chooseScore(score: number) {
+  if (limitDisable()) {
+    const curQuestion = store.currentTask?.questionList[curQuestionIndex.value];
+    void message.error(`该题限制轨迹数量为${curQuestion.trackCount}个`);
+    return;
+  }
   if (store.currentScore === score) {
     store.currentScore = undefined;
   } else {
@@ -789,6 +832,10 @@ const buttonHeightForSelective = $computed(() =>
   background-color: var(--app-score-color);
   color: white;
 }
+.limit-disable {
+  cursor: not-allowed;
+  background-color: #ddd !important;
+}
 
 .all-zero-unselective-button {
   height: v-bind(buttonHeightForSelective);

+ 14 - 4
src/features/mark/MarkHeader.vue

@@ -450,15 +450,25 @@ watchEffect(() => {
     store.setting.topCount > 0 &&
     store.setting.topCount === store.status.personCount
   ) {
+    // Modal.confirm({
+    //   centered: true,
+    //   mask: true,
+    //   zIndex: 6000,
+    //   maskStyle: { opacity: 0.97 },
+    //   content: `分配任务份已完成,是否继续?`,
+    //   okText: "继续",
+    //   cancelText: "退出",
+    //   onCancel: logout,
+    // });
     Modal.confirm({
       centered: true,
       mask: true,
       zIndex: 6000,
       maskStyle: { opacity: 0.97 },
-      content: `分配任务份已完成,是否继续?`,
-      okText: "继续",
-      cancelText: "退出",
-      onCancel: logout,
+      content: `分配任务份已完成,请退出`,
+      okText: "退出",
+      cancelButtonProps: { style: { display: "none" } },
+      onOk: logout,
     });
   }
 });

+ 8 - 2
src/features/student/importInspect/ImportInspect.vue

@@ -43,8 +43,14 @@ let currentStudentId = $ref(0);
 
 async function updateSetting() {
   const settingRes = await getInspectedSettingOfImportInspect(studentId);
-  const { examType, fileServer, doubleTrack } = settingRes.data;
-  store.initSetting({ examType, fileServer, doubleTrack } as AdminPageSetting);
+  const { examType, fileServer, doubleTrack, collationLabelList } =
+    settingRes.data;
+  store.initSetting({
+    examType,
+    fileServer,
+    doubleTrack,
+    collationLabelList,
+  } as AdminPageSetting);
   store.status.totalCount = settingRes.data.inspectCount;
   store.status.markedCount = 0;
 

+ 179 - 173
src/features/student/scoreVerify/ScoreVerify.vue

@@ -1,173 +1,179 @@
-<template>
-  <div class="my-container">
-    <mark-header />
-    <div class="tw-flex tw-gap-1">
-      <mark-body origImageUrls="sheetUrls" @error="renderError" />
-      <MarkBoardInspect
-        :tagged="isCurrentTagged"
-        :isFirst="isFirst"
-        :isLast="isLast"
-        @makeTag="saveTaskToServer"
-        @fetchTask="fetchTask"
-      />
-    </div>
-  </div>
-  <MinimapModal />
-  <PaperModal />
-</template>
-
-<script setup lang="ts">
-import { onMounted, ref } from "vue";
-// import {
-//   getInspectedSettingOfImportInspect,
-//   getSingleInspectedTaskOfImportInspect,
-//   saveInspectedTaskOfImportInspect,
-// } from "@/api/importInspectPage";
-import {
-  getInspectedSettingOfImportInspect,
-  getSingleInspectedTaskOfImportInspect,
-  saveInspectedTaskOfImportInspect,
-} from "@/api/scoreVerify";
-import { store } from "@/store/store";
-import MarkHeader from "./MarkHeader.vue";
-import MinimapModal from "@/features/mark/MinimapModal.vue";
-import PaperModal from "@/features/mark/PaperModal.vue";
-import { useRoute } from "vue-router";
-// import MarkBody from "../studentInspect/MarkBody.vue";
-import MarkBody from "./markBody.vue";
-import MarkBoardInspect from "./MarkBoardInspect.vue";
-import type { AdminPageSetting } from "@/types";
-import { message } from "ant-design-vue";
-import { addFileServerPrefixToTask } from "@/utils/utils";
-
-const route = useRoute();
-const { studentId } = route.query as {
-  studentId: string | number;
-};
-
-let studentIds: (number | string)[] = $ref([]);
-// let tagIds: number[] = $ref([]);
-let currentStudentId = $ref<string | number>(0);
-const fileServer = ref("");
-
-async function updateSetting() {
-  const settingRes = await getInspectedSettingOfImportInspect(
-    studentId as string
-  );
-  const { examType, fileServer, doubleTrack } = settingRes.data;
-  store.initSetting({ examType, fileServer, doubleTrack } as AdminPageSetting);
-  // store.status.totalCount = settingRes.data.inspectCount;
-  // store.status.markedCount = 0;
-
-  // if (!settingRes.data.inspectCount) {
-  //   store.message = settingRes.data.message;
-  // } else {
-  if (studentId) {
-    studentIds = [studentId];
-  } else {
-    studentIds = settingRes.data.studentIds || [];
-  }
-  if (!studentIds.length) {
-    await message.warning("没有数据需要校验");
-  }
-  // tagIds = settingRes.data.tagIds;
-  // }
-  return fileServer;
-}
-// 要通过fetchTask调用
-async function updateTask() {
-  if (!currentStudentId) {
-    return;
-  }
-  const mkey = "fetch_task_key";
-  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
-  let res = await getSingleInspectedTaskOfImportInspect("" + currentStudentId);
-  void message.success({
-    content: res.data.task?.studentId ? "获取成功" : "无任务",
-    key: mkey,
-  });
-  isCurrentTagged = !!res.data.flagged;
-  store.setting.subject.paperUrl = res.data.paperUrl
-    ? fileServer.value + res.data.paperUrl
-    : "";
-  if (res.data.task?.studentId) {
-    let rawTask = res.data.task;
-    store.currentTask = addFileServerPrefixToTask(rawTask);
-  } else {
-    store.message = res.data.message;
-  }
-}
-let isCurrentTagged = $ref(false);
-
-// const isCurrentTagged = $computed(() => tagIds.includes(currentStudentId));
-const isFirst = $computed(() => studentIds.indexOf(currentStudentId) === 0);
-const isLast = $computed(
-  () => studentIds.indexOf(currentStudentId) === studentIds.length - 1
-);
-
-async function fetchTask(next: boolean, init?: boolean) {
-  if (init) {
-    currentStudentId = studentIds[0];
-  } else if (isLast && next) {
-    return; // currentStudentId是最后一个不调用
-  } else if (isFirst && !next) {
-    return; // currentStudentId是第一个不调用
-  } else {
-    currentStudentId =
-      studentIds[studentIds.indexOf(currentStudentId) + (next ? 1 : -1)];
-  }
-  if (!currentStudentId) return; // 无currentStudentId不调用
-  store.status.totalCount = studentIds.length;
-  // store.status.markedCount = studentIds.indexOf(currentStudentId) + 1;
-  await updateTask();
-  if (!store.status.markedCountStuIds) {
-    store.status.markedCountStuIds = [currentStudentId];
-  } else {
-    store.status.markedCountStuIds = Array.from(
-      new Set([...store.status.markedCountStuIds, currentStudentId])
-    );
-  }
-  store.status.markedCount = store.status.markedCountStuIds.length;
-}
-onMounted(async () => {
-  fileServer.value = await updateSetting();
-  await fetchTask(true, true);
-});
-
-const saveTaskToServer = async () => {
-  const mkey = "save_task_key";
-  void message.loading({ content: "标记评卷任务...", key: mkey });
-  const res = await saveInspectedTaskOfImportInspect(
-    currentStudentId + "",
-    !isCurrentTagged + ""
-  );
-  if (res.data.success) {
-    void message.success({
-      content: isCurrentTagged ? "取消标记成功" : "标记成功",
-      key: mkey,
-      duration: 2,
-    });
-    isCurrentTagged = !isCurrentTagged;
-    // if (isCurrentTagged) {
-    //   tagIds.splice(tagIds.indexOf(currentStudentId), 1);
-    // } else {
-    //   tagIds.push(currentStudentId);
-    // }
-  } else {
-    console.log(res.data.message);
-    void message.error({ content: res.data.message, key: mkey, duration: 10 });
-  }
-};
-
-const renderError = () => {
-  store.currentTask = undefined;
-  store.message = "加载失败,请重新加载。";
-};
-</script>
-
-<style scoped>
-.my-container {
-  width: 100%;
-  overflow: clip;
-}
-</style>
+<template>
+  <div class="my-container">
+    <mark-header />
+    <div class="tw-flex tw-gap-1">
+      <mark-body origImageUrls="sheetUrls" @error="renderError" />
+      <MarkBoardInspect
+        :tagged="isCurrentTagged"
+        :isFirst="isFirst"
+        :isLast="isLast"
+        @makeTag="saveTaskToServer"
+        @fetchTask="fetchTask"
+      />
+    </div>
+  </div>
+  <MinimapModal />
+  <PaperModal />
+</template>
+
+<script setup lang="ts">
+import { onMounted, ref } from "vue";
+// import {
+//   getInspectedSettingOfImportInspect,
+//   getSingleInspectedTaskOfImportInspect,
+//   saveInspectedTaskOfImportInspect,
+// } from "@/api/importInspectPage";
+import {
+  getInspectedSettingOfImportInspect,
+  getSingleInspectedTaskOfImportInspect,
+  saveInspectedTaskOfImportInspect,
+} from "@/api/scoreVerify";
+import { store } from "@/store/store";
+import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
+import PaperModal from "@/features/mark/PaperModal.vue";
+import { useRoute } from "vue-router";
+// import MarkBody from "../studentInspect/MarkBody.vue";
+import MarkBody from "./markBody.vue";
+import MarkBoardInspect from "./MarkBoardInspect.vue";
+import type { AdminPageSetting } from "@/types";
+import { message } from "ant-design-vue";
+import { addFileServerPrefixToTask } from "@/utils/utils";
+
+const route = useRoute();
+const { studentId } = route.query as {
+  studentId: string | number;
+};
+
+let studentIds: (number | string)[] = $ref([]);
+// let tagIds: number[] = $ref([]);
+let currentStudentId = $ref<string | number>(0);
+const fileServer = ref("");
+
+async function updateSetting() {
+  const settingRes = await getInspectedSettingOfImportInspect(
+    studentId as string
+  );
+  const { examType, fileServer, doubleTrack, collationLabelList } =
+    settingRes.data;
+  store.initSetting({
+    examType,
+    fileServer,
+    doubleTrack,
+    collationLabelList,
+  } as AdminPageSetting);
+  // store.status.totalCount = settingRes.data.inspectCount;
+  // store.status.markedCount = 0;
+
+  // if (!settingRes.data.inspectCount) {
+  //   store.message = settingRes.data.message;
+  // } else {
+  if (studentId) {
+    studentIds = [studentId];
+  } else {
+    studentIds = settingRes.data.studentIds || [];
+  }
+  if (!studentIds.length) {
+    await message.warning("没有数据需要校验");
+  }
+  // tagIds = settingRes.data.tagIds;
+  // }
+  return fileServer;
+}
+// 要通过fetchTask调用
+async function updateTask() {
+  if (!currentStudentId) {
+    return;
+  }
+  const mkey = "fetch_task_key";
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  let res = await getSingleInspectedTaskOfImportInspect("" + currentStudentId);
+  void message.success({
+    content: res.data.task?.studentId ? "获取成功" : "无任务",
+    key: mkey,
+  });
+  isCurrentTagged = !!res.data.flagged;
+  store.setting.subject.paperUrl = res.data.paperUrl
+    ? fileServer.value + res.data.paperUrl
+    : "";
+  if (res.data.task?.studentId) {
+    let rawTask = res.data.task;
+    store.currentTask = addFileServerPrefixToTask(rawTask);
+  } else {
+    store.message = res.data.message;
+  }
+}
+let isCurrentTagged = $ref(false);
+
+// const isCurrentTagged = $computed(() => tagIds.includes(currentStudentId));
+const isFirst = $computed(() => studentIds.indexOf(currentStudentId) === 0);
+const isLast = $computed(
+  () => studentIds.indexOf(currentStudentId) === studentIds.length - 1
+);
+
+async function fetchTask(next: boolean, init?: boolean) {
+  if (init) {
+    currentStudentId = studentIds[0];
+  } else if (isLast && next) {
+    return; // currentStudentId是最后一个不调用
+  } else if (isFirst && !next) {
+    return; // currentStudentId是第一个不调用
+  } else {
+    currentStudentId =
+      studentIds[studentIds.indexOf(currentStudentId) + (next ? 1 : -1)];
+  }
+  if (!currentStudentId) return; // 无currentStudentId不调用
+  store.status.totalCount = studentIds.length;
+  // store.status.markedCount = studentIds.indexOf(currentStudentId) + 1;
+  await updateTask();
+  if (!store.status.markedCountStuIds) {
+    store.status.markedCountStuIds = [currentStudentId];
+  } else {
+    store.status.markedCountStuIds = Array.from(
+      new Set([...store.status.markedCountStuIds, currentStudentId])
+    );
+  }
+  store.status.markedCount = store.status.markedCountStuIds.length;
+}
+onMounted(async () => {
+  fileServer.value = await updateSetting();
+  await fetchTask(true, true);
+});
+
+const saveTaskToServer = async () => {
+  const mkey = "save_task_key";
+  void message.loading({ content: "标记评卷任务...", key: mkey });
+  const res = await saveInspectedTaskOfImportInspect(
+    currentStudentId + "",
+    !isCurrentTagged + ""
+  );
+  if (res.data.success) {
+    void message.success({
+      content: isCurrentTagged ? "取消标记成功" : "标记成功",
+      key: mkey,
+      duration: 2,
+    });
+    isCurrentTagged = !isCurrentTagged;
+    // if (isCurrentTagged) {
+    //   tagIds.splice(tagIds.indexOf(currentStudentId), 1);
+    // } else {
+    //   tagIds.push(currentStudentId);
+    // }
+  } else {
+    console.log(res.data.message);
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
+  }
+};
+
+const renderError = () => {
+  store.currentTask = undefined;
+  store.message = "加载失败,请重新加载。";
+};
+</script>
+
+<style scoped>
+.my-container {
+  width: 100%;
+  overflow: clip;
+}
+</style>

+ 7 - 1
src/features/student/studentInspect/MarkBody.vue

@@ -7,7 +7,12 @@
     <div v-if="!store.currentTask" class="tw-text-center">
       {{ store.message }}
     </div>
-    <div v-else :style="{ width: answerPaperScale }" class="tw-pt-2">
+    <div
+      v-else
+      :style="{ width: answerPaperScale, position: 'relative' }"
+      class="tw-pt-2"
+    >
+      <Abnormal />
       <div
         v-for="(item, index) in sliceImagesWithTrackList"
         :key="index"
@@ -41,6 +46,7 @@ import { useTimers } from "@/setups/useTimers";
 import { loadImage, addHeaderTrackColorAttr } from "@/utils/utils";
 import { dragImage } from "@/features/mark/use/draggable";
 import ZoomPaper from "@/components/ZoomPaper.vue";
+import Abnormal from "@/components/Abnormal.vue";
 
 interface SliceImage {
   url: string;

+ 2 - 0
src/features/student/studentInspect/StudentInspect.vue

@@ -113,6 +113,7 @@ async function updateSetting() {
     enableSplit,
     doubleTrack,
     inspectScroll,
+    collationLabelList,
   } = settingRes.data;
   store.initSetting({
     examType,
@@ -123,6 +124,7 @@ async function updateSetting() {
     enableSplit,
     doubleTrack,
     inspectScroll,
+    collationLabelList,
   });
   if (store.setting.subject?.paperUrl && store.isMultiMedia) {
     await getPaper(store);

+ 2 - 0
src/features/student/studentTrack/StudentTrack.vue

@@ -37,6 +37,7 @@ async function updateSetting() {
     splitConfig,
     enableSplit,
     doubleTrack,
+    collationLabelList,
   } = settingRes.data;
   store.initSetting({
     examType,
@@ -46,6 +47,7 @@ async function updateSetting() {
     splitConfig,
     enableSplit,
     doubleTrack,
+    collationLabelList,
   });
   if (store.setting.subject?.paperUrl && store.isMultiMedia) {
     await getPaper(store);

+ 1 - 1
src/main.ts

@@ -16,7 +16,7 @@ import filters from "@/filters";
 // import "ant-design-vue/dist/antd.css";
 // vite-plugin-components 不能引入message的style
 import "ant-design-vue/es/message/style/css.js";
-
+import "animate.css";
 import QmButton from "@/components/QmButton.vue";
 import QmDialog from "@/components/QmDialog.vue";
 

+ 1 - 0
src/store/store.ts

@@ -37,6 +37,7 @@ const initState: MarkStore = {
     startTime: 0,
     endTime: 0,
     selective: false,
+    collationLabelList: [],
   },
   status: <MarkStore["status"]>{},
   groups: [],

+ 8 - 0
src/types/index.ts

@@ -116,6 +116,8 @@ export interface Setting {
   inspectScroll?: boolean;
   /** 评卷员页头是否展示客观分 */
   showObjectiveScore?: boolean;
+  /**异常信息code映射表 */
+  collationLabelList?: any;
 }
 
 /** 科目信息(试卷和答案功能) */
@@ -151,6 +153,7 @@ export interface AdminPageSetting {
   enableSplit: boolean;
   doubleTrack?: boolean;
   inspectScroll?: boolean;
+  collationLabelList?: any;
 }
 
 export interface AdminPageSettingForImport extends AdminPageSetting {
@@ -220,6 +223,9 @@ interface RawTask {
   headerTagList?: any;
   arbitrateIndex?: string;
   afterRejectScoreList?: string;
+  arbitrateType?: "QUESTION" | "GROUP";
+  /**异常信息展示所需的code,去setting里找对应code的文案 */
+  collationLabelCode?: string;
 }
 
 export interface Task extends RawTask {
@@ -260,6 +266,8 @@ interface RawQuestion {
   selective?: any;
   hasSetUnselective?: any;
   selectivePart?: any;
+  /**该题目限制的轨迹数量 */
+  trackCount?: any;
 }
 export interface Question extends RawQuestion {
   /** question 在 task 里面的 index ,用来对应 scoreList 的 score */

+ 2 - 2
vite.config.ts

@@ -3,10 +3,10 @@ import vue from "@vitejs/plugin-vue";
 import ViteComponents from "unplugin-vue-components/vite";
 import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
 
-const SERVER_URL = "http://192.168.10.123:8000";
+// const SERVER_URL = "http://192.168.10.123:8000";
 // const SERVER_URL = "https://www.markingcloud.com";
 // const SERVER_URL = "http://192.168.11.103:8090";
-// const SERVER_URL = "http://192.168.11.201:8000";
+const SERVER_URL = "http://192.168.10.225";
 
 const path = require("path");