Michael Wang 4 years ago
parent
commit
0dbd3f862a

+ 33 - 0
src/api/arbitratePage.ts

@@ -21,6 +21,13 @@ export async function getSingleArbitrateTask(historyId: string) {
   return httpApp.post("/admin/exam/arbitrate/getTask", form);
 }
 
+/** 查看仲裁任务2次分数 */
+export async function getArbitrateList(historyId: string) {
+  const form = new FormData();
+  historyId && form.append("historyId", historyId);
+  return httpApp.post("/admin/exam/arbitrate/getArbitrationList", form);
+}
+
 /** 批量仲裁得到单个学生的仲裁任务 */
 export async function getOneOfArbitrateTask(
   subjectCode: string,
@@ -56,6 +63,32 @@ export async function getArbitrateSetting(
   return httpApp.post("/admin/exam/arbitrate/getSetting", form);
 }
 
+/** 批量仲裁历史 */
+export async function getArbitrateHistory({
+  subjectCode,
+  groupNumber,
+  pageNumber = 1,
+  pageSize = 10,
+  order = "markerTime",
+  sort = "DESC",
+}: {
+  subjectCode: string;
+  groupNumber?: string;
+  pageNumber?: number; // 从1开始
+  pageSize?: number;
+  order?: "markerTime" | "markerScore";
+  sort?: "ASC" | "DESC";
+}) {
+  const form = new FormData();
+  form.append("subjectCode", subjectCode);
+  groupNumber && form.append("groupNumber", groupNumber);
+  form.append("pageNumber", pageNumber + "");
+  form.append("pageSize", pageSize + "");
+  form.append("order", order);
+  form.append("sort", sort);
+  return httpApp.post("/admin/exam/arbitrate/getHistory", form);
+}
+
 /** 保存仲裁任务 */
 export async function saveArbitrateTask(
   libraryId: string,

+ 68 - 11
src/features/arbitrate/Arbitrate.vue

@@ -2,25 +2,38 @@
   <div class="my-container">
     <mark-header />
     <div class="tw-flex tw-gap-1">
-      <ArbitrateMarkList />
       <mark-history
         @reload="reloadAndfetchTask"
         :should-reload="shouldReloadHistory"
       />
+      <ArbitrateMarkList />
       <mark-body />
-      <MarkBoardInspect @inspect="saveTaskToServer" />
+      <mark-board-key-board
+        v-if="showMarkBoardKeyBoard"
+        @submit="saveTaskToServer"
+      />
+      <mark-board-mouse v-if="showMarkBoardMouse" @submit="saveTaskToServer" />
     </div>
   </div>
+  <MinimapModal />
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, onMounted, ref } from "vue";
-import { store } from "./store";
+import { computed, defineComponent, onMounted, ref, watch } from "vue";
+// 要共用UI就要共用store
+import {
+  findCurrentTaskMarkResult,
+  removeOldPreviousMarkResult,
+  store,
+} from "@/features/mark/store";
 import MarkHeader from "./MarkHeader.vue";
 import { useRoute } from "vue-router";
 import MarkBody from "./MarkBody.vue";
+
+import MarkBoardKeyBoard from "@/features/mark/MarkBoardKeyBoard.vue";
+import MarkBoardMouse from "@/features/mark/MarkBoardMouse.vue";
+import MinimapModal from "@/features/mark/MinimapModal.vue";
 import MarkHistory from "./MarkHistory.vue";
-import MarkBoardInspect from "./MarkBoardInspect.vue";
 import { message } from "ant-design-vue";
 import {
   clearArbitrateTask,
@@ -32,6 +45,7 @@ import {
 } from "@/api/arbitratePage";
 import ArbitrateMarkList from "./ArbitrateMarkList.vue";
 import { Setting, Task } from "@/types";
+import { isNumber } from "lodash";
 
 export default defineComponent({
   name: "Arbitrate",
@@ -39,8 +53,10 @@ export default defineComponent({
     MarkHeader,
     MarkBody,
     MarkHistory,
-    MarkBoardInspect,
+    MarkBoardKeyBoard,
+    MarkBoardMouse,
     ArbitrateMarkList,
+    MinimapModal,
   },
   setup: () => {
     const route = useRoute();
@@ -128,12 +144,51 @@ export default defineComponent({
       await updateTask();
     }
 
+    const showMarkBoardKeyBoard = computed(() => {
+      return store.setting.uiSetting["normal.mode"] === "keyboard";
+    });
+    const showMarkBoardMouse = computed(() => {
+      return store.setting.uiSetting["normal.mode"] === "mouse";
+    });
+
     onMounted(async () => {
       // await updateClearTask();
-      // updateSetting();
-      // fetchTask(); // mark-header 会调用 (watchEffect)
+      // await updateSetting();
+      // await fetchTask(); // mark-header 会调用 (watchEffect)
     });
 
+    watch(
+      () => store.currentTask,
+      () => {
+        // 回评切换任务,先删除之前回评任务的markResult
+        removeOldPreviousMarkResult();
+        store.currentMarkResult = findCurrentTaskMarkResult();
+
+        // 重置当前选择的quesiton和score
+        store.currentQuestion = undefined;
+        store.currentScore = undefined;
+      }
+    );
+
+    // FIXME: 更新分数,在评卷界面不需要
+    watch(
+      () => store.currentTask,
+      () => {
+        const markResult = store.currentMarkResult;
+
+        if (markResult && store.currentTask) {
+          const scoreList = store.currentTask.questionList.map((q) => q.score);
+          markResult.scoreList = [...(scoreList as number[])];
+          console.log(markResult.scoreList);
+          markResult.markerScore =
+            (
+              markResult.scoreList.filter((s) => isNumber(s)) as number[]
+            ).reduce((acc, v) => (acc += Math.round(v * 100)), 0) / 100;
+        }
+      },
+      { deep: true }
+    );
+
     async function getSingleStuTask() {
       return getSingleArbitrateTask(libraryId);
     }
@@ -154,11 +209,11 @@ export default defineComponent({
       const res = (await saveArbitrateTask(
         store.currentTask.libraryId + "",
         store.currentTask.studentId + "",
-        0,
-        [0]
+        store.currentMarkResult?.markerScore as number,
+        store.currentMarkResult?.scoreList as Array<number>
       )) as any;
       if (res.data.success && store.currentTask) {
-        message.success({ content: "复核成功", key: mkey, duration: 2 });
+        message.success({ content: "仲裁成功", key: mkey, duration: 2 });
         if (!store.historyOpen) {
           store.currentTask = undefined;
           if (!isSingleStudent) fetchTask();
@@ -177,6 +232,8 @@ export default defineComponent({
       store,
       fetchTask,
       reloadAndfetchTask,
+      showMarkBoardKeyBoard,
+      showMarkBoardMouse,
       saveTaskToServer,
       shouldReloadHistory,
     };

+ 61 - 3
src/features/arbitrate/ArbitrateMarkList.vue

@@ -1,14 +1,64 @@
 <template>
-  <div class="container"></div>
+  <div class="container tw-mt-2 tw-ml-1">
+    <div v-for="(markDetail, index) of list" :key="index">
+      <table class="tw-mb-4">
+        <tr style="border-bottom: 1px dotted grey">
+          <td>评卷员</td>
+          <td>{{ markDetail.markerName }}</td>
+        </tr>
+        <tr>
+          <td>时间</td>
+          <td>
+            {{
+              markDetail.markTime &&
+              $filters.datetimeFilter(markDetail.markTime)
+            }}
+          </td>
+        </tr>
+        <tr>
+          <td>总分</td>
+          <td>{{ markDetail.totalScore }}</td>
+        </tr>
+        <tr>
+          <td>详情</td>
+          <td>{{ markDetail.scoreList }}</td>
+        </tr>
+      </table>
+    </div>
+  </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, reactive } from "vue";
+import { defineComponent, reactive, watch } from "vue";
+import { store } from "@/features/mark/store";
+import { getArbitrateList } from "@/api/arbitratePage";
+
+interface MarkDetail {
+  markerName: string;
+  markTime: number;
+  totalScore: number;
+  scoreList: string;
+}
 
 export default defineComponent({
   name: "ArbitrateMarkList",
   setup() {
-    const list = reactive([]);
+    let list = reactive([] as Array<MarkDetail>);
+
+    watch(
+      () => store.currentTask,
+      async () => {
+        if (store.currentTask) {
+          const res = await getArbitrateList(
+            store.currentTask?.libraryId as unknown as string
+          );
+          list.splice(0);
+          list.push(...res.data);
+        } else {
+          list.splice(0);
+        }
+      }
+    );
 
     return { list };
   },
@@ -19,4 +69,12 @@ export default defineComponent({
 .container {
   min-width: 200px;
 }
+.container table tr :first-child {
+  width: 60px;
+  font-weight: bold;
+}
+.container table tr td {
+  max-width: 140px;
+  word-wrap: break-word;
+}
 </style>

+ 0 - 265
src/features/arbitrate/MarkBoardInspect.vue

@@ -1,265 +0,0 @@
-<template>
-  <div
-    v-if="store.currentTask"
-    :style="{ display: store.MarkBoardTrackCollapse ? 'none' : 'block' }"
-    class="mark-board-track-container"
-  >
-    <div>
-      <h1 class="tw-text-3xl tw-text-center">试卷总分:{{ markerScore }}</h1>
-    </div>
-
-    <div v-if="groups">
-      <template v-for="(groupNumber, index) in groups" :key="index">
-        <div class="tw-mb-4">
-          <div
-            class="tw-flex tw-justify-between tw-place-items-center hover:tw-bg-gray-200"
-            style="border-bottom: 1px solid grey"
-            @mouseover="addFocusTrack(groupNumber, undefined, undefined)"
-            @mouseleave="removeFocusTrack"
-          >
-            分组 {{ groupNumber }}
-            <input
-              class="tw-my-auto"
-              title="打回"
-              type="checkbox"
-              @click="groupClicked(groupNumber)"
-              :checked="groupChecked(groupNumber)"
-            />
-          </div>
-          <div v-if="questions">
-            <template v-for="(question, index) in questions" :key="index">
-              <div
-                v-if="question.groupNumber === groupNumber"
-                class="question tw-flex tw-place-items-center tw-mb-1 tw-ml-2 hover:tw-bg-gray-200"
-                @mouseover="
-                  addFocusTrack(
-                    undefined,
-                    question.mainNumber,
-                    question.subNumber
-                  )
-                "
-                @mouseleave="removeFocusTrack"
-              >
-                <span class="tw-flex-1">
-                  {{ question.title }} {{ question.mainNumber }}-{{
-                    question.subNumber
-                  }}
-                </span>
-                <span class="tw-flex-1 tw-text-center">
-                  {{ question.score || 0 }}
-                </span>
-                <input
-                  title="打回"
-                  type="checkbox"
-                  @change="questionCheckChanged(question)"
-                  :checked="questionChecked(question)"
-                />
-              </div>
-            </template>
-          </div>
-        </div>
-      </template>
-    </div>
-
-    <div class="tw-flex tw-justify-center">
-      <qm-button
-        type="primary"
-        v-if="
-          store.currentTask.inspectTime && store.currentTask.inspectTime > 0
-        "
-        @click="reject"
-      >
-        打回
-      </qm-button>
-      <qm-button
-        v-else-if="checkedQuestions.length === 0"
-        @click="inspect"
-        type="primary"
-      >
-        复核
-      </qm-button>
-      <qm-button v-else @click="reject" type="primary">打回</qm-button>
-    </div>
-  </div>
-</template>
-
-<script lang="ts">
-import { Question } from "@/types";
-import { message } from "ant-design-vue";
-import { computed, defineComponent, reactive, watch } from "vue";
-import { store } from "./store";
-
-export default defineComponent({
-  name: "MarkBoardInspect",
-  emits: ["inspect", "reject"],
-  setup(props, { emit }) {
-    let checkedQuestions = reactive([] as Array<Question>);
-
-    watch(
-      () => store.currentTask,
-      () => {
-        checkedQuestions.splice(0);
-      }
-    );
-    const groups = computed(() => {
-      const gs = store.currentTask?.questionList.map((q) => q.groupNumber);
-      return [...new Set(gs)].sort((a, b) => a - b);
-    });
-
-    const questions = computed(() => {
-      const qs = store.currentTask?.questionList;
-      return qs;
-    });
-
-    const markerScore = computed(
-      () =>
-        (questions.value
-          ?.map((q) => Math.round((q.score || 0) * 100))
-          .reduce((acc, s) => acc + s) || 0) / 100
-    );
-
-    function addToCheckedQuestion(question: Question) {
-      checkedQuestions.push(question);
-    }
-    function removeCheckedQuestion(question: Question) {
-      const idx = checkedQuestions.indexOf(question);
-      checkedQuestions.splice(idx, 1);
-    }
-    function groupChecked(groupNumber: number) {
-      return (
-        checkedQuestions.filter((q) => q.groupNumber === groupNumber).length ===
-        questions.value?.filter((q) => q.groupNumber === groupNumber).length
-      );
-    }
-
-    function questionChecked(question: Question) {
-      return checkedQuestions.includes(question);
-    }
-
-    function questionCheckChanged(question: Question) {
-      const checked = questionChecked(question);
-      if (checked) {
-        removeCheckedQuestion(question);
-      } else {
-        addToCheckedQuestion(question);
-      }
-    }
-
-    function groupClicked(groupNumber: number) {
-      if (groupChecked(groupNumber)) {
-        checkedQuestions
-          .filter((q) => q.groupNumber === groupNumber)
-          .forEach((q) => {
-            const idx = checkedQuestions.indexOf(q);
-            checkedQuestions.splice(idx, 1);
-          });
-      } else {
-        questions.value
-          ?.filter((q) => q.groupNumber === groupNumber)
-          .forEach((q) => {
-            if (!questionChecked(q)) checkedQuestions.push(q);
-          });
-      }
-    }
-
-    function addFocusTrack(
-      groupNumber: number | undefined,
-      mainNumber: number | undefined,
-      subNumber: string | undefined
-    ) {
-      store.focusTracks.splice(0);
-
-      if (groupNumber) {
-        questions.value
-          ?.filter((q) => q.groupNumber === groupNumber)
-          ?.map((q) => q.trackList)
-          .reduce((acc, ts) => acc.concat(ts))
-          .forEach((t) => {
-            store.focusTracks.push(t);
-          });
-      } else {
-        questions.value
-          ?.map((q) => q.trackList)
-          .reduce((acc, ts) => acc.concat(ts))
-          .filter((t) => {
-            if (mainNumber) {
-              return t.mainNumber === mainNumber && t.subNumber === subNumber;
-            } else {
-              return false;
-            }
-          })
-          .forEach((t) => {
-            store.focusTracks.push(t);
-          });
-      }
-      // console.log(store.focusTracks);
-    }
-
-    function removeFocusTrack() {
-      store.focusTracks.splice(0);
-    }
-
-    function reject() {
-      if (checkedQuestions.length === 0) {
-        message.warn({ content: "请先选择试题。" });
-        return;
-      }
-      emit("reject", checkedQuestions);
-    }
-
-    function inspect() {
-      emit("inspect");
-    }
-
-    return {
-      store,
-      markerScore,
-      groups,
-      checkedQuestions,
-      questions,
-      groupChecked,
-      questionChecked,
-      questionCheckChanged,
-      groupClicked,
-      addFocusTrack,
-      removeFocusTrack,
-      reject,
-      inspect,
-    };
-  },
-});
-</script>
-
-<style scoped>
-.mark-board-track-container {
-  max-width: 250px;
-  min-width: 250px;
-  border-left: 1px solid grey;
-  padding-left: 6px;
-  padding-right: 6px;
-  max-height: calc(100vh - 41px);
-  overflow: scroll;
-}
-.question {
-  min-width: 100px;
-  border-bottom: 1px dotted grey;
-}
-
-.current-question {
-  border: 1px solid yellowgreen;
-  background-color: lightblue;
-}
-.single-score {
-  width: 30px;
-  height: 30px;
-  display: grid;
-  place-content: center;
-
-  border: 1px solid black;
-  border-radius: 5px;
-}
-.current-score {
-  border: 1px solid yellowgreen;
-  background-color: lightblue;
-}
-</style>

+ 14 - 335
src/features/arbitrate/MarkBody.vue

@@ -1,347 +1,26 @@
 <template>
-  <div class="mark-body-container tw-flex-auto tw-p-2" ref="dragContainer">
-    <a-spin
-      :spinning="rendering"
-      size="large"
-      tip="Loading..."
-      style="margin-top: 50px"
-    >
-      <div v-if="!store.currentTask" class="tw-text-center">
-        {{ store.message }}
-      </div>
-      <div v-else :style="{ width: answerPaperScale }">
-        <div
-          v-for="(item, index) in sliceImagesWithTrackList"
-          :key="index"
-          class="single-image-container"
-        >
-          <img :src="item.url" draggable="false" />
-          <MarkDrawTrack
-            :track-list="item.trackList"
-            :special-tag-list="item.tagList"
-            :original-image="item.originalImage"
-            :slice-image="item.sliceImage"
-            :dx="item.dx"
-            :dy="item.dy"
-          />
-          <hr class="image-seperator" />
-        </div>
-      </div>
-    </a-spin>
-  </div>
+  <CommonMarkBody
+    v-if="store"
+    :useMarkResult="false"
+    :store="store"
+    uniquePropName="libraryId"
+    @error="$emit('error')"
+  />
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, reactive, ref, watchEffect } from "vue";
-import { store } from "./store";
-import MarkDrawTrack from "./MarkDrawTrack.vue";
-import { SpecialTag, Track } from "@/types";
-import { useTimers } from "@/setups/useTimers";
-import {
-  getDataUrlForSliceConfig,
-  getDataUrlForSplitConfig,
-  loadImage,
-} from "@/utils/utils";
-import { dragImage } from "@/features/mark/use/draggable";
+import CommonMarkBody from "@/features/mark/CommonMarkBody.vue";
+import { defineComponent, watch } from "vue";
+import { store } from "@/features/mark/store";
 
-interface SliceImage {
-  url: string;
-  indexInSliceUrls: number;
-  trackList: Array<Track>;
-  tagList: Array<SpecialTag>;
-  originalImage: HTMLImageElement;
-  sliceImage: HTMLImageElement;
-  dx: number;
-  dy: number;
-  accumTopHeight: number;
-  effectiveWidth: number;
-}
-// should not render twice at the same time
-let __lock = false;
-let __currentStudentId = -1; // save __currentStudentIdof lock
 export default defineComponent({
   name: "MarkBody",
-  components: { MarkDrawTrack },
+  components: { CommonMarkBody },
   emits: ["error"],
-  setup(props, { emit }) {
-    const { dragContainer } = dragImage();
-
-    const { addTimeout } = useTimers();
-
-    function hasSliceConfig() {
-      return store.currentTask?.sliceConfig?.length;
-    }
-
-    let rendering = ref(false);
-    let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
-    let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
-    let theFinalHeight = 0; // 最终宽度,用来定位轨迹在第几张图片,不包括image-seperator高度
-
-    async function getImageUsingDataUrl(
-      dataUrl: string
-    ): Promise<HTMLImageElement> {
-      return new Promise((resolve) => {
-        const image = new Image();
-        image.src = dataUrl;
-        image.onload = function () {
-          resolve(image);
-        };
-      });
-    }
-
-    async function processSliceConfig() {
-      if (!store.currentTask) return;
-
-      const images = [];
-      const urls = [];
-      // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
-      for (const sliceConfig of store.currentTask.sliceConfig) {
-        const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
-        const image = await loadImage(url);
-        images.push(image);
-        urls.push(url);
-        if (sliceConfig.w === 0 && sliceConfig.h === 0) {
-          // 选择整图时,w/h 为0
-          sliceConfig.w = image.naturalWidth;
-          sliceConfig.h = image.naturalHeight;
-        }
-      }
-
-      theFinalHeight = store.currentTask.sliceConfig
-        .map((v) => v.h)
-        .reduce((acc, v) => (acc += v));
-      maxSliceWidth = Math.max(
-        ...store.currentTask.sliceConfig.map((v) => v.w)
-      );
-
-      // 用来保存sliceImage在整个图片容器中(不包括image-seperator)的高度范围
-      let accumTopHeight = 0;
-      let accumBottomHeight = 0;
-      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
-      for (const sliceConfig of store.currentTask.sliceConfig) {
-        accumBottomHeight += sliceConfig.h;
-        const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
-        const indexInSliceUrls = sliceConfig.i;
-        const image = images[indexInSliceUrls - 1];
-
-        const dataUrl = await getDataUrlForSliceConfig(
-          image,
-          sliceConfig,
-          maxSliceWidth,
-          url
-        );
-        const trackLists = store.currentTask.questionList
-          .map((q) => q.trackList)
-          .reduce((acc, t) => {
-            acc = acc.concat(t);
-            return acc;
-          }, [] as Array<Track>);
-        const thisImageTrackList = trackLists.filter(
-          (t) => t.offsetIndex === indexInSliceUrls
-        );
-        const thisImageTagList = (
-          store.currentTask.specialTagList ?? []
-        ).filter((t) => t.offsetIndex === indexInSliceUrls);
-
-        const sliceImage = await getImageUsingDataUrl(dataUrl);
-        tempSliceImagesWithTrackList.push({
-          url: dataUrl,
-          indexInSliceUrls,
-          // 通过positionY来定位是第几张slice的还原,并过滤出相应的track
-          trackList: thisImageTrackList.filter(
-            (t) =>
-              t.positionY >= accumTopHeight / theFinalHeight &&
-              t.positionY < accumBottomHeight / theFinalHeight
-          ),
-          tagList: thisImageTagList.filter(
-            (t) =>
-              t.positionY >= accumTopHeight / theFinalHeight &&
-              t.positionY < accumBottomHeight / theFinalHeight
-          ),
-          originalImage: image,
-          sliceImage,
-          dx: sliceConfig.x,
-          dy: sliceConfig.y,
-          accumTopHeight,
-          effectiveWidth: sliceConfig.w,
-        });
-        accumTopHeight = accumBottomHeight;
-      }
-      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
-    }
-
-    async function processSplitConfig() {
-      if (!store.currentTask) return;
-
-      const images = [];
-      for (const url of store.currentTask.sliceUrls) {
-        const image = await loadImage(url);
-        images.push(image);
-      }
-
-      const splitConfigPairs = store.setting.splitConfig
-        .map((v, index, ary) => (index % 2 === 0 ? [v, ary[index + 1]] : false))
-        .filter((v) => v) as unknown as Array<[number, number]>;
-
-      const maxSplitConfig = Math.max(...store.setting.splitConfig);
-      maxSliceWidth =
-        Math.max(...images.map((v) => v.naturalWidth)) * maxSplitConfig;
-
-      theFinalHeight =
-        splitConfigPairs.length *
-        images.reduce((acc, v) => (acc += v.naturalHeight), 0);
-
-      let accumTopHeight = 0;
-      let accumBottomHeight = 0;
-      const tempSliceImagesWithTrackList = [] as Array<SliceImage>;
-      for (const url of store.currentTask.sliceUrls) {
-        for (const config of splitConfigPairs) {
-          const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
-          const image = images[indexInSliceUrls - 1];
-
-          accumBottomHeight += image.naturalHeight;
-
-          const dataUrl = await getDataUrlForSplitConfig(
-            image,
-            config,
-            maxSliceWidth,
-            url
-          );
-
-          const trackLists = store.currentTask.questionList
-            .map((q) => q.trackList)
-            .reduce((acc, t) => {
-              acc = acc.concat(t);
-              return acc;
-            }, [] as Array<Track>);
-          const thisImageTrackList = trackLists.filter(
-            (t) => t.offsetIndex === indexInSliceUrls
-          );
-          const thisImageTagList = (
-            store.currentTask.specialTagList ?? []
-          ).filter((t) => t.offsetIndex === indexInSliceUrls);
-          const sliceImage = await getImageUsingDataUrl(dataUrl);
-          tempSliceImagesWithTrackList.push({
-            url: dataUrl,
-            indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
-            trackList: thisImageTrackList.filter(
-              (t) =>
-                t.positionY >= accumTopHeight / theFinalHeight &&
-                t.positionY < accumBottomHeight / theFinalHeight
-            ),
-            tagList: thisImageTagList.filter(
-              (t) =>
-                t.positionY >= accumTopHeight / theFinalHeight &&
-                t.positionY < accumBottomHeight / theFinalHeight
-            ),
-            originalImage: image,
-            sliceImage,
-            dx: image.naturalWidth * config[0],
-            dy: 0,
-            accumTopHeight,
-            effectiveWidth: image.naturalWidth * config[1],
-          });
-          accumTopHeight = accumBottomHeight;
-        }
-      }
-      sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
-    }
-    const renderPaperAndMark = async () => {
-      if (__lock) {
-        if (store.currentTask?.studentId === __currentStudentId) {
-          console.log("重复渲染,返回");
-          return;
-        }
-        console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
-        await new Promise((res) => setTimeout(res, 1000));
-        await renderPaperAndMark();
-        return;
-      }
-      __lock = true;
-      __currentStudentId = store.currentTask?.studentId ?? -1;
-      for (const s of sliceImagesWithTrackList) {
-        // console.log("revoke", s.url);
-        URL.revokeObjectURL(s.url);
-      }
-      sliceImagesWithTrackList.splice(0);
-
-      if (!store.currentTask) {
-        __lock = false;
-        return;
-      }
-
-      try {
-        rendering.value = true;
-        if (hasSliceConfig()) {
-          await processSliceConfig();
-        } else {
-          await processSplitConfig();
-        }
-      } catch (error) {
-        sliceImagesWithTrackList.splice(0);
-        console.log("render error ", error);
-        // 图片加载出错,自动加载下一个任务
-        emit("error");
-      } finally {
-        __lock = false;
-        rendering.value = false;
-      }
-    };
-
-    watchEffect(renderPaperAndMark);
-
-    const answerPaperScale = computed(() => {
-      // 放大、缩小不影响页面之前的滚动条定位
-      let percentWidth = 0;
-      let percentTop = 0;
-      const container = document.querySelector(
-        ".mark-body-container"
-      ) as HTMLDivElement;
-      if (container) {
-        const { scrollLeft, scrollTop, scrollWidth, scrollHeight } = container;
-        percentWidth = scrollLeft / scrollWidth;
-        percentTop = scrollTop / scrollHeight;
-      }
-
-      addTimeout(() => {
-        if (container) {
-          const { scrollWidth, scrollHeight } = container;
-          container.scrollTo({
-            left: scrollWidth * percentWidth,
-            top: scrollHeight * percentTop,
-          });
-        }
-      }, 10);
-      const scale = store.setting.uiSetting["answer.paper.scale"];
-      return scale * 100 + "%";
-    });
-
-    return {
-      dragContainer,
-      store,
-      rendering,
-      sliceImagesWithTrackList,
-      answerPaperScale,
-    };
+  setup() {
+    return { store };
   },
 });
 </script>
 
-<style scoped>
-.mark-body-container {
-  height: calc(100vh - 41px);
-  overflow: scroll;
-  background-size: 8px 8px;
-  background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
-    linear-gradient(to bottom, transparent 4px, #e7e7e7 4px);
-}
-.mark-body-container img {
-  width: 100%;
-}
-.single-image-container {
-  position: relative;
-}
-.image-seperator {
-  border: 2px solid rgba(120, 120, 120, 0.1);
-}
-</style>
+<style scoped></style>

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

@@ -52,9 +52,9 @@
         />
       </li>
     </ul>
-    <!-- <div @click="toggleHistory" v-if="!isSingleStudent" title="回看">
+    <div @click="toggleHistory" v-if="!isSingleStudent" title="回看">
       <HistoryOutlined class="icon-font icon-font-size-20" />
-    </div> -->
+    </div>
     <div class="tw-flex tw-place-items-center">
       <UserOutlined class="icon-font icon-with-text" />{{
         store.setting.userName
@@ -71,7 +71,7 @@
 
 <script lang="ts">
 import { computed, defineComponent, onMounted, ref } from "vue";
-import { store } from "./store";
+import { store } from "@/features/mark/store";
 import {
   ZoomInOutlined,
   ZoomOutOutlined,
@@ -123,6 +123,9 @@ export default defineComponent({
     const normalScale = () => {
       store.setting.uiSetting["answer.paper.scale"] = 1;
     };
+    const toggleHistory = () => {
+      store.historyOpen = !store.historyOpen;
+    };
     const greaterThanOneScale = computed(() => {
       return store.setting.uiSetting["answer.paper.scale"] > 1;
     });
@@ -154,6 +157,7 @@ export default defineComponent({
       normalScale,
       greaterThanOneScale,
       lessThanOneScale,
+      toggleHistory,
       closeWindow,
     };
   },

+ 6 - 4
src/features/arbitrate/MarkHistory.vue

@@ -52,13 +52,13 @@
 </template>
 
 <script lang="ts">
-import { getInspectedHistory } from "@/api/inspectPage";
 import { Task } from "@/types";
 import { defineComponent, ref, watch, watchEffect } from "vue";
 import { useRoute } from "vue-router";
-import { store } from "./store";
+import { store } from "@/features/mark/store";
 import { CloseOutlined } from "@ant-design/icons-vue";
 import { cloneDeep } from "lodash";
+import { getArbitrateHistory } from "@/api/arbitratePage";
 
 export default defineComponent({
   name: "MarkHistory",
@@ -69,8 +69,9 @@ export default defineComponent({
   emits: ["reload"],
   setup(props, { emit }) {
     const route = useRoute();
-    const { subjectCode } = route.query as {
+    const { subjectCode, groupNumber } = route.query as {
       subjectCode: string;
+      groupNumber: string;
     };
 
     watchEffect(async () => {
@@ -104,10 +105,11 @@ export default defineComponent({
       pageSize?: number;
     }) {
       loading.value = true;
-      const res = await getInspectedHistory({
+      const res = await getArbitrateHistory({
         pageNumber,
         pageSize,
         subjectCode,
+        groupNumber,
       });
       loading.value = false;
       if (res.data) {

+ 1 - 0
src/features/mark/MarkBody.vue

@@ -231,6 +231,7 @@ export default defineComponent({
       if (markResult && store.currentTask) {
         const scoreList = store.currentTask.questionList.map((q) => q.score);
         markResult.scoreList = [...(scoreList as number[])];
+        console.log(markResult.scoreList);
         markResult.markerScore =
           (markResult.scoreList.filter((s) => isNumber(s)) as number[]).reduce(
             (acc, v) => (acc += Math.round(v * 100)),