Michael Wang 3 жил өмнө
parent
commit
23526a5d92
63 өөрчлөгдсөн 385 нэмэгдсэн , 315 устгасан
  1. 15 3
      .eslintrc.js
  2. 2 1
      components.d.ts
  3. 1 0
      src/api/inspectPage.ts
  4. 1 0
      src/api/libraryInspectPage.ts
  5. 1 1
      src/api/markPage.ts
  6. 2 0
      src/components/PageError404.vue
  7. 7 3
      src/components/QmButton.vue
  8. 4 2
      src/components/QmDialog.vue
  9. 4 7
      src/components/ZoomPaper.vue
  10. 9 9
      src/features/arbitrate/Arbitrate.vue
  11. 4 2
      src/features/arbitrate/ArbitrateMarkList.vue
  12. 2 2
      src/features/arbitrate/MarkBody.vue
  13. 3 3
      src/features/arbitrate/MarkHeader.vue
  14. 15 15
      src/features/library/inspect/LibraryInspect.vue
  15. 9 9
      src/features/library/inspect/MarkBoardInspect.vue
  16. 2 2
      src/features/library/inspect/MarkBody.vue
  17. 3 3
      src/features/library/inspect/MarkHeader.vue
  18. 2 2
      src/features/library/libraryTrack/LibraryTrack.vue
  19. 2 2
      src/features/library/libraryTrack/MarkBody.vue
  20. 3 3
      src/features/library/libraryTrack/MarkHeader.vue
  21. 2 2
      src/features/library/quality/MarkBody.vue
  22. 3 3
      src/features/library/quality/MarkHeader.vue
  23. 3 3
      src/features/library/quality/Quality.vue
  24. 1 1
      src/features/mark/AllPaperModal.vue
  25. 26 19
      src/features/mark/CommonMarkBody.vue
  26. 35 23
      src/features/mark/Mark.vue
  27. 12 12
      src/features/mark/MarkBoardKeyBoard.vue
  28. 3 3
      src/features/mark/MarkBoardMouse.vue
  29. 8 7
      src/features/mark/MarkBoardTrack.vue
  30. 6 2
      src/features/mark/MarkBody.vue
  31. 12 12
      src/features/mark/MarkChangeProfile.vue
  32. 3 3
      src/features/mark/MarkDrawTrack.vue
  33. 11 9
      src/features/mark/MarkHeader.vue
  34. 50 42
      src/features/mark/MarkHistory.vue
  35. 9 9
      src/features/mark/MarkProblemDialog.vue
  36. 8 5
      src/features/mark/MarkSwitchGroupDialog.vue
  37. 1 1
      src/features/mark/MinimapModal.vue
  38. 8 6
      src/features/mark/MultiMediaMarkBody.vue
  39. 1 1
      src/features/mark/SheetViewModal.vue
  40. 6 6
      src/features/mark/SpecialTagModal.vue
  41. 2 1
      src/features/mark/use/autoChooseFirstQuestion.ts
  42. 1 1
      src/features/mark/use/focusTracks.ts
  43. 4 4
      src/features/mark/use/splitPane.ts
  44. 8 8
      src/features/student/importInspect/ImportInspect.vue
  45. 3 3
      src/features/student/importInspect/MarkBoardInspect.vue
  46. 3 3
      src/features/student/importInspect/MarkHeader.vue
  47. 14 14
      src/features/student/inspect/Inspect.vue
  48. 9 9
      src/features/student/inspect/MarkBoardInspect.vue
  49. 3 3
      src/features/student/inspect/MarkBody.vue
  50. 1 1
      src/features/student/inspect/MarkDrawTrack.vue
  51. 3 3
      src/features/student/inspect/MarkHeader.vue
  52. 3 3
      src/features/student/studentTrack/MarkHeader.vue
  53. 4 4
      src/features/student/studentTrack/StudentTrack.vue
  54. 3 3
      src/main.ts
  55. 7 6
      src/plugins/axiosApp.ts
  56. 5 4
      src/plugins/axiosNoAuth.ts
  57. 1 1
      src/plugins/axiosNotice.ts
  58. 5 1
      src/plugins/eventBus.ts
  59. 1 1
      src/router/index.ts
  60. 2 0
      src/setups/useTimers.ts
  61. 1 1
      src/types/global.d.ts
  62. 2 2
      src/utils/ua.ts
  63. 1 1
      src/utils/utils.ts

+ 15 - 3
.eslintrc.js

@@ -10,7 +10,7 @@ module.exports = {
   },
   parser: "@typescript-eslint/parser",
   parserOptions: {
-    ecmaVersion: "2022",
+    ecmaVersion: "2021",
     sourceType: "module",
     ecmaFeatures: {
       jsx: true,
@@ -28,9 +28,20 @@ module.exports = {
     "prettier",
   ],
   rules: {
-    // "vue/script-setup-uses-vars": "error",
+    // 和 $ref let 冲突
+    "prefer-const": "off",
+    "@typescript-eslint/no-explicit-any": "off",
+    "@typescript-eslint/ban-ts-comment": "off",
+    "@typescript-eslint/no-unsafe-assignment": "off",
+    "@typescript-eslint/no-unsafe-member-access": "off",
+    "@typescript-eslint/restrict-plus-operands": "off",
+    "@typescript-eslint/restrict-template-expressions": "off",
+    "@typescript-eslint/no-non-null-assertion": "off",
+    "@typescript-eslint/no-empty-function": "off",
+    "vue/v-on-event-hyphenation": "off",
+    "vue/no-v-html": "off",
   },
-  // ignorePatterns: [".eslintrc.js"],
+  ignorePatterns: [".eslintrc.js", "src/test"],
   overrides: [
     {
       files: ["src/**/**.vue"],
@@ -53,6 +64,7 @@ module.exports = {
       globals: {
         $ref: true,
         $computed: true,
+        $$: true,
       },
     },
   ],

+ 2 - 1
components.d.ts

@@ -4,7 +4,6 @@
 
 declare module 'vue' {
   export interface GlobalComponents {
-    404: typeof import('./src/components/404.vue')['default']
     AButton: typeof import('ant-design-vue/es')['Button']
     AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
     ADropdown: typeof import('ant-design-vue/es')['Dropdown']
@@ -20,6 +19,8 @@ declare module 'vue' {
     ASpin: typeof import('ant-design-vue/es')['Spin']
     ASwitch: typeof import('ant-design-vue/es')['Switch']
     ATooltip: typeof import('ant-design-vue/es')['Tooltip']
+    Page404: typeof import('./src/components/Page404.vue')['default']
+    PageError404: typeof import('./src/components/PageError404.vue')['default']
     QmButton: typeof import('./src/components/QmButton.vue')['default']
     QmDialog: typeof import('./src/components/QmDialog.vue')['default']
     ZoomPaper: typeof import('./src/components/ZoomPaper.vue')['default']

+ 1 - 0
src/api/inspectPage.ts

@@ -118,6 +118,7 @@ export async function rejectInspectedTask(
 ) {
   questionList = JSON.parse(
     JSON.stringify(questionList, (key, value) =>
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       !key.startsWith("__") ? value : undefined
     )
   );

+ 1 - 0
src/api/libraryInspectPage.ts

@@ -78,6 +78,7 @@ export async function rejectInspectedTask(
 ) {
   questionList = JSON.parse(
     JSON.stringify(questionList, (key, value) =>
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-return
       !key.startsWith("__") ? value : undefined
     )
   );

+ 1 - 1
src/api/markPage.ts

@@ -83,7 +83,7 @@ export async function changeUserInfo(name: string, password?: string) {
 }
 
 /** 评卷用户退出 */
-export async function doLogout() {
+export function doLogout() {
   window.location.href = "/mark/logout";
 }
 

+ 2 - 0
src/components/404.vue → src/components/PageError404.vue

@@ -1,3 +1,5 @@
 <template>
   <div class="tw-text-center tw-text-3xl">页面没找到(404)</div>
 </template>
+
+<script setup lang="ts"></script>

+ 7 - 3
src/components/QmButton.vue

@@ -1,8 +1,8 @@
 <template>
   <!-- FIXED: 从 vue 3.1.5 升级到 3.2.1 时这里出错了,slot也要有key -->
   <a-button v-bind="newAttrs" :loading="inInterval" @click="insideClick">
-    <template v-for="(_, slot) of $slots" v-slot:[slot]="scope">
-      <slot :name="asAny(slot)" v-bind="asAny(scope)" :key="slot" />
+    <template v-for="(_, slot) of $slots" #[slot]="scope" :key="slot">
+      <slot v-bind="asAny(scope)" :key="slot" :name="asAny(slot)" />
     </template>
     <!-- <slot name="default" /> -->
   </a-button>
@@ -29,16 +29,20 @@ export default {
     let inInterval = $ref(false);
     const insideClick = (e: MouseEvent) => {
       inInterval = true;
+      // false warning
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
       setTimeout(() => (inInterval = false), props.clickTimeout);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-call
       parentOnClick(e);
       // 确保焦点不停留在此处,以免Enter键触发
       // @ts-ignore
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-call
       e.target?.blur();
     };
     // newAttrs.onClick = insideClick;
 
     function asAny(input: any): any {
-      return input as any;
+      return input;
     }
     return { newAttrs, inInterval, insideClick, asAny };
   },

+ 4 - 2
src/components/QmDialog.vue

@@ -45,6 +45,8 @@ import { onMounted, onUpdated, reactive } from "vue";
 import { CloseOutlined } from "@ant-design/icons-vue";
 import { store } from "@/store/store";
 
+// 因为要更改props取得的值,所以不需要reactivity
+// eslint-disable-next-line vue/no-setup-props-destructure
 const { top, width, height, title, zIndex } = withDefaults(
   defineProps<{
     title: string;
@@ -119,7 +121,7 @@ const handleDragMouseMove = (e: MouseEvent) => {
   mousePosition.offsetX = clientX;
   mousePosition.offsetY = clientY;
 };
-const handleDragMouseUp = (e: MouseEvent) => {
+const handleDragMouseUp = () => {
   mousePosition.offsetX = 0;
   mousePosition.offsetY = 0;
   mouseHandler.removeEventListener("mousedown", handleDragMouseDown);
@@ -146,7 +148,7 @@ const handleResizeMouseMove = (e: MouseEvent) => {
   mousePosition.offsetX = clientX;
   mousePosition.offsetY = clientY;
 };
-const handleResizeMouseUp = (e: MouseEvent) => {
+const handleResizeMouseUp = () => {
   mousePosition.offsetX = 0;
   mousePosition.offsetY = 0;
   resizeHandler.removeEventListener("mousedown", handleResizeMouseDown);

+ 4 - 7
src/components/ZoomPaper.vue

@@ -7,37 +7,34 @@
       :style="{
         color: greaterThanOneScale ? 'red' : 'white',
       }"
-      @click="upScale"
       title="放大"
+      @click="upScale"
     />
     <ZoomOutOutlined
       class="icon-font-size-20 tw-cursor-pointer"
       :style="{
         color: lessThanOneScale ? 'red' : 'white',
       }"
-      @click="downScale"
       title="缩小"
+      @click="downScale"
     />
     <FullscreenOutlined
       class="icon-font-size-20 tw-cursor-pointer"
       style="color: white"
-      @click="normalScale"
       title="适应"
+      @click="normalScale"
     />
   </div>
 </template>
 
 <script setup lang="ts">
-import type { MarkStore } from "@/types";
 import {
   ZoomInOutlined,
   ZoomOutOutlined,
   FullscreenOutlined,
 } from "@ant-design/icons-vue";
 import { computed } from "vue";
-
-const props = defineProps<{ store: MarkStore }>();
-const { store } = props;
+import { store } from "@/store/store";
 
 const upScale = () => {
   const s = store.setting.uiSetting["answer.paper.scale"];

+ 9 - 9
src/features/arbitrate/Arbitrate.vue

@@ -4,8 +4,8 @@
     <div class="tw-flex tw-gap-1">
       <mark-history
         v-if="!isSingleStudent"
-        :subjectCode="subjectCode"
-        :groupNumber="groupNumber"
+        :subject-code="subjectCode"
+        :group-number="groupNumber"
         :get-history="getArbitrateHistory"
       />
       <ArbitrateMarkList />
@@ -98,14 +98,14 @@ async function updateStatus() {
 }
 async function updateTask() {
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res;
   if (isSingleStudent) {
     res = await getSingleStuTask();
   } else {
     res = await getOneOfStuTask();
   }
-  message.success({
+  void message.success({
     content: res.data.studentId ? "获取成功" : "无任务",
     key: mkey,
   });
@@ -160,7 +160,7 @@ const saveTaskToServer = async (unselective: boolean) => {
   if (!store.currentTask) return;
   console.log("save inspect task to server");
   const mkey = "save_task_key";
-  message.loading({ content: "保存评卷任务...", key: mkey });
+  void message.loading({ content: "保存评卷任务...", key: mkey });
   let res;
   if (unselective) {
     res = (await saveArbitrateTask(
@@ -180,18 +180,18 @@ const saveTaskToServer = async (unselective: boolean) => {
     )) as any;
   }
   if (res.data.success && store.currentTask) {
-    message.success({ content: "仲裁成功", key: mkey, duration: 2 });
+    void message.success({ content: "仲裁成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
-      if (!isSingleStudent) fetchTask();
+      if (!isSingleStudent) await fetchTask();
     } else {
       EventBus.emit("should-reload-history");
     }
   } else if (res.data.message) {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 

+ 4 - 2
src/features/arbitrate/ArbitrateMarkList.vue

@@ -56,8 +56,10 @@ watch(
       const res = await getArbitrateList(
         store.currentTask?.libraryId as unknown as string
       );
-      list.splice(0);
-      list.push(...res.data);
+      if (Array.isArray(res.data)) {
+        list.splice(0);
+        list.push(...(<Array<MarkDetail>>res.data));
+      }
     } else {
       list.splice(0);
     }

+ 2 - 2
src/features/arbitrate/MarkBody.vue

@@ -1,10 +1,10 @@
 <template>
   <CommonMarkBody
     v-if="store"
-    :useMarkResult="false"
+    :use-mark-result="false"
     :store="store"
+    :make-track="() => {}"
     @error="$emit('error')"
-    :makeTrack="() => {}"
   />
 </template>
 

+ 3 - 3
src/features/arbitrate/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       v-if="!isSingleStudent"
@@ -54,7 +54,7 @@
       </span>
     </div>
     <div class="tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <a-popover
       v-if="store.isScanImage"
       title="小助手"
@@ -171,7 +171,7 @@ const closeWindow = async () => {
 onMounted(() => {
   // 不确定是否一定能在关闭页面时调用
   window.addEventListener("beforeunload", () => {
-    updateClearTask();
+    updateClearTask().catch((e) => console.log(e));
   });
 });
 </script>

+ 15 - 15
src/features/library/inspect/LibraryInspect.vue

@@ -4,9 +4,9 @@
     <div class="tw-flex tw-gap-1">
       <mark-history
         v-if="!isSingleStudent"
-        :subjectCode="subjectCode"
-        :groupNumber="groupNumber"
-        orderTimeField="inspectTime"
+        :subject-code="subjectCode"
+        :group-number="groupNumber"
+        order-time-field="inspectTime"
         :get-history="getLibraryInspectedHistory"
       />
       <mark-body @error="renderError" />
@@ -73,9 +73,9 @@ async function updateStatus() {
 }
 async function updateTask() {
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res = await getOneOfStuTask();
-  message.success({
+  void message.success({
     content: res.data.studentId ? "获取成功" : "无任务",
     key: mkey,
   });
@@ -119,46 +119,46 @@ let realLibraryId = $computed(
 const saveTaskToServer = async () => {
   console.log("save inspect task to server");
   const mkey = "save_task_key";
-  message.loading({ content: "保存评卷任务...", key: mkey });
+  void message.loading({ content: "保存评卷任务...", key: mkey });
   const res = (await saveInspectedTask(realLibraryId)) as any;
   if (res.data.success && store.currentTask) {
-    message.success({ content: "复核成功", key: mkey, duration: 2 });
+    void message.success({ content: "复核成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
-      if (!isSingleStudent) fetchTask();
+      if (!isSingleStudent) await fetchTask();
     } else {
       EventBus.emit("should-reload-history");
     }
   } else if (res.data.message) {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 
 const rejectQuestions = async (questions: Array<Question>) => {
   if (!store.currentTask) return;
   const mkey = "reject_task_key";
-  message.loading({ content: "打回评卷任务...", key: mkey });
+  void message.loading({ content: "打回评卷任务...", key: mkey });
   const res = await rejectInspectedTask(
     store.currentTask.libraryId + "",
     questions
   );
   if (res.data.success) {
     store.currentTask = undefined;
-    message.success({ content: "打回成功", key: mkey, duration: 2 });
+    void message.success({ content: "打回成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
-      if (!isSingleStudent) fetchTask();
+      if (!isSingleStudent) await fetchTask();
     } else {
       EventBus.emit("should-reload-history");
     }
   } else if (res.data.message) {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 

+ 9 - 9
src/features/library/inspect/MarkBoardInspect.vue

@@ -19,7 +19,7 @@
       </div>
     </div>
 
-    <div class="tw-flex-grow tw-overflow-auto tw-my-5" v-if="groups">
+    <div v-if="groups" class="tw-flex-grow tw-overflow-auto tw-my-5">
       <template v-for="(groupNumber, index) in groups" :key="index">
         <div class="tw-mb-4 tw-bg-white tw-p-4">
           <div
@@ -35,12 +35,12 @@
               class="tw-my-auto"
               title="打回"
               type="checkbox"
-              @click="groupClicked(groupNumber)"
               :checked="groupChecked(groupNumber)"
+              @click="groupClicked(groupNumber)"
             />
           </div>
           <div v-if="questions">
-            <template v-for="(question, index) in questions" :key="index">
+            <template v-for="(question, index2) in questions" :key="index2">
               <div
                 v-if="question.groupNumber === groupNumber"
                 class="
@@ -69,8 +69,8 @@
                   :disabled="question.score === -1"
                   title="打回"
                   type="checkbox"
-                  @change="questionCheckChanged(question)"
                   :checked="questionChecked(question)"
+                  @change="questionCheckChanged(question)"
                 />
               </div>
             </template>
@@ -81,28 +81,28 @@
 
     <div class="tw-flex tw-flex-shrink-0 tw-justify-center">
       <qm-button
-        type="primary"
-        class="full-width-btn undo-btn"
         v-if="
           store.currentTask.inspectTime && store.currentTask.inspectTime > 0
         "
+        type="primary"
+        class="full-width-btn undo-btn"
         @click="reject"
       >
         打回
       </qm-button>
       <qm-button
         v-else-if="checkedQuestions.length === 0"
-        @click="inspect"
         type="primary"
         class="full-width-btn"
+        @click="inspect"
       >
         复核
       </qm-button>
       <qm-button
         v-else
-        @click="reject"
         type="primary"
         class="full-width-btn undo-btn"
+        @click="reject"
         >打回</qm-button
       >
     </div>
@@ -186,7 +186,7 @@ function groupClicked(groupNumber: number) {
 
 function reject() {
   if (checkedQuestions.length === 0) {
-    message.warn({ content: "请先选择试题。" });
+    void message.warn({ content: "请先选择试题。" });
     return;
   }
   emit("reject", checkedQuestions);

+ 2 - 2
src/features/library/inspect/MarkBody.vue

@@ -1,10 +1,10 @@
 <template>
   <CommonMarkBody
     v-if="store"
-    :useMarkResult="false"
+    :use-mark-result="false"
     :store="store"
+    :make-track="() => {}"
     @error="$emit('error')"
-    :makeTrack="() => {}"
   />
 </template>
 

+ 3 - 3
src/features/library/inspect/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       v-if="!isSingleStudent"
@@ -53,7 +53,7 @@
         }}</span>
       </span>
     </div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <div class="tw-flex tw-place-items-center tw-justify-end tw-ml-auto">
       {{ store.setting.userName }}
     </div>
@@ -118,7 +118,7 @@ const closeWindow = async () => {
 onMounted(() => {
   // 不确定是否一定能在关闭页面时调用
   window.addEventListener("beforeunload", () => {
-    updateClearTask();
+    updateClearTask().catch((e) => console.log(e));
   });
 });
 </script>

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

@@ -46,9 +46,9 @@ async function updateSetting() {
 
 async function updateTask() {
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res = await getSingleStuTask();
-  message.success({
+  void message.success({
     content: res.data.task.studentId ? "获取成功" : "无任务",
     key: mkey,
   });

+ 2 - 2
src/features/library/libraryTrack/MarkBody.vue

@@ -1,10 +1,10 @@
 <template>
   <CommonMarkBody
     v-if="store"
-    :useMarkResult="false"
+    :use-mark-result="false"
     :store="store"
+    :make-track="() => {}"
     @error="$emit('error')"
-    :makeTrack="() => {}"
   />
 </template>
 

+ 3 - 3
src/features/library/libraryTrack/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       class="
@@ -29,7 +29,7 @@
       </div>
     </div>
     <div class="tw-flex tw-gap-2 tw-items-center tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <div class="tw-flex tw-cursor-pointer tw-items-center tw-flex-1">
       <div
         class="
@@ -60,7 +60,7 @@ import { store } from "@/store/store";
 import { UserOutlined, PoweroffOutlined } from "@ant-design/icons-vue";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 
-const closeWindow = async () => {
+const closeWindow = () => {
   window.close();
 };
 </script>

+ 2 - 2
src/features/library/quality/MarkBody.vue

@@ -1,10 +1,10 @@
 <template>
   <CommonMarkBody
     v-if="store"
-    :useMarkResult="false"
+    :use-mark-result="false"
     :store="store"
+    :make-track="() => {}"
     @error="$emit('error')"
-    :makeTrack="() => {}"
   />
 </template>
 

+ 3 - 3
src/features/library/quality/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       class="
@@ -29,7 +29,7 @@
       </div>
     </div>
     <div class="tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <a-popover
       v-if="store.isScanImage"
       title="小助手"
@@ -94,7 +94,7 @@ import {
 } from "@ant-design/icons-vue";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 
-const closeWindow = async () => {
+const closeWindow = () => {
   window.close();
 };
 </script>

+ 3 - 3
src/features/library/quality/Quality.vue

@@ -4,9 +4,9 @@
     <div class="tw-flex tw-gap-1">
       <mark-history
         title="给分记录"
-        :subjectCode="subjectCode"
-        :markerId="markerId"
-        :markerScore="markerScore"
+        :subject-code="subjectCode"
+        :marker-id="markerId"
+        :marker-score="markerScore"
         :get-history="getQualityHistory"
       />
       <mark-body @error="renderError" />

+ 1 - 1
src/features/mark/AllPaperModal.vue

@@ -7,9 +7,9 @@
           <span
             v-for="(u, index) in urls"
             :key="index"
-            @click="checkedIndex = index"
             class="image-index hover:tw-bg-gray-300"
             :class="checkedIndex === index && 'tw-bg-gray-300'"
+            @click="checkedIndex = index"
           >
             {{ index + 1 }}
           </span>

+ 26 - 19
src/features/mark/CommonMarkBody.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="mark-body-container tw-flex-auto tw-p-2 tw-relative"
     ref="dragContainer"
+    class="mark-body-container tw-flex-auto tw-p-2 tw-relative"
   >
     <div
       v-if="!store.currentTask"
@@ -26,8 +26,8 @@
       >
         <img
           :src="item.url"
-          @click="(event) => innerMakeTrack(event, item)"
           draggable="false"
+          @click="(event) => innerMakeTrack(event, item)"
           @contextmenu="showBigImage"
         />
         <MarkDrawTrack
@@ -49,7 +49,7 @@
       {{ markStatus }}
       <div class="double-triangle"></div>
     </div>
-    <ZoomPaper v-if="store.isScanImage && store.currentTask" :store="store" />
+    <ZoomPaper v-if="store.isScanImage && store.currentTask" />
   </div>
   <slot name="slot-cursor" />
 </template>
@@ -77,16 +77,23 @@ import "viewerjs/dist/viewer.css";
 import Viewer from "viewerjs";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 
-const props = defineProps<{
-  useMarkResult?: boolean;
-  makeTrack: Function;
-  store: MarkStore; // 以前不是一个类型的store,现在变成一样的了,所以这个可以删掉了,并且可以优化这个组件的使用
-}>();
+type MakeTrack = (
+  event: MouseEvent,
+  item: SliceImage,
+  maxSliceWidth: number,
+  theFinalHeight: number
+) => void | (() => void);
+const props = withDefaults(
+  defineProps<{
+    useMarkResult?: boolean;
+    makeTrack: MakeTrack;
+    store: MarkStore; // 以前不是一个类型的store,现在变成一样的了,所以这个可以删掉了,并且可以优化这个组件的使用
+  }>(),
+  { useMarkResult: false, makeTrack: () => {} }
+);
 
 const emit = defineEmits(["error"]);
 
-const { useMarkResult = false, makeTrack } = props;
-
 // start: 图片拖动。在轨迹模式下,仅当没有选择分数时可用。
 const { dragContainer } = dragImage();
 // end: 图片拖动
@@ -149,7 +156,7 @@ let theFinalHeight = 0; // 最终宽度,用来定位轨迹在第几张图片
 
 async function processSliceConfig() {
   let markResult = store.currentTask?.markResult as MarkResult;
-  if (useMarkResult) {
+  if (props.useMarkResult) {
     // check if have MarkResult for currentTask
     if (!markResult) return;
   }
@@ -192,7 +199,7 @@ async function processSliceConfig() {
     );
 
     let trackLists = [] as Array<Track>;
-    if (useMarkResult) {
+    if (props.useMarkResult) {
       trackLists = markResult.trackList;
     } else {
       trackLists = store.currentTask.questionList
@@ -207,7 +214,7 @@ async function processSliceConfig() {
     );
 
     let tagLists = [] as Array<SpecialTag>;
-    if (useMarkResult) {
+    if (props.useMarkResult) {
       tagLists = markResult.specialTagList ?? [];
     } else {
       tagLists = store.currentTask.specialTagList ?? [];
@@ -255,7 +262,7 @@ async function processSliceConfig() {
 
 async function processSplitConfig() {
   let markResult = store.currentTask?.markResult as MarkResult;
-  if (useMarkResult) {
+  if (props.useMarkResult) {
     // check if have MarkResult for currentTask
     if (!markResult) return;
   }
@@ -323,7 +330,7 @@ async function processSplitConfig() {
       );
 
       let trackLists = [] as Array<Track>;
-      if (useMarkResult) {
+      if (props.useMarkResult) {
         trackLists = markResult.trackList;
       } else {
         // 成绩查询 questionList 可能为空
@@ -339,7 +346,7 @@ async function processSplitConfig() {
       );
 
       let tagLists = [] as Array<SpecialTag>;
-      if (useMarkResult) {
+      if (props.useMarkResult) {
         tagLists = markResult.specialTagList ?? [];
       } else {
         tagLists = store.currentTask.specialTagList ?? [];
@@ -403,7 +410,7 @@ const renderPaperAndMark = async () => {
   // check if have MarkResult for currentTask
   let markResult = store.currentTask?.markResult;
 
-  if ((useMarkResult && !markResult) || !store.currentTask) {
+  if ((props.useMarkResult && !markResult) || !store.currentTask) {
     renderLock = false;
     return;
   }
@@ -479,7 +486,7 @@ let answerPaperScale = $computed(() => {
 
 // start: 显示评分状态和清除轨迹
 let markStatus = $ref("");
-if (useMarkResult) {
+if (props.useMarkResult) {
   watch(
     () => store.currentTask,
     () => {
@@ -528,7 +535,7 @@ if (useMarkResult) {
 
 // start: 评分
 const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
-  makeTrack && makeTrack(event, item, maxSliceWidth, theFinalHeight);
+  props.makeTrack(event, item, maxSliceWidth, theFinalHeight);
 };
 // end: 评分
 

+ 35 - 23
src/features/mark/Mark.vue

@@ -151,7 +151,8 @@ addInterval(() => {
   if (!preDrawing) {
     if (store.tasks.length < (store.setting.prefetchCount ?? 3)) {
       // 回看打开时,停止取任务
-      if (!store.historyOpen) updateTask();
+      if (!store.historyOpen)
+        updateTask().catch((e) => console.log("定时获取任务出错", e));
     }
   }
 }, 5 * 1000);
@@ -170,7 +171,9 @@ onMounted(async () => {
 });
 
 const __debounceUpdate = debounce(() => {
-  updateUISetting(store.setting.mode, store.setting.uiSetting);
+  updateUISetting(store.setting.mode, store.setting.uiSetting).catch((e) =>
+    console.log("保存设置出错", e)
+  );
 }, 3000);
 watch(
   () => [store.setting.uiSetting, store.setting.mode],
@@ -232,7 +235,7 @@ const unselectiveSubmit = async () => {
   try {
     const res = await doUnselectiveType();
     if (res?.data.success) {
-      message.success({ content: "未选做处理成功", duration: 3 });
+      void message.success({ content: "未选做处理成功", duration: 3 });
       if (!store.historyOpen) {
         store.currentTask = undefined;
         store.tasks.shift();
@@ -241,42 +244,51 @@ const unselectiveSubmit = async () => {
       if (store.historyOpen) {
         EventBus.emit("should-reload-history");
       }
-      updateStatus();
+      await updateStatus();
     } else {
-      message.error({ content: res?.data.message || "错误", duration: 5 });
+      void message.error({ content: res?.data.message || "错误", duration: 5 });
     }
   } catch (error) {
     console.log("未选做处理失败", error);
-    message.error({ content: "网络异常", duration: 5 });
+    void message.error({ content: "网络异常", duration: 5 });
     await new Promise((res) => setTimeout(res, 1500));
     window.location.reload();
   }
 };
+
 const saveTaskToServer = async () => {
-  const markResult = store.currentTask?.markResult;
+  if (!store.currentTask) return;
+  const markResult = store.currentTask.markResult;
   if (!markResult) return;
 
   const mkey = "save_task_key";
 
-  const errors = markResult.scoreList.reduce((p, c, index) => {
-    const question = store.currentTask?.questionList[index]!;
+  type SubmitError = {
+    question: Question;
+    index: number;
+    error: string;
+  };
+
+  const errors = [] as Array<SubmitError>;
+  markResult.scoreList.forEach((score, index) => {
+    if (!store.currentTask) return;
+    const question = store.currentTask.questionList[index]!;
     let error;
-    if (!isNumber(c)) {
+    if (!isNumber(score)) {
       error = `${question.mainNumber}-${question.subNumber} 没有赋分不能提交。`;
-    } else if (isNumber(question.maxScore) && c > question.maxScore) {
+    } else if (isNumber(question.maxScore) && score > question.maxScore) {
       error = `${question.mainNumber}-${question.subNumber} 赋分大于最高分不能提交。`;
-    } else if (isNumber(question.minScore) && c < question.minScore) {
+    } else if (isNumber(question.minScore) && score < question.minScore) {
       error = `${question.mainNumber}-${question.subNumber} 赋分小于最低分不能提交。`;
     }
     if (error) {
-      p.push({ question, index, error });
+      errors.push({ question, index, error });
     }
-    return p;
-  }, [] as Array<{ question: Question; index: number; error: string }>);
+  });
   if (errors.length !== 0) {
     console.log(errors);
     const msg = errors.map((v) => h("div", `${v.error}`));
-    message.warning({
+    void message.warning({
       content: h("span", ["校验失败", ...msg]),
       duration: 10,
       key: mkey,
@@ -300,7 +312,7 @@ const saveTaskToServer = async () => {
         .map((q) => Math.round((q.score || 0) * 100))
         .reduce((acc, s) => acc + s, 0) / 100;
     if (trackScores !== markResult.markerScore) {
-      message.error({
+      void message.error({
         content: "轨迹分与总分不一致,请检查。",
         duration: 3,
         key: mkey,
@@ -313,7 +325,7 @@ const saveTaskToServer = async () => {
       markResult.trackList.length === 0 &&
       markResult.specialTagList.length === 0
     ) {
-      message.error({
+      void message.error({
         content: "强制标记已开启,请至少使用一个标记。",
         duration: 5,
         key: mkey,
@@ -322,11 +334,11 @@ const saveTaskToServer = async () => {
     }
   }
   console.log("save task to server");
-  message.loading({ content: "保存评卷任务...", key: mkey });
+  void message.loading({ content: "保存评卷任务...", key: mkey });
   const res = (await saveTask()) as any;
-  updateStatus();
+  updateStatus().catch((e) => console.log("保存任务后获取status出错", e));
   if (res.data.success && store.currentTask) {
-    message.success({ content: "保存成功", key: mkey, duration: 2 });
+    void message.success({ content: "保存成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
       store.tasks.shift();
@@ -336,9 +348,9 @@ const saveTaskToServer = async () => {
     }
   } else if (res.data.message) {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 </script>

+ 12 - 12
src/features/mark/MarkBoardKeyBoard.vue

@@ -44,8 +44,8 @@
           <a-popconfirm
             v-if="store.setting.enableAllZero"
             title="确定给全零分?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('allZeroSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -58,8 +58,8 @@
           <a-popconfirm
             v-if="store.setting.selective"
             title="确定是未选做?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('unselectiveSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -89,18 +89,18 @@
         :key="index"
       >
         <div
+          :id="'bq-' + question.mainNumber + '-' + question.subNumber"
+          class="
+            question
+            tw-rounded tw-p-1 tw-mb-2 tw-cursor-pointer tw-relative
+          "
+          :class="isCurrentQuestion(question) && 'current-question'"
           @click="
             () => {
               chooseQuestion(question);
               scrollToQuestion(question);
             }
           "
-          class="
-            question
-            tw-rounded tw-p-1 tw-mb-2 tw-cursor-pointer tw-relative
-          "
-          :class="isCurrentQuestion(question) && 'current-question'"
-          :id="'bq-' + question.mainNumber + '-' + question.subNumber"
         >
           <div class="tw-flex tw-justify-between">
             <div>
@@ -232,7 +232,7 @@ function numberKeyListener(event: KeyboardEvent) {
     }
 
     if (scoreStr.length === 0) {
-      message.error({ content: "请输入分数", duration: 5 });
+      void message.error({ content: "请输入分数", duration: 5 });
       console.log("请输入分数");
       return;
     }
@@ -248,19 +248,19 @@ function numberKeyListener(event: KeyboardEvent) {
         /^[1-9]\d*\.\d*[1-9]$/.test(scoreStr)
       )
     ) {
-      message.error({ content: "分数格式不正确", duration: 5 });
+      void message.error({ content: "分数格式不正确", duration: 5 });
       console.log("分数格式不正确");
       return;
     }
     const score = parseFloat(scoreStr);
     // console.log(score);
     if (!isNumber(score)) {
-      message.error({ content: "非数字输入", duration: 5 });
+      void message.error({ content: "非数字输入", duration: 5 });
       console.log("非数字输入");
       return;
     }
     if (!questionScoreSteps.includes(score)) {
-      message.error({ content: "输入的分数不在有效间隔内", duration: 5 });
+      void message.error({ content: "输入的分数不在有效间隔内", duration: 5 });
       return;
     }
     const { __index } = store.currentQuestion;

+ 3 - 3
src/features/mark/MarkBoardMouse.vue

@@ -44,8 +44,8 @@
           <a-popconfirm
             v-if="store.setting.enableAllZero"
             title="确定给全零分?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('allZeroSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -58,8 +58,8 @@
           <a-popconfirm
             v-if="store.setting.selective"
             title="确定是未选做?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('unselectiveSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -107,9 +107,9 @@
               <div
                 v-for="(s, i) in questionScoreSteps(question)"
                 :key="i"
-                @click="chooseScore(question, s)"
                 class="single-score tw-cursor-pointer"
                 :class="isCurrentScore(question, s) && 'current-score'"
+                @click="chooseScore(question, s)"
               >
                 {{ s }}
               </div>

+ 8 - 7
src/features/mark/MarkBoardTrack.vue

@@ -29,8 +29,8 @@
           <a-popconfirm
             v-if="store.setting.enableAllZero && !store.setting.forceSpecialTag"
             title="确定给全零分?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('allZeroSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -43,8 +43,8 @@
           <a-popconfirm
             v-if="store.setting.selective"
             title="确定是未选做?"
+            :overlay-style="{ width: '200px' }"
             @confirm="$emit('unselectiveSubmit')"
-            :overlayStyle="{ width: '200px' }"
           >
             <a-button
               type="primary"
@@ -94,9 +94,9 @@
           :key="index"
         >
           <div
-            @click="chooseQuestion(question)"
             class="question tw-rounded tw-cursor-pointer tw-relative tw-mb-2"
             :class="isCurrentQuestion(question) && 'current-question'"
+            @click="chooseQuestion(question)"
             @mouseover="
               addFocusTrack(undefined, question.mainNumber, question.subNumber)
             "
@@ -109,6 +109,7 @@
             </div>
             <div class="tw-font-medium tw-text-2xl score">
               <!-- 特殊的空格符号 -->
+              <!-- eslint-disable-next-line no-irregular-whitespace -->
               {{ store.currentTask.markResult.scoreList[index] ?? " " }}
             </div>
           </div>
@@ -116,6 +117,7 @@
       </div>
 
       <div
+        ref="dragSpliter"
         style="
           width: 100%;
           height: 4px;
@@ -123,7 +125,6 @@
           background-color: grey;
           cursor: row-resize;
         "
-        ref="dragSpliter"
       ></div>
       <div
         class="tw-flex tw-flex-wrap tw-mt-5 tw-overflow-auto tw-content-start"
@@ -133,9 +134,9 @@
         <div
           v-for="(s, i) in questionScoreSteps"
           :key="i"
-          @click="chooseScore(s)"
           class="single-score tw-cursor-pointer tw-font-bold"
           :class="isCurrentScore(s) && 'current-score'"
+          @click="chooseScore(s)"
         >
           {{ s }}
         </div>
@@ -153,7 +154,7 @@
           background-color: var(--app-undo-button-bg-color);
           border-color: var(--app-undo-button-bg-color);
         "
-        :clickTimeout="300"
+        :click-timeout="300"
         @click="clearLatestMarkOfCurrentQuetion"
       >
         回退
@@ -163,7 +164,7 @@
         type="primary"
         shape="round"
         size="large"
-        :clickTimeout="300"
+        :click-timeout="300"
         @click="clearAllMarksOfCurrentQuetion"
       >
         清除本题

+ 6 - 2
src/features/mark/MarkBody.vue

@@ -1,8 +1,8 @@
 <template>
   <CommonMarkBody
-    :useMarkResult="true"
+    :use-mark-result="true"
     :store="store"
-    :makeTrack="makeTrack"
+    :make-track="makeTrack"
     @error="$emit('error')"
   />
   <div class="cursor">
@@ -164,11 +164,13 @@ watch(
     const shouldHide = store.setting.mode === ModeEnum.COMMON;
     if (shouldHide) {
       // console.log("hide cursor", theCursor);
+      // eslint-disable-next-line @typescript-eslint/no-unsafe-call
       theCursor && theCursor.destroy();
     } else {
       if (document.querySelector(".cursor")) {
         // console.log("show cursor", theCursor);
         // theCursor && theCursor.enable();
+        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
         theCursor = new CustomCursor(".cursor", {
           focusElements: [
             {
@@ -184,6 +186,7 @@ watch(
 let theCursor = null as any;
 onMounted(() => {
   if (store.isTrackMode) {
+    // eslint-disable-next-line @typescript-eslint/no-unsafe-call
     theCursor = new CustomCursor(".cursor", {
       focusElements: [
         {
@@ -196,6 +199,7 @@ onMounted(() => {
 });
 
 onUnmounted(() => {
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
   theCursor && theCursor.destroy();
 });
 </script>

+ 12 - 12
src/features/mark/MarkChangeProfile.vue

@@ -1,35 +1,35 @@
 <template>
   <a-modal
-    title="修改个人信息"
     v-model:visible="visible"
+    title="修改个人信息"
     :confirm-loading="confirmLoading"
+    :z-index="6000"
+    wrap-class-name="profile-wrapper"
     @ok="handleOk"
     @cancel="handleCancel"
-    :zIndex="6000"
-    wrapClassName="profile-wrapper"
   >
-    <a-form labelAlign="right" :labelCol="{ span: 4 }">
+    <a-form label-align="right" :label-col="{ span: 4 }">
       <a-form-item class="tw-mb-2" :required="true" label="姓名">
         <a-input
-          @keydown.stop="() => {}"
           v-model:value="user.name"
           placeholder="姓名"
+          @keydown.stop="() => {}"
         />
       </a-form-item>
       <a-form-item class="tw-mb-2" label="新密码">
         <a-input
-          @keydown.stop="() => {}"
-          type="password"
           v-model:value="user.password"
+          type="password"
           placeholder="输入新密码"
+          @keydown.stop="() => {}"
         />
       </a-form-item>
       <a-form-item class="tw-mb-2" label="确认新密码">
         <a-input
-          @keydown.stop="() => {}"
-          type="password"
           v-model:value="user.confirmPassword"
+          type="password"
           placeholder="再次输入新密码"
+          @keydown.stop="() => {}"
         />
       </a-form-item>
     </a-form>
@@ -64,16 +64,16 @@ const showModal = () => {
 
 const handleOk = () => {
   if (user.name.length === 0 || user.name.length >= 30) {
-    message.error({ content: "姓名长度必须在1到30之间" });
+    void message.error({ content: "姓名长度必须在1到30之间" });
     return;
   }
   if (user.password.length !== 0 || user.confirmPassword.length !== 0) {
     if (user.password.length === 0 || user.password.length >= 16) {
-      message.error({ content: "密码长度必须在1到16之间" });
+      void message.error({ content: "密码长度必须在1到16之间" });
       return;
     }
     if (user.password !== user.confirmPassword) {
-      message.error({ content: "两次输入的密码不一致" });
+      void message.error({ content: "两次输入的密码不一致" });
       return;
     }
   }

+ 3 - 3
src/features/mark/MarkDrawTrack.vue

@@ -1,16 +1,16 @@
 <template>
   <transition-group name="track-score" tag="div">
-    <template v-for="(track, index) in trackList">
+    <template v-for="track in trackList">
       <div
         v-if="store.shouldShowTrack"
+        :key="`key-${track.mainNumber}-${track.subNumber}-${track.offsetY}-${track.offsetX}`"
         class="score-container"
         :class="[focusedTrack(track) && 'score-animation']"
         :style="computeTopAndLeft(track)"
-        :key="`key-${track.mainNumber}-${track.subNumber}-${track.offsetY}-${track.offsetX}`"
       >
         <span
-          class="tw-m-auto"
           :id="`a-${track.mainNumber}-${track.subNumber}-${track.offsetY}-${track.offsetX}`"
+          class="tw-m-auto"
         >
           {{ track.score }}
         </span>

+ 11 - 9
src/features/mark/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-2 tw-justify-between tw-items-center header-container"
     v-if="store.setting && store.setting.subject.name"
+    class="tw-flex tw-gap-2 tw-justify-between tw-items-center header-container"
   >
     <a-tooltip>
       <template #title>回评</template>
@@ -75,9 +75,9 @@
         <span class="header-small-text">已评</span>
         <transition-group name="count-animation" tag="span">
           <span
+            :key="store.status.personCount || 0"
             class="highlight-text"
             style="display: block"
-            :key="store.status.personCount || 0"
           >
             {{ store.status.personCount }}
           </span>
@@ -91,9 +91,9 @@
         <span class="header-small-text">未评</span>
         <transition-group name="count-animation" tag="span">
           <span
+            :key="todoCount || 0"
             class="highlight-text"
             style="display: block"
-            :key="todoCount || 0"
           >
             {{ todoCount }}
           </span>
@@ -103,9 +103,9 @@
         <span class="header-small-text">进度</span>
         <transition-group name="count-animation" tag="span">
           <span
+            :key="progress || '-'"
             class="highlight-text"
             style="display: block"
-            :key="progress || '-'"
           >
             {{ progress }}%
           </span>
@@ -138,7 +138,7 @@
     </div>
     <div class="tw-flex">
       <a-dropdown class="header-bg-color">
-        <template #overlay v-if="!store.setting.forceMode">
+        <template v-if="!store.setting.forceMode" #overlay>
           <a-menu>
             <a-menu-item key="1" @click="toggleSettingMode">
               {{ exchangeModeName }}
@@ -289,10 +289,10 @@
       </div>
     </a-popover>
     <div
-      @click="openSwitchGroupModal"
       class="tw-flex tw-place-content-center tw-cursor-pointer tw-items-center"
       style="max-width: 8%"
       :title="store.setting.groupTitle + '-' + store.setting.groupNumber"
+      @click="openSwitchGroupModal"
     >
       <img
         src="./images/group.png"
@@ -318,7 +318,7 @@
         <!-- <PoweroffOutlined /> -->
         退出
       </div>
-      <a-tooltip placement="bottomRight" :overlayStyle="{ width: '58px' }">
+      <a-tooltip placement="bottomRight" :overlay-style="{ width: '58px' }">
         <template #title>给分板</template>
         <div
           class="tw-flex tw-place-content-center tw-cursor-pointer menu"
@@ -348,7 +348,7 @@
 
 <script setup lang="ts">
 import { doLogout, updateUISetting } from "@/api/markPage";
-import { watchEffect, ref } from "vue";
+import { watchEffect } from "vue";
 import { store } from "@/store/store";
 import { ModeEnum } from "@/types";
 import MarkChangeProfile from "./MarkChangeProfile.vue";
@@ -397,6 +397,7 @@ let changeProfileRef = $ref<InstanceType<typeof MarkChangeProfile>>();
 
 const openProfileModal = () => {
   // @ts-ignore https://github.com/vuejs/vue-next/issues/4397
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
   changeProfileRef?.showModal();
 };
 
@@ -404,6 +405,7 @@ let switchGroupRef = $ref<InstanceType<typeof MarkSwitchGroupDialog>>();
 
 const openSwitchGroupModal = () => {
   // @ts-ignore https://github.com/vuejs/vue-next/issues/4397
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
   switchGroupRef?.showModal();
 };
 
@@ -411,6 +413,7 @@ let problemRef = $ref<InstanceType<typeof MarkProblemDialog>>();
 
 const openProblemModal = () => {
   // @ts-ignore https://github.com/vuejs/vue-next/issues/4397
+  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
   problemRef?.showModal();
 };
 
@@ -431,7 +434,6 @@ watchEffect(() => {
       onCancel: () => {
         logout();
       },
-      onOk: () => {},
     });
   }
 });

+ 50 - 42
src/features/mark/MarkHistory.vue

@@ -10,11 +10,11 @@
       <input
         v-if="props.showSearch"
         v-model="secretNumberInput"
-        @keydown.stop="() => {}"
-        @keypress.stop="() => {}"
         type="text"
         placeholder="查找试卷"
         class="tw-flex-1 tw-rounded tw-h-8 tw-pl-1 tw-pr-8"
+        @keydown.stop="() => {}"
+        @keypress.stop="() => {}"
         @keyup.enter="searchHistoryTask"
       />
       <SearchOutlined
@@ -26,8 +26,8 @@
     <div class="tw-flex tw-justify-between tw-mt-5">
       <div class="tw-cursor-pointer tw-flex">编号</div>
       <div
-        @click="toggleOrderBy(props.orderTimeField)"
         class="tw-cursor-pointer tw-flex tw-items-center"
+        @click="toggleOrderBy(props.orderTimeField)"
       >
         时间
         <CaretUpOutlined
@@ -42,8 +42,8 @@
         />
       </div>
       <div
-        @click="toggleOrderBy('markerScore')"
         class="tw-cursor-pointer tw-flex tw-items-center"
+        @click="toggleOrderBy('markerScore')"
       >
         分数
         <CaretUpOutlined
@@ -58,7 +58,6 @@
       <div style="margin-bottom: -40px; padding-bottom: 40px">
         <div v-for="(task, index) of store.historyTasks" :key="index">
           <div
-            @click="replaceCurrentTask(task)"
             class="
               tw-flex
               tw-justify-between
@@ -69,6 +68,7 @@
               tw-py-2
             "
             :class="store.currentTask === task && 'current-task'"
+            @click="replaceCurrentTask(task)"
           >
             <div class="tw-break-words" style="width: 62px">
               {{ task.secretNumber }}
@@ -95,17 +95,17 @@
       <div class="tw-flex tw-gap-2">
         <a-button
           shape="circle"
-          @click="previousPage"
           type="primary"
           title="上一页"
+          @click="previousPage"
         >
           <div class="left-triangle"></div>
         </a-button>
         <a-button
           shape="circle"
-          @click="nextPage"
           type="primary"
           title="下一页"
+          @click="nextPage"
         >
           <div class="right-triangle"></div>
         </a-button>
@@ -150,6 +150,10 @@ const props = withDefaults(
     showOrder: false,
     showSearch: false,
     orderTimeField: "markerTime",
+    subjectCode: undefined,
+    groupNumber: undefined,
+    markerId: undefined,
+    markerScore: undefined,
   }
 );
 
@@ -158,6 +162,7 @@ let loading = $ref(false);
 let currentPage = $ref(1);
 let order: MarkHistoryOrderBy = $ref("markerTime");
 if (props.orderTimeField) {
+  // eslint-disable-next-line vue/no-setup-props-destructure
   order = props.orderTimeField;
 }
 let sort: MarkHistorySortField = $ref("DESC");
@@ -192,7 +197,7 @@ const currentTaskChange = async () => {
 watch(() => store.historyOpen, currentTaskChange);
 watch([$$(order), $$(sort), $$(currentPage)], currentTaskChange);
 
-EventBus.on("should-reload-history", async () => {
+EventBus.on("should-reload-history", () => {
   // await updateHistoryTask({
   //   secretNumber: secretNumberInput,
   //   order: order,
@@ -201,42 +206,45 @@ EventBus.on("should-reload-history", async () => {
   // });
   // // 提交后,渲染第一条
   // replaceCurrentTask(store.historyTasks[0]);
-  store.globalMask = true;
-  try {
-    const res = await props.getHistory({
-      secretNumber: store.currentTask?.secretNumber,
-      order: order,
-      sort: sort,
-      pageNumber: currentPage,
-      subjectCode: props.subjectCode,
-      groupNumber: props.groupNumber,
-      markerId: props.markerId,
-      markerScore: props.markerScore,
-    });
-    if (res.data) {
-      let data = cloneDeep(res.data) as Array<Task>;
-      data = data.map(addFileServerPrefixToTask);
-      if (store.currentTask) {
-        // 这种方式(对象被重新构造了)能查找到index,我也很惊讶
-        const indexOfTasks = store.historyTasks.indexOf(store.currentTask);
-        if (data[0]) {
-          // 如果原任务依然存在
-          store.historyTasks.splice(indexOfTasks, 1, data[0]);
-          replaceCurrentTask(store.historyTasks[indexOfTasks]);
+  // TODO: 因为 mitt 不支持 async,这里是个workaround。 https://github.com/developit/mitt/issues/122
+  (async () => {
+    store.globalMask = true;
+    try {
+      const res = await props.getHistory({
+        secretNumber: store.currentTask?.secretNumber,
+        order: order,
+        sort: sort,
+        pageNumber: currentPage,
+        subjectCode: props.subjectCode,
+        groupNumber: props.groupNumber,
+        markerId: props.markerId,
+        markerScore: props.markerScore,
+      });
+      if (res.data) {
+        let data = cloneDeep(res.data) as Array<Task>;
+        data = data.map(addFileServerPrefixToTask);
+        if (store.currentTask) {
+          // 这种方式(对象被重新构造了)能查找到index,我也很惊讶
+          const indexOfTasks = store.historyTasks.indexOf(store.currentTask);
+          if (data[0]) {
+            // 如果原任务依然存在
+            store.historyTasks.splice(indexOfTasks, 1, data[0]);
+            replaceCurrentTask(store.historyTasks[indexOfTasks]);
+          } else {
+            // 问题卷会查找不到,这里直接删除此任务
+            store.historyTasks.splice(indexOfTasks, 1);
+            replaceCurrentTask(store.historyTasks[indexOfTasks]);
+          }
         } else {
-          // 问题卷会查找不到,这里直接删除此任务
-          store.historyTasks.splice(indexOfTasks, 1);
-          replaceCurrentTask(store.historyTasks[indexOfTasks]);
+          // 问题卷会将清除它作为 currentTask ,然后刷新当前页
+          store.historyTasks = data;
+          replaceCurrentTask(store.historyTasks[0]);
         }
-      } else {
-        // 问题卷会将清除它作为 currentTask ,然后刷新当前页
-        store.historyTasks = data;
-        replaceCurrentTask(store.historyTasks[0]);
       }
+    } finally {
+      store.globalMask = false;
     }
-  } finally {
-    store.globalMask = false;
-  }
+  })().catch((e) => console.log("reload-history error", e));
 });
 
 async function updateHistoryTask({
@@ -292,11 +300,11 @@ function toggleOrderBy(toOrder: MarkHistoryOrderBy) {
   }
 }
 
-function searchHistoryTask() {
+async function searchHistoryTask() {
   if (currentPage !== 1) {
     currentPage = 1;
   } else {
-    currentTaskChange();
+    await currentTaskChange();
   }
 }
 </script>

+ 9 - 9
src/features/mark/MarkProblemDialog.vue

@@ -1,11 +1,11 @@
 <template>
   <a-modal
-    title="选择试卷的问题类型"
     v-model:visible="visible"
-    @cancel="handleCancel"
+    title="选择试卷的问题类型"
     width="300px"
-    :zIndex="6000"
-    wrapClassName="profile-wrapper"
+    :z-index="6000"
+    wrap-class-name="profile-wrapper"
+    @cancel="handleCancel"
   >
     <template #footer>
       <a-button key="back" @click="handleCancel">取消</a-button>
@@ -46,13 +46,13 @@ async function updateStatus() {
 
 const chooseProblemType = async (problemId: number) => {
   if (!store.currentTask) {
-    message.warn({ content: "没有可以标记的任务", duration: 5 });
+    void message.warn({ content: "没有可以标记的任务", duration: 5 });
     return;
   }
   try {
     const res = await doProblemType(problemId);
     if (res?.data.success) {
-      message.success({ content: "问题卷处理成功", duration: 3 });
+      void message.success({ content: "问题卷处理成功", duration: 3 });
       visible = false;
       store.currentTask = undefined;
       if (store.historyOpen) {
@@ -61,13 +61,13 @@ const chooseProblemType = async (problemId: number) => {
         store.tasks.shift();
         store.currentTask = store.tasks[0];
       }
-      updateStatus();
+      await updateStatus();
     } else {
-      message.error({ content: res?.data.message || "错误", duration: 5 });
+      void message.error({ content: res?.data.message || "错误", duration: 5 });
     }
   } catch (error) {
     console.log("问题卷处理失败", error);
-    message.error({ content: "网络异常", duration: 5 });
+    void message.error({ content: "网络异常", duration: 5 });
     await new Promise((res) => setTimeout(res, 1500));
     window.location.reload();
   }

+ 8 - 5
src/features/mark/MarkSwitchGroupDialog.vue

@@ -1,9 +1,9 @@
 <template>
   <a-modal
-    title="切换分组"
     v-model:visible="visible"
-    :zIndex="6000"
-    wrapClassName="profile-wrapper"
+    title="切换分组"
+    :z-index="6000"
+    wrap-class-name="profile-wrapper"
   >
     <table class="group-table">
       <tr>
@@ -68,7 +68,7 @@ const isCurrentGroup = (groupNumber: number) => {
 };
 
 const chooseGroup = async (markerId: number) => {
-  const res = await doSwitchGroup(markerId)
+  await doSwitchGroup(markerId)
     .then((res) => {
       // 切换分组相当于刷新页面,此时之前的所有的状态消失,即task/markResult不存在了
       if (res.data.success) {
@@ -76,7 +76,10 @@ const chooseGroup = async (markerId: number) => {
         if (body) body.innerHTML = "重新加载中...";
         return new Promise((resolve) => setTimeout(() => resolve(true), 300));
       } else {
-        message.error({ content: res.data.message || "错误", duration: 5 });
+        void message.error({
+          content: res.data.message || "错误",
+          duration: 5,
+        });
         return false;
       }
     })

+ 1 - 1
src/features/mark/MinimapModal.vue

@@ -9,8 +9,8 @@
   >
     <div
       class="mini-map-container"
-      v-html="imagesHtml"
       @click.stop="setScrollTo"
+      v-html="imagesHtml"
     ></div>
   </qm-dialog>
 </template>

+ 8 - 6
src/features/mark/MultiMediaMarkBody.vue

@@ -13,8 +13,8 @@
           <template v-if="question.objective">
             <div v-if="question.options">
               <div
-                v-for="(option, index) in question.options"
-                :key="index"
+                v-for="(option, index2) in question.options"
+                :key="index2"
                 class="tw-flex tw-gap-1"
               >
                 {{ indexToABCD(option.number) }}.
@@ -122,7 +122,7 @@ watch(
     if (!store.currentTask?.jsonUrl) return;
     const res = await updateStudentAnswerJSON();
 
-    const stuAnswers = res.data; // TODO: add type
+    const stuAnswers: StudentAnswer[] = res.data; // TODO: add type
     for (const ans of stuAnswers) {
       if (ans.answer && !Array.isArray(ans.answer)) {
         ans.answer = [ans.answer];
@@ -141,8 +141,9 @@ watch(
               .filter((v) => typeof v !== "undefined")
               .join("-")
         ) || {
-          mainNumber: mainNumber,
+          mainNumber: +mainNumber,
           subNumber: subNumber,
+          subIndex: "",
           answer: [],
         };
         const taskQuestion = (store.currentTask?.questionList || []).find(
@@ -178,8 +179,9 @@ watch(
               .filter((v) => typeof v !== "undefined")
               .join("-")
         ) || {
-          mainNumber: mainNumber,
+          mainNumber: +mainNumber,
           subNumber: subNumber,
+          subIndex: "",
           answer: [],
         };
 
@@ -204,7 +206,7 @@ const renderObjective = (ans: any) => {
   } else if (Array.isArray(ans) && typeof ans[0] === "boolean") {
     return ans[0] ? "A" : "B";
   } else if (Array.isArray(ans) && typeof ans[0] === "number") {
-    return ans.map((v) => indexToABCD(v)).join("");
+    return (ans as unknown as number[]).map((v) => indexToABCD(v)).join("");
   } else if (Array.isArray(ans) && ans.length === 0) {
     return "";
   } else {

+ 1 - 1
src/features/mark/SheetViewModal.vue

@@ -7,9 +7,9 @@
           <span
             v-for="(u, index) in dataUrls"
             :key="index"
-            @click="checkedIndex = index"
             class="image-index hover:tw-bg-gray-300"
             :class="checkedIndex === index && 'tw-bg-gray-300'"
+            @click="checkedIndex = index"
             >{{ index + 1 }}</span
           >
         </div>

+ 6 - 6
src/features/mark/SpecialTagModal.vue

@@ -14,27 +14,27 @@
       style="color: var(--app-main-text-color)"
     >
       <div
-        @click="toggleTag('√')"
         :class="[store.currentSpecialTag === '√' && 'tag-selected', 'tag']"
+        @click="toggleTag('√')"
       >
       </div>
       <div
-        @click="toggleTag('X')"
         :class="[store.currentSpecialTag === 'X' && 'tag-selected', 'tag']"
+        @click="toggleTag('X')"
       >
         X
       </div>
       <div
-        @click="toggleTag('乄')"
         :class="[store.currentSpecialTag === '乄' && 'tag-selected', 'tag']"
+        @click="toggleTag('乄')"
       >
       </div>
       <div
-        @click="toggleTag('——')"
         :class="[store.currentSpecialTag === '——' && 'tag-selected', 'tag']"
         style="width: 70px; border-radius: 5px"
+        @click="toggleTag('——')"
       >
         <u>下划线</u>
       </div>
@@ -49,7 +49,7 @@
           background-color: var(--app-undo-button-bg-color);
           border-color: var(--app-undo-button-bg-color);
         "
-        :clickTimeout="300"
+        :click-timeout="300"
         @click="clearLatestTagOfCurrentTask"
       >
         回退
@@ -59,7 +59,7 @@
         type="primary"
         shape="round"
         size="large"
-        :clickTimeout="300"
+        :click-timeout="300"
         @click="clearAllTagsOfCurrentTask"
       >
         清除全部

+ 2 - 1
src/features/mark/use/autoChooseFirstQuestion.ts

@@ -39,7 +39,8 @@ const scrollToQuestionOfBoard = async (question: Question) => {
 
 export function chooseQuestion(question: Question) {
   store.currentQuestion = question;
-  scrollToQuestionOfBoard(question);
+  // FIXME: maybe should be an async function, temp fix for eslint
+  void scrollToQuestionOfBoard(question);
 }
 
 /** chooseQuestion 当currentTask改变是,自动选择第一题 */

+ 1 - 1
src/features/mark/use/focusTracks.ts

@@ -3,7 +3,7 @@ import { store } from "@/store/store";
 let hovering = false;
 let timeoutId = -1;
 
-export async function addFocusTrack(
+export function addFocusTrack(
   groupNumber: number | undefined,
   mainNumber: number | undefined,
   subNumber: string | undefined

+ 4 - 4
src/features/mark/use/splitPane.ts

@@ -31,10 +31,10 @@ export function dragSplitPane() {
     // // Scroll the element
     // // dragSpliter.style.marginTop =
     // //   (parseFloat(dragSpliter.style.marginTop) || 0) + dy + "px";
-    dragSpliter.style.marginTop = dy + "px";
+    dragSpliter.style.marginTop = `${dy}px`;
     // console.log(dragSpliter.style.marginTop);
-    const target = e.target as HTMLElement;
-    const parent = target.parentElement;
+    // const target = e.target as HTMLElement;
+    // const parent = target.parentElement;
     // console.log(getComputedStyle(e.target.parentElement).height);
     // console.log({
     //   offsetTop: target.offsetTop,
@@ -49,7 +49,7 @@ export function dragSplitPane() {
   };
 
   const mouseUpHandler = function (e: MouseEvent) {
-    const dy = e.clientY - pos.y;
+    // const dy = e.clientY - pos.y;
 
     // Scroll the element
     // dragSpliter.style.marginTop =

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

@@ -2,11 +2,11 @@
   <div class="my-container">
     <mark-header />
     <div class="tw-flex tw-gap-1">
-      <mark-body @error="renderError" usingImage="sheetUrls" />
+      <mark-body using-image="sheetUrls" @error="renderError" />
       <MarkBoardInspect
         :tagged="isCurrentTagged"
-        :isFirst="isFirst"
-        :isLast="isLast"
+        :is-first="isFirst"
+        :is-last="isLast"
         @makeTag="saveTaskToServer"
         @fetchTask="fetchTask"
       />
@@ -66,9 +66,9 @@ async function updateTask() {
     return;
   }
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res = await getSingleInspectedTask("" + currentStudentId);
-  message.success({
+  void message.success({
     content: res.data.studentId ? "获取成功" : "无任务",
     key: mkey,
   });
@@ -111,13 +111,13 @@ onMounted(async () => {
 
 const saveTaskToServer = async () => {
   const mkey = "save_task_key";
-  message.loading({ content: "标记评卷任务...", key: mkey });
+  void message.loading({ content: "标记评卷任务...", key: mkey });
   const res = (await saveInspectedTask(
     currentStudentId + "",
     !isCurrentTagged + ""
   )) as any;
   if (res.data.success) {
-    message.success({
+    void message.success({
       content: isCurrentTagged ? "取消标记成功" : "标记成功",
       key: mkey,
       duration: 2,
@@ -129,7 +129,7 @@ const saveTaskToServer = async () => {
     }
   } else {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   }
 };
 

+ 3 - 3
src/features/student/importInspect/MarkBoardInspect.vue

@@ -24,7 +24,7 @@
       ></div>
     </div>
 
-    <div class="tw-flex-grow tw-overflow-auto tw-my-5" v-if="groups">
+    <div v-if="groups" class="tw-flex-grow tw-overflow-auto tw-my-5">
       <template v-for="(groupNumber, index) in groups" :key="index">
         <div class="tw-mb-4 tw-bg-white tw-p-4">
           <div
@@ -38,7 +38,7 @@
             <span class="secondary-text">分组 {{ groupNumber }}</span>
           </div>
           <div v-if="questions">
-            <template v-for="(question, index) in questions" :key="index">
+            <template v-for="(question, index2) in questions" :key="index2">
               <div
                 v-if="question.groupNumber === groupNumber"
                 class="
@@ -80,10 +80,10 @@
         上一个
       </a-button>
       <a-button
-        @click="fetchTask(true)"
         type="primary"
         class="full-width-btn"
         :disabled="props.isLast"
+        @click="fetchTask(true)"
         >下一个</a-button
       >
     </div>

+ 3 - 3
src/features/student/importInspect/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       class="
@@ -45,7 +45,7 @@
         }}</span>
       </span>
     </div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <div
       class="tw-flex tw-place-items-center tw-cursor-pointer tw-ml-auto"
       @click="closeWindow"
@@ -107,7 +107,7 @@ const closeWindow = async () => {
 onMounted(() => {
   // 不确定是否一定能在关闭页面时调用
   window.addEventListener("beforeunload", () => {
-    updateClearTask();
+    updateClearTask().catch((e) => console.log(e));
   });
 });
 </script>

+ 14 - 14
src/features/student/inspect/Inspect.vue

@@ -4,8 +4,8 @@
     <div class="tw-flex tw-gap-1">
       <mark-history
         v-if="!isSingleStudent"
-        :subjectCode="subjectCode"
-        orderTimeField="inspectTime"
+        :subject-code="subjectCode"
+        order-time-field="inspectTime"
         :get-history="getInspectedHistory"
       />
       <mark-body @error="renderError" />
@@ -96,14 +96,14 @@ async function updateStatus() {
 }
 async function updateTask() {
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res;
   if (isSingleStudent) {
     res = await getSingleStuTask();
   } else {
     res = await getOneOfStuTask();
   }
-  message.success({
+  void message.success({
     content: res.data.studentId ? "获取成功" : "无任务",
     key: mkey,
   });
@@ -161,42 +161,42 @@ let realStudentId = $computed(
 const saveTaskToServer = async () => {
   console.log("save inspect task to server");
   const mkey = "save_task_key";
-  message.loading({ content: "保存评卷任务...", key: mkey });
+  void message.loading({ content: "保存评卷任务...", key: mkey });
   const res = (await saveInspectedTask(realStudentId)) as any;
   if (res.data.success && store.currentTask) {
-    message.success({ content: "复核成功", key: mkey, duration: 2 });
+    void message.success({ content: "复核成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
-      if (!isSingleStudent) fetchTask();
+      if (!isSingleStudent) await fetchTask();
     } else {
       EventBus.emit("should-reload-history");
     }
   } else if (res.data.message) {
     console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 
 const rejectQuestions = async (questions: Array<Question>) => {
   const mkey = "reject_task_key";
-  message.loading({ content: "打回评卷任务...", key: mkey });
+  void message.loading({ content: "打回评卷任务...", key: mkey });
   const res = (await rejectInspectedTask(realStudentId, questions)) as any;
   if (res.data.success && store.currentTask) {
     store.currentTask = undefined;
-    message.success({ content: "打回成功", key: mkey, duration: 2 });
+    void message.success({ content: "打回成功", key: mkey, duration: 2 });
     if (!store.historyOpen) {
       store.currentTask = undefined;
-      if (!isSingleStudent) fetchTask();
+      if (!isSingleStudent) await fetchTask();
     } else {
       EventBus.emit("should-reload-history");
     }
   } else if (res.data.message) {
     // console.log(res.data.message);
-    message.error({ content: res.data.message, key: mkey, duration: 10 });
+    void message.error({ content: res.data.message, key: mkey, duration: 10 });
   } else if (!store.currentTask) {
-    message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
+    void message.warn({ content: "暂无新任务", key: mkey, duration: 10 });
   }
 };
 

+ 9 - 9
src/features/student/inspect/MarkBoardInspect.vue

@@ -19,7 +19,7 @@
       </div>
     </div>
 
-    <div class="tw-flex-grow tw-overflow-auto tw-my-5" v-if="groups">
+    <div v-if="groups" class="tw-flex-grow tw-overflow-auto tw-my-5">
       <template v-for="(groupNumber, index) in groups" :key="index">
         <div class="tw-mb-4 tw-bg-white tw-p-4">
           <div
@@ -35,12 +35,12 @@
               class="tw-my-auto"
               title="打回"
               type="checkbox"
-              @click="groupClicked(groupNumber)"
               :checked="groupChecked(groupNumber)"
+              @click="groupClicked(groupNumber)"
             />
           </div>
           <div v-if="questions">
-            <template v-for="(question, index) in questions" :key="index">
+            <template v-for="(question, index2) in questions" :key="index2">
               <div
                 v-if="question.groupNumber === groupNumber"
                 class="
@@ -69,8 +69,8 @@
                   :disabled="question.score === -1"
                   title="打回"
                   type="checkbox"
-                  @change="questionCheckChanged(question)"
                   :checked="questionChecked(question)"
+                  @change="questionCheckChanged(question)"
                 />
               </div>
             </template>
@@ -81,28 +81,28 @@
 
     <div class="tw-flex tw-flex-shrink-0 tw-justify-center">
       <qm-button
-        type="primary"
-        class="full-width-btn undo-btn"
         v-if="
           store.currentTask.inspectTime && store.currentTask.inspectTime > 0
         "
+        type="primary"
+        class="full-width-btn undo-btn"
         @click="reject"
       >
         打回
       </qm-button>
       <qm-button
         v-else-if="checkedQuestions.length === 0"
-        @click="inspect"
         type="primary"
         class="full-width-btn"
+        @click="inspect"
       >
         复核
       </qm-button>
       <qm-button
         v-else
-        @click="reject"
         type="primary"
         class="full-width-btn undo-btn"
+        @click="reject"
         >打回</qm-button
       >
     </div>
@@ -186,7 +186,7 @@ function groupClicked(groupNumber: number) {
 
 function reject() {
   if (checkedQuestions.length === 0) {
-    message.warn({ content: "请先选择试题。" });
+    void message.warn({ content: "请先选择试题。" });
     return;
   }
   emit("reject", checkedQuestions);

+ 3 - 3
src/features/student/inspect/MarkBody.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="mark-body-container tw-flex-auto tw-p-2" ref="dragContainer">
+  <div ref="dragContainer" class="mark-body-container tw-flex-auto tw-p-2">
     <div v-if="!store.currentTask" class="tw-text-center">
       {{ store.message }}
     </div>
@@ -43,7 +43,7 @@ interface SliceImage {
   width: string; // 图片在整个图片列表里面的宽度比例
 }
 
-const { usingImage = "sliceUrls" } = withDefaults(
+const props = withDefaults(
   defineProps<{
     usingImage?: "sheetUrls" | "sliceUrls";
   }>(),
@@ -64,7 +64,7 @@ async function processImage() {
   if (!store.currentTask) return;
 
   const images = [];
-  const urls = store.currentTask[usingImage] || [];
+  const urls = store.currentTask[props.usingImage] || [];
   for (const url of urls) {
     const image = await loadImage(url);
     images.push(image);

+ 1 - 1
src/features/student/inspect/MarkDrawTrack.vue

@@ -6,8 +6,8 @@
       :style="computeTopAndLeft(track)"
     >
       <span
-        class="tw-m-auto"
         :id="`a-${track.mainNumber}-${track.subNumber}-${track.offsetY}-${track.offsetX}`"
+        class="tw-m-auto"
       >
         {{ track.score }}
       </span>

+ 3 - 3
src/features/student/inspect/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       v-if="!isSingleStudent"
@@ -51,7 +51,7 @@
         <span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
       </span>
     </div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <div class="tw-flex tw-place-items-center tw-justify-end tw-ml-auto">
       {{ store.setting.userName }}
     </div>
@@ -116,7 +116,7 @@ const closeWindow = async () => {
 onMounted(() => {
   // 不确定是否一定能在关闭页面时调用
   window.addEventListener("beforeunload", () => {
-    updateClearTask();
+    updateClearTask().catch((e) => console.log(e));
   });
 });
 </script>

+ 3 - 3
src/features/student/studentTrack/MarkHeader.vue

@@ -1,7 +1,7 @@
 <template>
   <div
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
     v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
   >
     <div
       class="
@@ -29,7 +29,7 @@
       </div>
     </div>
     <div class="tw-flex tw-gap-2 tw-items-center tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" :store="store" />
+    <ZoomPaper v-if="store.isScanImage" />
     <div class="tw-flex tw-place-items-center tw-justify-end">
       {{ store.setting.userName }}
     </div>
@@ -47,7 +47,7 @@ import { store } from "@/store/store";
 import { PoweroffOutlined } from "@ant-design/icons-vue";
 import ZoomPaper from "@/components/ZoomPaper.vue";
 
-const closeWindow = async () => {
+const closeWindow = () => {
   window.close();
 };
 </script>

+ 4 - 4
src/features/student/studentTrack/StudentTrack.vue

@@ -5,10 +5,10 @@
       <mark-body v-if="store.isScanImage" @error="renderError" />
       <CommonMarkBody
         v-else
-        :useMarkResult="false"
+        :use-mark-result="false"
         :store="store"
+        :make-track="() => {}"
         @error="renderError"
-        :makeTrack="() => {}"
       />
     </div>
   </div>
@@ -50,9 +50,9 @@ async function updateSetting() {
 
 async function updateTask() {
   const mkey = "fetch_task_key";
-  message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
+  void message.info({ content: "获取任务中...", duration: 1.5, key: mkey });
   let res = await getSingleStuTask();
-  message.success({
+  void message.success({
     content: res.data.studentId ? "获取成功" : "无任务",
     key: mkey,
   });

+ 3 - 3
src/main.ts

@@ -27,11 +27,11 @@ app.use(createPinia());
 // app.use(Antd);
 app.config.globalProperties.$filters = filters;
 
-app.component("qm-button", QmButton);
-app.component("qm-dialog", QmDialog);
+app.component("QmButton", QmButton);
+app.component("QmDialog", QmDialog);
 
 if (import.meta.env.DEV) {
-  import("./devLogin")
+  await import("./devLogin")
     .then((m) => {
       return m.initLogin();
     })

+ 7 - 6
src/plugins/axiosApp.ts

@@ -26,7 +26,7 @@ _axiosApp.interceptors.request.use(
     if (error.config.setGlobalMask) {
       store.globalMask = false;
     }
-    message.error({ content: error, duration: 10 });
+    void message.error({ content: error, duration: 10 });
     console.log(error);
     return Promise.reject(error);
   }
@@ -48,7 +48,7 @@ _axiosApp.interceptors.response.use(
     if (!error.response) {
       if (showErrorMessage) {
         // "Network Error" 网络不通,直接返回
-        message.error({
+        void message.error({
           content: "网络连接异常,请检查网络设置。",
           duration: 10,
         });
@@ -64,12 +64,12 @@ _axiosApp.interceptors.response.use(
       return Promise.reject(error);
     } else if (status == 405) {
       if (showErrorMessage) {
-        message.error({ content: "没有权限!", duration: 10 });
+        void message.error({ content: "没有权限!", duration: 10 });
       }
       return Promise.reject(error);
     } else if (status == 502) {
       if (showErrorMessage) {
-        message.error({ content: "服务器异常(502)!", duration: 10 });
+        void message.error({ content: "服务器异常(502)!", duration: 10 });
       }
       return Promise.reject(error);
     }
@@ -78,11 +78,11 @@ _axiosApp.interceptors.response.use(
       const data = error.response.data;
       if (data && data.message) {
         if (showErrorMessage) {
-          message.error({ content: data.message, duration: 10 });
+          void message.error({ content: data.message, duration: 10 });
         }
       } else {
         if (showErrorMessage) {
-          message.error({
+          void message.error({
             content: "未定义异常: " + JSON.stringify(data, null, 2),
             duration: 10,
           });
@@ -93,6 +93,7 @@ _axiosApp.interceptors.response.use(
   }
 );
 
+// eslint-disable-next-line
 loadProgressBar(null, _axiosApp);
 
 export const httpApp = _axiosApp;

+ 5 - 4
src/plugins/axiosNoAuth.ts

@@ -22,7 +22,7 @@ _axiosNoAuth.interceptors.request.use(
     return config;
   },
   function (error) {
-    message.error({ content: error, duration: 10 });
+    void message.error({ content: error, duration: 10 });
     return Promise.reject(error);
   }
 );
@@ -35,7 +35,7 @@ _axiosNoAuth.interceptors.response.use(
   (error) => {
     if (!error.response) {
       // "Network Error" 网络不通,直接返回
-      message.error({
+      void message.error({
         content: "网络连接异常,请检查网络设置。",
         duration: 10,
       });
@@ -44,9 +44,9 @@ _axiosNoAuth.interceptors.response.use(
 
     const data = error.response.data;
     if (data && data.desc) {
-      message.error({ content: data.desc, duration: 10 });
+      void message.error({ content: data.desc, duration: 10 });
     } else {
-      message.error({
+      void message.error({
         content: `异常(${error.response.status}): ${error.config.url}`,
         duration: 10,
       });
@@ -55,6 +55,7 @@ _axiosNoAuth.interceptors.response.use(
   }
 );
 
+// eslint-disable-next-line
 loadProgressBar(null, _axiosNoAuth);
 
 export const httpNoAuth = _axiosNoAuth;

+ 1 - 1
src/plugins/axiosNotice.ts

@@ -4,7 +4,7 @@ import { doLogout } from "@/api/markPage";
 
 export const notifyInvalidTokenThrottled = throttle(
   () => {
-    message.error({
+    void message.error({
       content: "登录失效,请重新登录!",
       duration: 10,
     });

+ 5 - 1
src/plugins/eventBus.ts

@@ -1,5 +1,9 @@
 import mitt from "mitt";
 
-const EventBus = mitt();
+type Events = {
+  "should-reload-history": undefined;
+};
+
+const EventBus = mitt<Events>();
 
 export default EventBus;

+ 1 - 1
src/router/index.ts

@@ -42,7 +42,7 @@ const routes = [
   {
     path: "/:pathMatch(.*)*",
     name: "NotFound",
-    component: () => import("@/components/404.vue"),
+    component: () => import("@/components/Page404.vue"),
   },
 ];
 

+ 2 - 0
src/setups/useTimers.ts

@@ -12,6 +12,7 @@ export function useTimers() {
    * @param {function} fn 要执行的函数
    * @param {number} interval 执行间隔ms
    */
+  // eslint-disable-next-line @typescript-eslint/ban-types
   function addInterval(fn: Function, interval: number) {
     const i = setInterval(fn, interval);
     mixin__intervals.push(i);
@@ -21,6 +22,7 @@ export function useTimers() {
    * @param {function} fn 要执行的函数
    * @param {number} timeout 触发时间ms
    */
+  // eslint-disable-next-line @typescript-eslint/ban-types
   function addTimeout(fn: Function, timeout: number) {
     const i = setTimeout(fn, timeout);
     mixin__timeouts.push(i);

+ 1 - 1
src/types/global.d.ts

@@ -1,4 +1,4 @@
-import Vue, { ComponentCustomProperties } from "vue";
+// import Vue, { ComponentCustomProperties } from "vue";
 import filters from "@/filters";
 import { default as message } from "ant-design-vue/lib/message";
 // import { default as notification } from "ant-design-vue/lib/notification";

+ 2 - 2
src/utils/ua.ts

@@ -4,8 +4,8 @@ const ua = new UAParser();
 
 function printUA() {
   console.log(
-    `浏览器名称:${ua.getBrowser().name}   浏览器版本:${
-      ua.getBrowser().version
+    `浏览器名称:${ua.getBrowser().name || "undefined"}   浏览器版本:${
+      ua.getBrowser().version || "undefined"
     }`
   );
 

+ 1 - 1
src/utils/utils.ts

@@ -5,7 +5,7 @@ import { PictureSlice, Task } from "@/types";
 // 通过回看的测试,打开回看,再关闭回看,稍等一会儿再打开回看,确实可以看到该缓存时缓存了,该丢弃时丢弃了
 
 // 把store.currentTask当做 weakRef ,当它不存在时,就丢弃它所有的图片
-const weakedMapImages = new WeakMap<Object, Map<string, HTMLImageElement>>();
+// const weakedMapImages = new WeakMap<Object, Map<string, HTMLImageElement>>();
 
 /**
  * 异步获取图片