瀏覽代碼

复核:轨迹还原

Michael Wang 4 年之前
父節點
當前提交
073a218550

+ 6 - 11
src/components/inspect/Inspect.vue

@@ -1,16 +1,11 @@
 <template>
 <template>
   <div class="my-container">
   <div class="my-container">
     <mark-header />
     <mark-header />
-    <!-- <div class="tw-flex tw-gap-1">
-      <mark-history />
+    <div class="tw-flex tw-gap-1">
+      <!-- <mark-history /> -->
       <mark-body />
       <mark-body />
-      <mark-board-track v-if="showMarkBoardTrack" @submit="saveTaskToServer" />
-      <mark-board-key-board
-        v-if="showMarkBoardKeyBoard"
-        @submit="saveTaskToServer"
-      />
-      <mark-board-mouse v-if="showMarkBoardMouse" @submit="saveTaskToServer" />
-    </div> -->
+      <!-- <mark-board-track v-if="showMarkBoardTrack" @submit="saveTaskToServer" /> -->
+    </div>
   </div>
   </div>
 </template>
 </template>
 
 
@@ -26,14 +21,14 @@ import {
 import { store } from "./store";
 import { store } from "./store";
 import MarkHeader from "./MarkHeader.vue";
 import MarkHeader from "./MarkHeader.vue";
 import { useRoute } from "vue-router";
 import { useRoute } from "vue-router";
-// import MarkBody from "./MarkBody.vue";
+import MarkBody from "./MarkBody.vue";
 // import MarkHistory from "./MarkHistory.vue";
 // import MarkHistory from "./MarkHistory.vue";
 
 
 export default defineComponent({
 export default defineComponent({
   name: "Inspect",
   name: "Inspect",
   components: {
   components: {
     MarkHeader,
     MarkHeader,
-    // MarkBody,
+    MarkBody,
     // MarkHistory,
     // MarkHistory,
   },
   },
   setup: () => {
   setup: () => {

+ 155 - 0
src/components/inspect/MarkBody.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="mark-body-container tw-flex-auto">
+    <div v-if="!store.currentTask" class="tw-text-center">暂无待复核任务</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"
+          :original-image="item.originalImage"
+        />
+        <hr class="image-seperator" />
+      </div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { computed, defineComponent, reactive, watchEffect } from "vue";
+import { store } from "./store";
+import filters from "@/filters";
+import MarkDrawTrack from "./MarkDrawTrack.vue";
+import { Track } from "@/types";
+import { useTimers } from "@/setups/useTimers";
+import { loadImage } from "@/utils/utils";
+
+interface SliceImage {
+  url: string;
+  indexInSliceUrls: number;
+  trackList: Array<Track>;
+  originalImage: HTMLImageElement;
+}
+export default defineComponent({
+  name: "MarkBody",
+  components: { MarkDrawTrack },
+  setup() {
+    const { addTimeout } = useTimers();
+
+    let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
+    let _studentId = -1; // 判断是否改变了任务
+
+    async function processImage() {
+      if (!store.currentTask) return;
+
+      const images = [];
+      for (const url of store.currentTask.sliceUrls) {
+        console.log(url);
+        const image = await loadImage(
+          filters.toCompleteUrlWithFileServer(store.setting.fileServer, url)
+        );
+        images.push(image);
+      }
+
+      // TODO: add loading
+      for (const url of store.currentTask.sliceUrls) {
+        const completeUrl = filters.toCompleteUrlWithFileServer(
+          store.setting.fileServer,
+          url
+        );
+
+        const image = await loadImage(completeUrl);
+
+        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 ===
+            (store.currentTask && store.currentTask.sliceUrls.indexOf(url) + 1)
+        );
+
+        sliceImagesWithTrackList.push({
+          url: completeUrl,
+          indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
+          trackList: thisImageTrackList,
+          originalImage: image,
+        });
+      }
+    }
+    // 供回退和清除使用
+    // let trackLen = store.currentMarkResult?.trackList.length;
+    const renderPaperAndMark = async () => {
+      if (!store.currentTask) return;
+
+      // reset sliceImagesWithTrackList ,当切换任务时,要重新绘制图片和轨迹
+      if (_studentId !== store.currentTask.studentId) {
+        // 还原轨迹用得上
+        sliceImagesWithTrackList.splice(0);
+        _studentId = store.currentTask.studentId;
+      }
+
+      await processImage();
+    };
+
+    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 {
+      store,
+      sliceImagesWithTrackList,
+      answerPaperScale,
+    };
+  },
+});
+</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>

+ 65 - 0
src/components/inspect/MarkDrawTrack.vue

@@ -0,0 +1,65 @@
+<template>
+  <template v-for="(track, index) in trackList" :key="index">
+    <div class="score-container" :style="computeTopAndLeft(track)">
+      <span class="tw-m-auto">
+        {{ track.score }}
+      </span>
+    </div>
+  </template>
+</template>
+
+<script lang="ts">
+import { Track } from "@/types";
+import { defineComponent, PropType } from "vue";
+import { store } from "./store";
+
+export default defineComponent({
+  name: "MarkDrawTrack",
+  props: {
+    trackList: {
+      type: Array as PropType<Array<Track>>,
+    },
+    originalImage: {
+      type: Object as PropType<HTMLImageElement>,
+      required: true,
+    },
+  },
+  setup({ trackList, originalImage }) {
+    const computeTopAndLeft = (track: Track) => {
+      const topInsideSlice = track.offsetY;
+      const leftInsideSlice = track.offsetX;
+      // console.log({
+      //   topInsideSlice,
+      //   leftInsideSlice,
+      //   offx: track.offsetX,
+      //   offy: track.offsetY,
+      // });
+      return {
+        top: (topInsideSlice / originalImage.naturalHeight) * 100 + "%",
+        left: (leftInsideSlice / originalImage.naturalWidth) * 100 + "%",
+        "font-size": store.setting.uiSetting["answer.paper.scale"] * 2.2 + "em",
+      };
+    };
+
+    return { store, computeTopAndLeft };
+  },
+});
+</script>
+
+<style scoped>
+.score-container {
+  position: absolute;
+  display: flex;
+  place-content: center;
+  color: red;
+
+  /* to center score */
+  width: 200px;
+  height: 200px;
+  margin-top: -100px;
+  margin-left: -100px;
+
+  /* to click through div */
+  pointer-events: none;
+}
+</style>

+ 1 - 1
src/components/inspect/store.ts

@@ -14,7 +14,7 @@ const obj = {
   status: {
   status: {
     totalCount: 0,
     totalCount: 0,
   },
   },
-  currentTask: <Task>{},
+  currentTask: undefined,
   historyOpen: false,
   historyOpen: false,
   MarkBoardTrackCollapse: false,
   MarkBoardTrackCollapse: false,
   historyTasks: [],
   historyTasks: [],

+ 4 - 0
src/filters/index.ts

@@ -11,4 +11,8 @@ export default {
   toCompleteUrl(path: string) {
   toCompleteUrl(path: string) {
     return store.setting.fileServer + path;
     return store.setting.fileServer + path;
   },
   },
+  /** 根据fileServer得到完整的资源路径 */
+  toCompleteUrlWithFileServer(fileServer: string, path: string) {
+    return fileServer + path;
+  },
 };
 };