Explorar o código

refactor draw process

Michael Wang %!s(int64=4) %!d(string=hai) anos
pai
achega
974a9af79e

+ 6 - 6
src/components/mark/Mark.vue

@@ -25,7 +25,11 @@ import {
   saveTask,
   updateUISetting,
 } from "@/api/markPage";
-import { findCurrentTaskMarkResult, store } from "./store";
+import {
+  findCurrentTaskMarkResult,
+  removeCurrentMarkResult,
+  store,
+} from "./store";
 import MarkHeader from "./MarkHeader.vue";
 import MarkBody from "./MarkBody.vue";
 import { useTimers } from "@/setups/useTimers";
@@ -148,11 +152,7 @@ export default defineComponent({
       const res = (await saveTask()) as any;
       updateStatus();
       if (res.data.success && store.currentTask) {
-        let { libraryId, studentId } = store.currentTask;
-        const i = store.markResults.findIndex(
-          (s) => s.libraryId === libraryId && s.studentId === studentId
-        );
-        store.markResults.splice(i, 1);
+        removeCurrentMarkResult();
         store.currentTask = undefined;
         store.tasks.shift();
       } else {

+ 171 - 189
src/components/mark/MarkBody.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="mark-body-container tw-flex-auto" ref="container">
+  <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
@@ -9,7 +9,7 @@
       >
         <img
           :src="item.url"
-          @click="(event) => makeMark(event, item)"
+          @click="(event) => makeScoreTrack(event, item)"
           draggable="false"
         />
         <MarkDrawTrack
@@ -30,8 +30,9 @@ import { computed, defineComponent, reactive, ref, watchEffect } from "vue";
 import { findCurrentTaskMarkResult, store } from "./store";
 import filters from "@/filters";
 import MarkDrawTrack from "./MarkDrawTrack.vue";
-import { MarkResult, Track } from "@/types";
+import { Track } from "@/types";
 import { useTimers } from "@/setups/useTimers";
+import { loadImage } from "@/utils/utils";
 
 interface SliceImage {
   url: string;
@@ -50,114 +51,175 @@ export default defineComponent({
   setup() {
     const { addTimeout } = useTimers();
 
-    const container = ref(null);
+    function hasSliceConfig() {
+      return store.currentTask?.sliceConfig?.length;
+    }
+
     let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
     let _studentId = -1; // 判断是否改变了任务
-    let maxSliceWidth = 0;
-    let theFinalHeight = 0;
+    let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
+    let theFinalHeight = 0; // 最终宽度,用来定位轨迹在第几张图片,不包括image-seperator高度
 
-    const renderPaperAndMark = async () => {
-      async function loadImage(url: string): Promise<HTMLImageElement> {
-        return new Promise((resolve, reject) => {
-          const image = new Image();
-          image.setAttribute("crossorigin", "anonymous");
-          image.src = url;
-          image.onload = () => resolve(image);
-          image.onerror = reject;
-        });
+    async function processSliceConfig() {
+      // check if have MarkResult for currentTask
+      let markResult = findCurrentTaskMarkResult();
+
+      if (!markResult || !store.currentTask) return;
+
+      // TODO: 图片加载出错,自动加载下一个任务
+      for (const url of store.currentTask.sliceUrls) {
+        await loadImage(filters.toCompleteUrl(url));
       }
-      if (!store.currentTask?.libraryId) {
-        return;
+      // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
+      for (const sliceConfig of store.currentTask.sliceConfig) {
+        const url = filters.toCompleteUrl(
+          store.currentTask.sliceUrls[sliceConfig.i - 1]
+        );
+        const image = await loadImage(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;
+      for (const sliceConfig of store.currentTask.sliceConfig) {
+        accumBottomHeight += sliceConfig.h;
+        const url = filters.toCompleteUrl(
+          store.currentTask.sliceUrls[sliceConfig.i - 1]
+        );
+        const image = await loadImage(url);
 
+        const canvas = document.createElement("canvas");
+        // canvas.width = sliceConfig.w;
+        canvas.width = Math.max(sliceConfig.w, maxSliceWidth);
+        canvas.height = sliceConfig.h;
+        const ctx = canvas.getContext("2d");
+        if (!ctx) {
+          console.log('canvas.getContext("2d") error');
+        }
+        // drawImage 画图软件透明色
+        ctx?.drawImage(
+          image,
+          sliceConfig.x,
+          sliceConfig.y,
+          sliceConfig.w,
+          sliceConfig.h,
+          0,
+          0,
+          sliceConfig.w,
+          sliceConfig.h
+        );
+        // console.log(image, canvas.height, sliceConfig, ctx);
+        // console.log(canvas.toDataURL());
+        const thisImageTrackList = markResult.trackList.filter(
+          (v) => v.offsetIndex === sliceConfig.i
+        );
+
+        const dataUrl = canvas.toDataURL();
+        const sliceImage = new Image();
+        sliceImage.src = dataUrl;
+        // sliceConfig.x + sliceConfig.w
+        sliceImagesWithTrackList.push({
+          url: dataUrl,
+          indexInSliceUrls: sliceConfig.i,
+          // 通过positionY来定位是第几张slice的还原,并过滤出相应的track
+          trackList: thisImageTrackList.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;
+      }
+    }
+
+    async function processSplitConfig() {
       // check if have MarkResult for currentTask
       let markResult = findCurrentTaskMarkResult();
-      // console.log("watcheffect markResult 1", markResult, store.markResults);
 
       if (!markResult || !store.currentTask) return;
-      // store.markResults.splice(store.markResults.indexOf(markResult), 1);
-      // store.markResults.push(markResult);
-      // console.log("watcheffect markResult 3", markResult, store.markResults);
-      // const allTrackList = findCurrentTaskMarkResult().trackList;
-      // console.log(allTrackList);
-      // console.log(store.markResults);
 
-      // reset sliceImagesWithTrackList
-      if (_studentId !== store.currentTask.studentId) {
-        // 还原轨迹用得上
-        sliceImagesWithTrackList.splice(0);
-        _studentId = store.currentTask.studentId;
+      const images = [];
+      for (const url of store.currentTask.sliceUrls) {
+        const image = await loadImage(filters.toCompleteUrl(url));
+        images.push(image);
       }
 
-      if (store.currentTask.sliceConfig?.length) {
-        for (const url of store.currentTask.sliceUrls) {
-          await loadImage(filters.toCompleteUrl(url));
-        }
-        // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
-        for (const sliceConfig of store.currentTask.sliceConfig) {
-          const url = filters.toCompleteUrl(
-            store.currentTask.sliceUrls[sliceConfig.i - 1]
-          );
-          const image = await loadImage(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));
-        let accumTopHeight = 0;
-        let accumBottomHeight = 0;
-        for (const sliceConfig of store.currentTask.sliceConfig) {
-          accumBottomHeight += sliceConfig.h;
-          const url = filters.toCompleteUrl(
-            store.currentTask.sliceUrls[sliceConfig.i - 1]
-          );
-          const image = await loadImage(url);
-          if (sliceConfig.w === 0 && sliceConfig.h === 0) {
-            // 选择整图时,w/h 为0
-            sliceConfig.w = image.naturalWidth;
-            sliceConfig.h = image.naturalHeight;
-          }
+      // TODO: add loading
+      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 div = (container.value as unknown) as HTMLDivElement;
-          maxSliceWidth = Math.max(
-            ...store.currentTask.sliceConfig.map((v) => v.w)
-          );
+      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;
+      for (const url of store.currentTask.sliceUrls) {
+        const completeUrl = filters.toCompleteUrl(url);
+
+        for (const config of splitConfigPairs) {
+          const image = await loadImage(completeUrl);
+
+          accumBottomHeight += image.naturalHeight;
+
+          const width = image.naturalWidth * (config[1] - config[0]);
           const canvas = document.createElement("canvas");
-          // canvas.width = sliceConfig.w;
-          canvas.width = Math.max(sliceConfig.w, maxSliceWidth);
-          canvas.height = sliceConfig.h;
+          canvas.width = Math.max(width, maxSliceWidth);
+          canvas.height = image.naturalHeight;
           const ctx = canvas.getContext("2d");
+          if (!ctx) {
+            console.log('canvas.getContext("2d") error');
+          }
           // drawImage 画图软件透明色
           ctx?.drawImage(
             image,
-            sliceConfig.x,
-            sliceConfig.y,
-            sliceConfig.w,
-            sliceConfig.h,
+            image.naturalWidth * config[0],
             0,
+            image.naturalWidth * config[1],
+            image.naturalHeight,
             0,
-            sliceConfig.w,
-            sliceConfig.h
+            0,
+            image.naturalWidth * config[1],
+            image.naturalHeight
           );
           // console.log(image, canvas.height, sliceConfig, ctx);
           // console.log(canvas.toDataURL());
+
           const thisImageTrackList = markResult.trackList.filter(
-            (v) => v.offsetIndex === sliceConfig.i
+            (t) =>
+              t.offsetIndex ===
+              (store.currentTask &&
+                store.currentTask.sliceUrls.indexOf(url) + 1)
           );
 
           const dataUrl = canvas.toDataURL();
           const sliceImage = new Image();
           sliceImage.src = dataUrl;
-          // sliceConfig.x + sliceConfig.w
           sliceImagesWithTrackList.push({
-            url: dataUrl,
-            indexInSliceUrls: sliceConfig.i,
-            // 通过positionY来定位是第几张slice的还原,并过滤出相应的track
+            url: canvas.toDataURL(),
+            indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
             trackList: thisImageTrackList.filter(
               (t) =>
                 t.positionY >= accumTopHeight / theFinalHeight &&
@@ -165,94 +227,32 @@ export default defineComponent({
             ),
             originalImage: image,
             sliceImage,
-            dx: sliceConfig.x,
-            dy: sliceConfig.y,
+            dx: image.naturalWidth * config[0],
+            dy: 0,
             accumTopHeight,
-            effectiveWidth: sliceConfig.w,
+            effectiveWidth: image.naturalWidth * config[1],
           });
           accumTopHeight = accumBottomHeight;
         }
-      } else {
-        const images = [];
-        for (const url of store.currentTask.sliceUrls) {
-          const image = await loadImage(filters.toCompleteUrl(url));
-          images.push(image);
-        }
-
-        // TODO: add loading
-        const newConfig = (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 =
-          newConfig.length *
-          images.reduce((acc, v) => (acc += v.naturalHeight), 0);
-
-        let accumTopHeight = 0;
-        let accumBottomHeight = 0;
-        for (const url of store.currentTask.sliceUrls) {
-          const completeUrl = filters.toCompleteUrl(url);
-
-          for (const config of newConfig) {
-            const image = await loadImage(completeUrl);
-
-            accumBottomHeight += image.naturalHeight;
-            const div = (container.value as unknown) as HTMLDivElement;
+      }
+    }
+    const renderPaperAndMark = async () => {
+      // check if have MarkResult for currentTask
+      let markResult = findCurrentTaskMarkResult();
 
-            const width = image.naturalWidth * (config[1] - config[0]);
-            const canvas = document.createElement("canvas");
-            canvas.width = Math.max(width, maxSliceWidth);
-            canvas.height = image.naturalHeight;
-            const ctx = canvas.getContext("2d");
-            // drawImage 画图软件透明色
-            ctx?.drawImage(
-              image,
-              image.naturalWidth * config[0],
-              0,
-              image.naturalWidth * config[1],
-              image.naturalHeight,
-              0,
-              0,
-              image.naturalWidth * config[1],
-              image.naturalHeight
-            );
-            // console.log(image, canvas.height, sliceConfig, ctx);
-            // console.log(canvas.toDataURL());
+      if (!markResult || !store.currentTask) return;
 
-            const thisImageTrackList = markResult.trackList.filter(
-              (t) =>
-                t.offsetIndex ===
-                (store.currentTask &&
-                  store.currentTask.sliceUrls.indexOf(url) + 1)
-            );
+      // reset sliceImagesWithTrackList ,当切换任务时,要重新绘制图片和轨迹
+      if (_studentId !== store.currentTask.studentId) {
+        // 还原轨迹用得上
+        sliceImagesWithTrackList.splice(0);
+        _studentId = store.currentTask.studentId;
+      }
 
-            const dataUrl = canvas.toDataURL();
-            const sliceImage = new Image();
-            sliceImage.src = dataUrl;
-            sliceImagesWithTrackList.push({
-              url: canvas.toDataURL(),
-              indexInSliceUrls: store.currentTask.sliceUrls.indexOf(url) + 1,
-              trackList: thisImageTrackList.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;
-          }
-        }
+      if (hasSliceConfig()) {
+        await processSliceConfig();
+      } else {
+        await processSplitConfig();
       }
     };
 
@@ -262,25 +262,18 @@ export default defineComponent({
       // 放大、缩小不影响页面之前的滚动条定位
       let percentWidth = 0;
       let percentTop = 0;
-      if (document.querySelector(".mark-body-container")) {
-        const container = document.querySelector(
-          ".mark-body-container"
-        ) as HTMLDivElement;
-        const scrollLeft = container.scrollLeft;
-        const scrollTop = container.scrollTop;
-        const scrollWidth = container.scrollWidth;
-        const scrollHeight = container.scrollHeight;
+      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 (document.querySelector(".mark-body-container")) {
-          const container = document.querySelector(
-            ".mark-body-container"
-          ) as HTMLDivElement;
-          const scrollWidth = container.scrollWidth;
-          const scrollHeight = container.scrollHeight;
+        if (container) {
+          const { scrollWidth, scrollHeight } = container;
           container.scrollTo({
             left: scrollWidth * percentWidth,
             top: scrollHeight * percentTop,
@@ -291,7 +284,7 @@ export default defineComponent({
       return scale * 100 + "%";
     });
 
-    const makeMark = (event: MouseEvent, item: SliceImage) => {
+    const makeScoreTrack = (event: MouseEvent, item: SliceImage) => {
       // console.log(item);
       if (!store.currentQuestion || typeof store.currentScore === "undefined")
         return;
@@ -299,7 +292,7 @@ export default defineComponent({
       const track = {} as Track;
       track.mainNumber = store.currentQuestion?.mainNumber;
       track.subNumber = store.currentQuestion?.subNumber;
-      track.number = Math.round(Math.random() * 10000000);
+      track.number = Date.now();
       track.score = store.currentScore;
       track.offsetIndex = item.indexInSliceUrls;
       track.offsetX = Math.round(
@@ -311,34 +304,23 @@ export default defineComponent({
       track.positionX = (track.offsetX - item.dx) / maxSliceWidth;
       track.positionY =
         (track.offsetY - item.dy + item.accumTopHeight) / theFinalHeight;
-      // console.log(
-      //   track,
-      //   item.originalImage.naturalWidth,
-      //   item.originalImage.naturalHeight,
-      //   target.naturalWidth,
-      //   target.width,
-      //   track.offsetX,
-      //   item.effectiveWidth + item.dx
-      // );
       if (track.offsetX > item.effectiveWidth + item.dx) {
         console.log("不在有效宽度内,轨迹不生效");
         return;
       }
       store.currentScore = undefined;
       const markResult = findCurrentTaskMarkResult();
-      // console.log("makemark markresult", markResult);
       if (markResult) {
         markResult.trackList = [...markResult.trackList, track];
       }
-      // sliceImagesWithTrackList.find(s => s.indexInSliceUrls === item.indexInSliceUrls)
       item.trackList.push(track);
     };
+
     return {
-      container,
       store,
       sliceImagesWithTrackList,
       answerPaperScale,
-      makeMark,
+      makeScoreTrack,
     };
   },
   // renderTriggered({ key, target, type }) {

+ 19 - 0
src/components/mark/store.ts

@@ -81,6 +81,25 @@ export function findCurrentTaskMarkResult() {
   );
 }
 
+export function removeCurrentMarkResult() {
+  if (!store.currentTask) return;
+
+  function clearMarkResultFromTask(task: Task) {
+    let { libraryId, studentId } = task;
+    const i = store.markResults.findIndex(
+      (s) => s.libraryId === libraryId && s.studentId === studentId
+    );
+    store.markResults.splice(i, 1);
+  }
+
+  clearMarkResultFromTask(store.currentTask);
+
+  for (const task of store.tasks) {
+    // 清理不在当前任务中的markResult,防止回评产生的markResult造成垃圾
+    clearMarkResultFromTask(task);
+  }
+}
+
 export const store = reactive(obj);
 
 // 轨迹模式下,添加轨迹,更新分数

+ 1 - 0
src/components/mark/use/keyboardAndMouse.ts

@@ -11,6 +11,7 @@ export function keyMouse() {
     }
   }
 
+  // TODO: findCurrentTaskMarkResult => store.currentMarkResult
   const markResult = findCurrentTaskMarkResult();
 
   // 普通模式更新分数时

+ 14 - 0
src/utils/utils.ts

@@ -0,0 +1,14 @@
+/**
+ * 异步获取图片
+ * @param url 完整的图片路径
+ * @returns Promise<HTMLImageElement>
+ */
+export async function loadImage(url: string): Promise<HTMLImageElement> {
+  return new Promise((resolve, reject) => {
+    const image = new Image();
+    image.setAttribute("crossorigin", "anonymous");
+    image.src = url;
+    image.onload = () => resolve(image);
+    image.onerror = reject;
+  });
+}