Michael Wang 3 жил өмнө
parent
commit
07d93cf651

+ 1 - 0
components.d.ts

@@ -19,6 +19,7 @@ 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']
+    CommonMarkHeader: typeof import('./src/components/CommonMarkHeader.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']

+ 259 - 0
src/components/CommonMarkHeader.vue

@@ -0,0 +1,259 @@
+<template>
+  <div
+    v-if="store.setting"
+    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
+  >
+    <div
+      v-if="!isSingleStudent"
+      class="tw-flex tw-place-content-center tw-cursor-pointer tw-relative menu"
+      style="margin-right: -16px"
+      :class="[store.historyOpen && 'menu-toggled']"
+      @click="store.toggleHistory"
+    >
+      <span title="回看" class="tw-inline-flex tw-place-content-center">
+        <img
+          src="../features/mark/images/left-menu.svg"
+          :class="[store.historyOpen && 'svg-red']"
+        />
+      </span>
+      <div v-if="store.historyOpen" class="triangle"></div>
+    </div>
+
+    <div
+      class="tw-text-white tw-block tw-overflow-ellipsis tw-overflow-hidden tw-whitespace-nowrap header-big-text tw-ml-4"
+    >
+      {{
+        `${store.setting.subject?.code ?? ""}-${
+          store.setting.subject?.name ?? ""
+        }`
+      }}
+    </div>
+
+    <div class="tw-flex tw-gap-1">
+      <slot name="taskInfo">
+        <div>
+          <span class="header-small-text">编号</span>
+          <span class="highlight-text">
+            {{ store.currentTask?.secretNumber ?? "-" }}
+          </span>
+        </div>
+      </slot>
+    </div>
+
+    <div
+      v-if="!isSingleStudent"
+      class="tw-flex tw-gap-2 tw-items-center tw-flex-1"
+    >
+      <slot />
+    </div>
+
+    <div class="tw-flex-1"></div>
+    <ZoomPaper v-if="store.isScanImage" />
+    <a-popover
+      v-if="store.isScanImage"
+      title="小助手"
+      trigger="hover"
+      class="tw-cursor-pointer tw-flex tw-gap-2 tw-items-center"
+    >
+      <template #content>
+        <table class="assistant-table">
+          <tr v-if="store.setting.subject.paperUrl && showPaperAndAnswer">
+            <td>试卷</td>
+            <td>
+              <a-switch
+                v-model:checked="store.setting.uiSetting['paper.modal']"
+              />
+            </td>
+          </tr>
+          <tr v-if="store.setting.subject.answerUrl && showPaperAndAnswer">
+            <td>答案</td>
+            <td>
+              <a-switch
+                v-model:checked="store.setting.uiSetting['answer.modal']"
+              />
+            </td>
+          </tr>
+          <tr>
+            <td>缩略图</td>
+            <td>
+              <a-switch
+                v-model:checked="store.setting.uiSetting['minimap.modal']"
+              />
+            </td>
+          </tr>
+          <tr v-if="store.isScanImage">
+            <td>分数/标记大小</td>
+            <td>
+              <a-slider
+                v-model:value="store.setting.uiSetting['score.fontSize.scale']"
+                :min="0.5"
+                :step="0.1"
+                :max="2"
+                style="margin: 0"
+              />
+            </td>
+          </tr>
+        </table>
+      </template>
+      <div class="tw-flex">
+        小助手
+        <DownOutlined
+          style="font-size: 12px; display: inline-block"
+          class="tw-self-center tw-ml-1"
+        />
+      </div>
+    </a-popover>
+
+    <div class="tw-flex tw-cursor-pointer tw-items-center tw-flex-1">
+      <div
+        v-if="store.setting.groupNumber !== -987654"
+        class="tw-overflow-ellipsis tw-overflow-hidden tw-whitespace-nowrap tw-mr-1"
+      >
+        <span class="header-small-text">分组</span>
+        <span class="highlight-text">
+          {{ store.setting.groupNumber }}
+        </span>
+      </div>
+    </div>
+
+    <div class="tw-flex tw-place-items-center">
+      <UserOutlined class="icon-font icon-with-text" />{{
+        store.setting.userName
+      }}
+    </div>
+
+    <div
+      class="tw-flex tw-place-items-center tw-cursor-pointer tw-pr-4"
+      @click="closeWindow"
+    >
+      <PoweroffOutlined class="icon-font icon-with-text" />关闭
+    </div>
+
+    <div
+      v-if="showScoreBoard"
+      class="tw-flex tw-place-content-center tw-cursor-pointer tw-justify-self-end menu"
+      :class="[
+        store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
+      ]"
+      style="margin-left: -16px"
+      @click="store.toggleScoreBoard"
+    >
+      <span
+        title="给分板"
+        class="tw-inline-flex tw-place-content-center tw-relative"
+      >
+        <img
+          src="../features/mark/images/right-menu.svg"
+          :class="[store.isScoreBoardVisible && 'svg-red']"
+        />
+      </span>
+      <div
+        v-if="store.isScoreBoardVisible && store.currentTask"
+        class="triangle"
+      ></div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onMounted } from "vue";
+import { store } from "@/store/store";
+import {
+  UserOutlined,
+  PoweroffOutlined,
+  DownOutlined,
+} from "@ant-design/icons-vue";
+import ZoomPaper from "@/components/ZoomPaper.vue";
+
+const props = withDefaults(
+  defineProps<{
+    isSingleStudent?: boolean;
+    // eslint-disable-next-line
+    clearTasks?: Function;
+    showPaperAndAnswer?: boolean;
+    showScoreBoard?: boolean;
+  }>(),
+  {
+    isSingleStudent: true,
+    // eslint-disable-next-line
+    clearTasks: () => {},
+    showPaperAndAnswer: false,
+    showScoreBoard: false,
+  }
+);
+
+async function updateClearTask() {
+  await props.clearTasks();
+}
+
+const closeWindow = async () => {
+  await updateClearTask();
+  window.close();
+};
+
+onMounted(() => {
+  // 不确定是否一定能在关闭页面时调用
+  window.addEventListener("beforeunload", () => {
+    updateClearTask().catch((e) => console.log(e));
+  });
+});
+</script>
+
+<style scoped>
+.header-container {
+  position: relative;
+  height: 56px;
+  line-height: 16px;
+
+  background-color: var(--header-bg-color);
+  color: rgba(255, 255, 255, 0.5);
+}
+.menu {
+  width: 56px;
+  height: 56px;
+  padding: 20px;
+}
+.menu:hover,
+.menu-toggled {
+  background-color: rgba(255, 255, 255, 0.2);
+}
+
+.header-container >>> span {
+  vertical-align: middle;
+}
+.header-big-text {
+  font-size: 20px;
+  line-height: 30px;
+}
+.header-small-text {
+  font-size: var(--app-secondary-font-size);
+}
+.highlight-text {
+  color: white;
+  font-size: var(--app-title-font-size);
+}
+
+.assistant-table {
+  z-index: 5500;
+  border-collapse: separate;
+  border-spacing: 0 1em;
+  color: var(--app-bold-text-color);
+  width: 240px;
+}
+.assistant-table tr td:nth-child(2) {
+  text-align: right;
+}
+.svg-red {
+  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
+    brightness(104%) contrast(97%);
+}
+.triangle {
+  background-color: white;
+  width: 10px;
+  height: 10px;
+  clip-path: polygon(0 100%, 100% 100%, 50% 0);
+
+  position: absolute;
+  bottom: -2px;
+}
+</style>

+ 21 - 204
src/features/arbitrate/MarkHeader.vue

@@ -1,151 +1,26 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
+  <CommonMarkHeader
+    :isSingleStudent="isSingleStudent"
+    :clearTasks="clearTasks"
+    showPaperAndAnswer
+    showScoreBoard
   >
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-place-content-center tw-cursor-pointer tw-relative menu"
-      :class="[store.historyOpen && 'menu-toggled']"
-      @click="store.toggleHistory"
-    >
-      <span title="回看" class="tw-inline-flex tw-place-content-center">
-        <img
-          src="../mark/images/left-menu.svg"
-          :class="[store.historyOpen && 'svg-red']"
-        />
-      </span>
-      <div v-if="store.historyOpen" class="triangle"></div>
-    </div>
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-      "
-    >
-      {{
-        `${store.setting.subject.code ?? ""}-${
-          store.setting.subject.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1">
-      <div>
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber ?? "-" }}
-        </span>
-      </div>
-    </div>
-    <div v-if="!isSingleStudent" class="tw-flex tw-gap-2 tw-items-center">
-      <span>
-        <span class="header-small-text">待处理</span>
-        <span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
-      </span>
-      <span>
-        <span class="header-small-text">已处理</span>
-        <span class="highlight-text">{{
-          store.status.markedCount ?? "-"
-        }}</span>
-      </span>
-    </div>
-    <div class="tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <a-popover
-      v-if="store.isScanImage"
-      title="小助手"
-      trigger="hover"
-      class="tw-cursor-pointer tw-flex tw-gap-2 tw-items-center"
-    >
-      <template #content>
-        <table class="assistant-table">
-          <tr v-if="store.setting.subject.paperUrl">
-            <td>试卷</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['paper.modal']"
-              />
-            </td>
-          </tr>
-          <tr v-if="store.setting.subject.answerUrl">
-            <td>答案</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['answer.modal']"
-              />
-            </td>
-          </tr>
-          <tr>
-            <td>缩略图</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['minimap.modal']"
-              />
-            </td>
-          </tr>
-        </table>
-      </template>
-      <div class="tw-flex">
-        小助手
-        <DownOutlined
-          style="font-size: 12px; display: inline-block"
-          class="tw-self-center tw-ml-1"
-        />
-      </div>
-    </a-popover>
-    <div class="tw-flex tw-place-items-center">
-      <UserOutlined class="icon-font icon-with-text" />{{
-        store.setting.userName
-      }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-    <div
-      class="
-        tw-flex tw-place-content-center tw-cursor-pointer tw-justify-self-end
-        menu
-      "
-      :class="[
-        store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
-      ]"
-      @click="store.toggleScoreBoard"
-    >
-      <span
-        title="给分板"
-        class="tw-inline-flex tw-place-content-center tw-relative"
-      >
-        <img
-          src="../mark/images/right-menu.svg"
-          :class="[store.isScoreBoardVisible && 'svg-red']"
-        />
-      </span>
-      <div
-        v-if="store.isScoreBoardVisible && store.currentTask"
-        class="triangle"
-      ></div>
-    </div>
-  </div>
+    <span>
+      <span class="header-small-text">待处理</span>
+      <span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
+    </span>
+    <span>
+      <span class="header-small-text">已处理</span>
+      <span class="highlight-text">{{ store.status.markedCount ?? "-" }}</span>
+    </span>
+  </CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
-import { onMounted } from "vue";
 import { store } from "@/store/store";
-import {
-  UserOutlined,
-  PoweroffOutlined,
-  DownOutlined,
-} from "@ant-design/icons-vue";
 import { useRoute } from "vue-router";
 import { clearArbitrateTask } from "@/api/arbitratePage";
-import ZoomPaper from "@/components/ZoomPaper.vue";
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 
 const route = useRoute();
 let isSingleStudent = !!route.query.historyId;
@@ -159,49 +34,15 @@ const {
   historyId: string;
 };
 
-async function updateClearTask() {
-  await clearArbitrateTask(libraryId, subjectCode, groupNumber);
-}
-
-const closeWindow = async () => {
-  await updateClearTask();
-  window.close();
-};
-
-onMounted(() => {
-  // 不确定是否一定能在关闭页面时调用
-  window.addEventListener("beforeunload", () => {
-    updateClearTask().catch((e) => console.log(e));
-  });
-});
+let clearTasks = clearArbitrateTask.bind(
+  null,
+  libraryId,
+  subjectCode,
+  groupNumber
+);
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-.menu {
-  width: 56px;
-  height: 56px;
-  padding: 20px;
-}
-.menu:hover,
-.menu-toggled {
-  background-color: rgba(255, 255, 255, 0.2);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -209,28 +50,4 @@ onMounted(() => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.assistant-table {
-  z-index: 5500;
-  border-collapse: separate;
-  border-spacing: 0 1em;
-  color: var(--app-bold-text-color);
-  width: 240px;
-}
-.assistant-table tr td:nth-child(2) {
-  text-align: right;
-}
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-.triangle {
-  background-color: white;
-  width: 10px;
-  height: 10px;
-  clip-path: polygon(0 100%, 100% 100%, 50% 0);
-
-  position: absolute;
-  bottom: -2px;
-}
 </style>

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

@@ -13,12 +13,14 @@
       <MarkBoardInspect @inspect="saveTaskToServer" @reject="rejectQuestions" />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script setup lang="ts">
 import { onMounted, watch } from "vue";
 import { store } from "@/store/store";
 import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "./MarkBody.vue";
 import MarkHistory from "@/features/mark/MarkHistory.vue";

+ 15 - 145
src/features/library/inspect/MarkHeader.vue

@@ -1,102 +1,21 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
+  <CommonMarkHeader
+    :isSingleStudent="isSingleStudent"
+    :clearTasks="clearTasks"
+    showScoreBoard
   >
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-place-content-center tw-cursor-pointer tw-relative menu"
-      :class="[store.historyOpen && 'menu-toggled']"
-      @click="store.toggleHistory"
-    >
-      <span title="回看" class="tw-inline-flex tw-place-content-center">
-        <img
-          src="../../mark/images/left-menu.svg"
-          :class="[store.historyOpen && 'svg-red']"
-        />
-      </span>
-      <div v-if="store.historyOpen" class="triangle"></div>
-    </div>
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-ml-4
-      "
-    >
-      {{
-        `${store.setting.subject?.code ?? ""}-${
-          store.setting.subject?.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1">
-      <div>
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber ?? "-" }}
-        </span>
-      </div>
-    </div>
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-gap-2 tw-items-center tw-flex-1"
-    >
-      <span>
-        <span class="header-small-text">待复核</span
-        ><span class="highlight-text">{{
-          store.status.totalCount ?? "-"
-        }}</span>
-      </span>
-    </div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <div class="tw-flex tw-place-items-center tw-justify-end tw-ml-auto">
-      {{ store.setting.userName }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-    <div
-      class="
-        tw-flex tw-place-content-center tw-cursor-pointer tw-justify-self-end
-        menu
-      "
-      :class="[
-        store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
-      ]"
-      @click="store.toggleScoreBoard"
-    >
-      <span
-        title="给分板"
-        class="tw-inline-flex tw-place-content-center tw-relative"
-      >
-        <img
-          src="../../mark/images/right-menu.svg"
-          :class="[store.isScoreBoardVisible && 'svg-red']"
-        />
-      </span>
-      <div
-        v-if="store.isScoreBoardVisible && store.currentTask"
-        class="triangle"
-      ></div>
-    </div>
-  </div>
+    <span>
+      <span class="header-small-text">待复核</span
+      ><span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
+    </span>
+  </CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
-import { onMounted } from "vue";
 import { store } from "@/store/store";
-import { PoweroffOutlined } from "@ant-design/icons-vue";
 import { useRoute } from "vue-router";
 import { clearInspectedTaskOfLibraryInspect } from "@/api/libraryInspectPage";
-import ZoomPaper from "@/components/ZoomPaper.vue";
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 
 const route = useRoute();
 let isSingleStudent = $ref(false);
@@ -106,49 +25,14 @@ const { subjectCode, groupNumber } = route.query as {
   groupNumber: string;
 };
 
-async function updateClearTask() {
-  await clearInspectedTaskOfLibraryInspect(subjectCode, groupNumber);
-}
-
-const closeWindow = async () => {
-  await updateClearTask();
-  window.close();
-};
-
-onMounted(() => {
-  // 不确定是否一定能在关闭页面时调用
-  window.addEventListener("beforeunload", () => {
-    updateClearTask().catch((e) => console.log(e));
-  });
-});
+let clearTasks = clearInspectedTaskOfLibraryInspect.bind(
+  null,
+  subjectCode,
+  groupNumber
+);
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-.menu {
-  width: 56px;
-  height: 56px;
-  padding: 20px;
-}
-.menu:hover,
-.menu-toggled {
-  background-color: rgba(255, 255, 255, 0.2);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -156,18 +40,4 @@ onMounted(() => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-.triangle {
-  background-color: white;
-  width: 10px;
-  height: 10px;
-  clip-path: polygon(0 100%, 100% 100%, 50% 0);
-
-  position: absolute;
-  bottom: -2px;
-}
 </style>

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

@@ -5,12 +5,14 @@
       <mark-body @error="renderError" />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script setup lang="ts">
 import { onMounted } from "vue";
 import { store } from "@/store/store";
 import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "./MarkBody.vue";
 import { message } from "ant-design-vue";

+ 2 - 81
src/features/library/libraryTrack/MarkHeader.vue

@@ -1,87 +1,12 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
-  >
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-pl-5
-      "
-    >
-      {{
-        `${store.setting.subject.code ?? ""}-${
-          store.setting.subject.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1 tw-items-center">
-      <div class="tw-flex tw-items-center">
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber }}
-        </span>
-      </div>
-    </div>
-    <div class="tw-flex tw-gap-2 tw-items-center tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <div class="tw-flex tw-cursor-pointer tw-items-center tw-flex-1">
-      <div
-        class="
-          tw-overflow-ellipsis tw-overflow-hidden tw-whitespace-nowrap tw-mr-1
-        "
-      >
-        <span class="header-small-text">分组</span>
-        <span class="highlight-text">
-          {{ store.setting.groupNumber }}
-        </span>
-      </div>
-    </div>
-    <div class="tw-flex tw-place-items-center">
-      <UserOutlined class="icon-font icon-with-text" />
-      {{ store.setting.userName }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer tw-pr-5"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-  </div>
+  <CommonMarkHeader></CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
-import { store } from "@/store/store";
-import { UserOutlined, PoweroffOutlined } from "@ant-design/icons-vue";
-import ZoomPaper from "@/components/ZoomPaper.vue";
-
-const closeWindow = () => {
-  window.close();
-};
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -89,8 +14,4 @@ const closeWindow = () => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
 </style>

+ 2 - 126
src/features/library/quality/MarkHeader.vue

@@ -1,121 +1,12 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
-  >
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-pl-5
-      "
-    >
-      {{
-        `${store.setting.subject.code ?? ""}-${
-          store.setting.subject.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1">
-      <div>
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber }}
-        </span>
-      </div>
-    </div>
-    <div class="tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <a-popover
-      v-if="store.isScanImage"
-      title="小助手"
-      trigger="hover"
-      class="tw-cursor-pointer tw-flex tw-gap-2 tw-items-center"
-    >
-      <template #content>
-        <table class="assistant-table">
-          <tr v-if="store.setting.subject.paperUrl">
-            <td>试卷</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['paper.modal']"
-              />
-            </td>
-          </tr>
-          <tr v-if="store.setting.subject.answerUrl">
-            <td>答案</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['answer.modal']"
-              />
-            </td>
-          </tr>
-          <tr>
-            <td>缩略图</td>
-            <td>
-              <a-switch
-                v-model:checked="store.setting.uiSetting['minimap.modal']"
-              />
-            </td>
-          </tr>
-        </table>
-      </template>
-      <div class="tw-flex">
-        小助手
-        <DownOutlined
-          style="font-size: 12px; display: inline-block"
-          class="tw-self-center tw-ml-1"
-        />
-      </div>
-    </a-popover>
-    <div class="tw-flex tw-place-items-center">
-      <UserOutlined class="icon-font icon-with-text" />
-      {{ store.setting.userName }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer tw-pr-5"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-  </div>
+  <CommonMarkHeader showPaperAndAnswer></CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
-import { store } from "@/store/store";
-import {
-  UserOutlined,
-  PoweroffOutlined,
-  DownOutlined,
-} from "@ant-design/icons-vue";
-import ZoomPaper from "@/components/ZoomPaper.vue";
-
-const closeWindow = () => {
-  window.close();
-};
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -123,19 +14,4 @@ const closeWindow = () => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.assistant-table {
-  z-index: 5500;
-  border-collapse: separate;
-  border-spacing: 0 1em;
-  color: var(--app-bold-text-color);
-  width: 240px;
-}
-.assistant-table tr td:nth-child(2) {
-  text-align: right;
-}
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
 </style>

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

@@ -12,6 +12,7 @@
       />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script setup lang="ts">
@@ -23,6 +24,7 @@ import {
 } from "@/api/importInspectPage";
 import { store } from "@/store/store";
 import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "../studentInspect/MarkBody.vue";
 import MarkBoardInspect from "./MarkBoardInspect.vue";

+ 15 - 122
src/features/student/importInspect/MarkHeader.vue

@@ -1,26 +1,10 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
+  <CommonMarkHeader
+    :isSingleStudent="isSingleStudent"
+    :clearTasks="clearTasks"
+    showScoreBoard
   >
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-pl-5
-      "
-    >
-      {{
-        `${store.setting.subject.code ?? ""}-${
-          store.setting.subject.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-4">
+    <slot name="taskInfo">
       <div>
         <span class="header-small-text">学号</span>
         <span class="highlight-text">
@@ -33,59 +17,21 @@
           {{ store.currentTask?.studentName ?? "-" }}
         </span>
       </div>
-    </div>
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-gap-2 tw-items-center tw-flex-1"
-    >
-      <span>
-        <span class="header-small-text">待复核</span>
-        <span class="highlight-text">{{
-          store.status.totalCount - store.status.markedCount ?? "-"
-        }}</span>
-      </span>
-    </div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer tw-ml-auto"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-    <div
-      class="
-        tw-flex tw-place-content-center tw-cursor-pointer tw-justify-self-end
-        menu
-      "
-      :class="[
-        store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
-      ]"
-      @click="store.toggleScoreBoard"
-    >
-      <span
-        title="给分板"
-        class="tw-inline-flex tw-place-content-center tw-relative"
-      >
-        <img
-          src="../../mark/images/right-menu.svg"
-          :class="[store.isScoreBoardVisible && 'svg-red']"
-        />
-      </span>
-      <div
-        v-if="store.isScoreBoardVisible && store.currentTask"
-        class="triangle"
-      ></div>
-    </div>
-  </div>
+    </slot>
+    <span>
+      <span class="header-small-text">待复核</span>
+      <span class="highlight-text">{{
+        store.status.totalCount - store.status.markedCount ?? "-"
+      }}</span>
+    </span>
+  </CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
 import { clearInspectedTask } from "@/api/inspectPage";
-import { onMounted } from "vue";
 import { store } from "@/store/store";
-import { PoweroffOutlined } from "@ant-design/icons-vue";
 import { useRoute } from "vue-router";
-import ZoomPaper from "@/components/ZoomPaper.vue";
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 
 const route = useRoute();
 let isSingleStudent = $ref(false);
@@ -95,49 +41,10 @@ const { studentId, subjectCode } = route.query as {
   subjectCode: string;
 };
 
-async function updateClearTask() {
-  await clearInspectedTask(studentId, subjectCode);
-}
-
-const closeWindow = async () => {
-  await updateClearTask();
-  window.close();
-};
-
-onMounted(() => {
-  // 不确定是否一定能在关闭页面时调用
-  window.addEventListener("beforeunload", () => {
-    updateClearTask().catch((e) => console.log(e));
-  });
-});
+let clearTasks = clearInspectedTask.bind(null, studentId, subjectCode);
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-.menu {
-  width: 56px;
-  height: 56px;
-  padding: 20px;
-}
-.menu:hover,
-.menu-toggled {
-  background-color: rgba(255, 255, 255, 0.2);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -145,18 +52,4 @@ onMounted(() => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-.triangle {
-  background-color: white;
-  width: 10px;
-  height: 10px;
-  clip-path: polygon(0 100%, 100% 100%, 50% 0);
-
-  position: absolute;
-  bottom: -2px;
-}
 </style>

+ 5 - 1
src/features/student/studentInspect/MarkDrawTrack.vue

@@ -44,7 +44,11 @@ const computeTopAndLeft = (track: Track | SpecialTag) => {
   return {
     top: (topInsideSlice / props.originalImageHeight) * 100 + "%",
     left: (leftInsideSlice / props.originalImageWidth) * 100 + "%",
-    "font-size": store.setting.uiSetting["answer.paper.scale"] * 2.2 + "em",
+    "font-size":
+      (store.setting.uiSetting["score.fontSize.scale"] || 1) *
+        store.setting.uiSetting["answer.paper.scale"] *
+        2.2 +
+      "em",
   };
 };
 

+ 11 - 150
src/features/student/studentInspect/MarkHeader.vue

@@ -1,100 +1,21 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
+  <CommonMarkHeader
+    :isSingleStudent="isSingleStudent"
+    :clearTasks="clearTasks"
+    showScoreBoard
   >
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-place-content-center tw-cursor-pointer tw-relative menu"
-      :class="[store.historyOpen && 'menu-toggled']"
-      @click="store.toggleHistory"
-    >
-      <span title="回看" class="tw-inline-flex tw-place-content-center">
-        <img
-          src="../../mark/images/left-menu.svg"
-          :class="[store.historyOpen && 'svg-red']"
-        />
-      </span>
-      <div v-if="store.historyOpen" class="triangle"></div>
-    </div>
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-ml-4
-      "
-    >
-      {{
-        `${store.setting.subject?.code ?? ""}-${
-          store.setting.subject?.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1">
-      <div>
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber ?? "-" }}
-        </span>
-      </div>
-    </div>
-    <div
-      v-if="!isSingleStudent"
-      class="tw-flex tw-gap-2 tw-items-center tw-flex-1"
-    >
-      <span>
-        <span class="header-small-text">待复核</span>
-        <span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
-      </span>
-    </div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <div class="tw-flex tw-place-items-center tw-justify-end tw-ml-auto">
-      {{ store.setting.userName }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-    <div
-      class="
-        tw-flex tw-place-content-center tw-cursor-pointer tw-justify-self-end
-        menu
-      "
-      :class="[
-        store.isScoreBoardVisible && store.currentTask && 'menu-toggled',
-      ]"
-      @click="store.toggleScoreBoard"
-    >
-      <span
-        title="给分板"
-        class="tw-inline-flex tw-place-content-center tw-relative"
-      >
-        <img
-          src="../../mark/images/right-menu.svg"
-          :class="[store.isScoreBoardVisible && 'svg-red']"
-        />
-      </span>
-      <div
-        v-if="store.isScoreBoardVisible && store.currentTask"
-        class="triangle"
-      ></div>
-    </div>
-  </div>
+    <span>
+      <span class="header-small-text">待复核</span>
+      <span class="highlight-text">{{ store.status.totalCount ?? "-" }}</span>
+    </span>
+  </CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
 import { clearInspectedTask } from "@/api/inspectPage";
-import { onMounted } from "vue";
 import { store } from "@/store/store";
-import { PoweroffOutlined } from "@ant-design/icons-vue";
 import { useRoute } from "vue-router";
-import ZoomPaper from "@/components/ZoomPaper.vue";
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 
 const route = useRoute();
 let isSingleStudent = $ref(false);
@@ -104,49 +25,10 @@ const { studentId, subjectCode } = route.query as {
   subjectCode: string;
 };
 
-async function updateClearTask() {
-  await clearInspectedTask(studentId, subjectCode);
-}
-
-const closeWindow = async () => {
-  await updateClearTask();
-  window.close();
-};
-
-onMounted(() => {
-  // 不确定是否一定能在关闭页面时调用
-  window.addEventListener("beforeunload", () => {
-    updateClearTask().catch((e) => console.log(e));
-  });
-});
+let clearTasks = clearInspectedTask.bind(null, studentId, subjectCode);
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-.menu {
-  width: 56px;
-  height: 56px;
-  padding: 20px;
-}
-.menu:hover,
-.menu-toggled {
-  background-color: rgba(255, 255, 255, 0.2);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -154,25 +36,4 @@ onMounted(() => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
-.triangle {
-  background-color: white;
-  width: 10px;
-  height: 10px;
-  clip-path: polygon(0 100%, 100% 100%, 50% 0);
-
-  position: absolute;
-  bottom: -2px;
-}
-.dropdown-triangle {
-  background-color: #8c8d9b;
-  width: 7px;
-  height: 5px;
-  clip-path: polygon(0 0, 100% 0, 50% 100%);
-  margin-left: 4px;
-}
 </style>

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

@@ -12,6 +12,7 @@
       <MarkBoardInspect @inspect="saveTaskToServer" @reject="rejectQuestions" />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script setup lang="ts">
@@ -27,6 +28,7 @@ import {
 } from "@/api/inspectPage";
 import { store } from "@/store/store";
 import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "./MarkBody.vue";
 import MarkHistory from "@/features/mark/MarkHistory.vue";

+ 2 - 69
src/features/student/studentTrack/MarkHeader.vue

@@ -1,74 +1,12 @@
 <template>
-  <div
-    v-if="store.setting"
-    class="tw-flex tw-gap-4 tw-justify-start tw-items-center header-container"
-  >
-    <div
-      class="
-        tw-text-white
-        tw-block
-        tw-overflow-ellipsis
-        tw-overflow-hidden
-        tw-whitespace-nowrap
-        header-big-text
-        tw-pl-5
-      "
-    >
-      {{
-        `${store.setting.subject.code ?? ""}-${
-          store.setting.subject.name ?? ""
-        }`
-      }}
-    </div>
-    <div class="tw-flex tw-gap-1">
-      <div>
-        <span class="header-small-text">编号</span>
-        <span class="highlight-text">
-          {{ store.currentTask?.secretNumber }}
-        </span>
-      </div>
-    </div>
-    <div class="tw-flex tw-gap-2 tw-items-center tw-flex-1"></div>
-    <ZoomPaper v-if="store.isScanImage" />
-    <div class="tw-flex tw-place-items-center tw-justify-end">
-      {{ store.setting.userName }}
-    </div>
-    <div
-      class="tw-flex tw-place-items-center tw-cursor-pointer tw-pr-5"
-      @click="closeWindow"
-    >
-      <PoweroffOutlined class="icon-font icon-with-text" />关闭
-    </div>
-  </div>
+  <CommonMarkHeader></CommonMarkHeader>
 </template>
 
 <script setup lang="ts">
-import { store } from "@/store/store";
-import { PoweroffOutlined } from "@ant-design/icons-vue";
-import ZoomPaper from "@/components/ZoomPaper.vue";
-
-const closeWindow = () => {
-  window.close();
-};
+import CommonMarkHeader from "@/components/CommonMarkHeader.vue";
 </script>
 
 <style scoped>
-.header-container {
-  position: relative;
-  height: 56px;
-  line-height: 16px;
-
-  background-color: var(--header-bg-color);
-  color: rgba(255, 255, 255, 0.5);
-}
-
-.header-container span {
-  vertical-align: middle;
-}
-.header-big-text {
-  font-size: 20px;
-  line-height: 30px;
-}
 .header-small-text {
   font-size: var(--app-secondary-font-size);
 }
@@ -76,9 +14,4 @@ const closeWindow = () => {
   color: white;
   font-size: var(--app-title-font-size);
 }
-
-.svg-red {
-  filter: invert(27%) sepia(51%) saturate(2878%) hue-rotate(346deg)
-    brightness(104%) contrast(97%);
-}
 </style>

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

@@ -6,12 +6,14 @@
       <CommonMarkBody v-else @error="renderError" />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script setup lang="ts">
 import { onMounted } from "vue";
 import { store } from "@/store/store";
 import MarkHeader from "./MarkHeader.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "../studentInspect/MarkBody.vue";
 import CommonMarkBody from "@/features/mark/CommonMarkBody.vue";

+ 1 - 1
src/store/store.ts

@@ -28,7 +28,7 @@ export const useMarkStore = defineStore("mark", {
         },
         statusValue: "FORMAL",
         problemTypes: [],
-        groupNumber: 0,
+        groupNumber: -987654, // 默认不可能的值
         groupTitle: "",
         topCount: 0,
         splitConfig: [],