|
@@ -3,12 +3,6 @@
|
|
class="mark-body-container tw-flex-auto tw-p-2 tw-relative"
|
|
class="mark-body-container tw-flex-auto tw-p-2 tw-relative"
|
|
ref="dragContainer"
|
|
ref="dragContainer"
|
|
>
|
|
>
|
|
- <!-- <a-spin
|
|
|
|
- :spinning="rendering"
|
|
|
|
- size="large"
|
|
|
|
- tip="Loading..."
|
|
|
|
- style="margin-top: 50px"
|
|
|
|
- > -->
|
|
|
|
<div v-if="!store.currentTask" class="tw-text-center">
|
|
<div v-if="!store.currentTask" class="tw-text-center">
|
|
{{ store.message }}
|
|
{{ store.message }}
|
|
</div>
|
|
</div>
|
|
@@ -47,16 +41,15 @@
|
|
<hr class="image-seperator" />
|
|
<hr class="image-seperator" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
- <!-- </a-spin> -->
|
|
|
|
</div>
|
|
</div>
|
|
<slot name="slot-cursor" />
|
|
<slot name="slot-cursor" />
|
|
</template>
|
|
</template>
|
|
|
|
|
|
-<script lang="ts">
|
|
|
|
|
|
+<script setup lang="ts">
|
|
import {
|
|
import {
|
|
computed,
|
|
computed,
|
|
- defineComponent,
|
|
|
|
- PropType,
|
|
|
|
|
|
+ defineEmit,
|
|
|
|
+ defineProps,
|
|
reactive,
|
|
reactive,
|
|
ref,
|
|
ref,
|
|
watch,
|
|
watch,
|
|
@@ -64,7 +57,13 @@ import {
|
|
} from "vue";
|
|
} from "vue";
|
|
import { getMarkStatus } from "./store";
|
|
import { getMarkStatus } from "./store";
|
|
import MarkDrawTrack from "./MarkDrawTrack.vue";
|
|
import MarkDrawTrack from "./MarkDrawTrack.vue";
|
|
-import { MarkResult, MarkStore, SliceImage, SpecialTag, Track } from "@/types";
|
|
|
|
|
|
+import type {
|
|
|
|
+ MarkResult,
|
|
|
|
+ MarkStore,
|
|
|
|
+ SliceImage,
|
|
|
|
+ SpecialTag,
|
|
|
|
+ Track,
|
|
|
|
+} from "@/types";
|
|
import { useTimers } from "@/setups/useTimers";
|
|
import { useTimers } from "@/setups/useTimers";
|
|
import {
|
|
import {
|
|
getDataUrlForSliceConfig,
|
|
getDataUrlForSliceConfig,
|
|
@@ -73,401 +72,393 @@ import {
|
|
} from "@/utils/utils";
|
|
} from "@/utils/utils";
|
|
import { dragImage } from "./use/draggable";
|
|
import { dragImage } from "./use/draggable";
|
|
|
|
|
|
-// should not render twice at the same time
|
|
|
|
-let __lock = false;
|
|
|
|
-let __currentLibraryId = -1 as any; // save __currentLibraryId of lock
|
|
|
|
-export default defineComponent({
|
|
|
|
- name: "MarkBody",
|
|
|
|
- components: { MarkDrawTrack },
|
|
|
|
- props: {
|
|
|
|
- useMarkResult: { type: Boolean, default: false },
|
|
|
|
- makeTrack: { type: Function },
|
|
|
|
- store: { type: Object as PropType<MarkStore>, required: true }, // 实际上不是同一个store!!!
|
|
|
|
- uniquePropName: { type: String, default: "libraryId" },
|
|
|
|
- },
|
|
|
|
- emits: ["error"],
|
|
|
|
- setup({ useMarkResult, makeTrack, uniquePropName, store }, { emit }) {
|
|
|
|
- const { dragContainer } = dragImage();
|
|
|
|
-
|
|
|
|
- const { addTimeout } = useTimers();
|
|
|
|
-
|
|
|
|
- watch(
|
|
|
|
- () => store.minimapScrollTo,
|
|
|
|
- () => {
|
|
|
|
- const container = document.querySelector(
|
|
|
|
- ".mark-body-container"
|
|
|
|
- ) as HTMLDivElement;
|
|
|
|
- addTimeout(() => {
|
|
|
|
- if (container) {
|
|
|
|
- const { scrollHeight } = container;
|
|
|
|
- container.scrollTo({
|
|
|
|
- top: scrollHeight * store.minimapScrollTo,
|
|
|
|
- });
|
|
|
|
- }
|
|
|
|
- }, 10);
|
|
|
|
|
|
+const props =
|
|
|
|
+ defineProps<{
|
|
|
|
+ useMarkResult?: boolean;
|
|
|
|
+ makeTrack: Function;
|
|
|
|
+ store: MarkStore; // 实际上不是同一个store!!!
|
|
|
|
+ uniquePropName: string; // TODO: 这个字段不需要了,是以前的rendering字段附带要求的
|
|
|
|
+ }>();
|
|
|
|
+
|
|
|
|
+const emit = defineEmit(["error"]);
|
|
|
|
+
|
|
|
|
+const {
|
|
|
|
+ useMarkResult = false,
|
|
|
|
+ makeTrack,
|
|
|
|
+ store,
|
|
|
|
+ uniquePropName = "libraryId",
|
|
|
|
+} = props;
|
|
|
|
+
|
|
|
|
+// start: 图片拖动。在轨迹模式下,仅当没有选择分数时可用。
|
|
|
|
+const { dragContainer } = dragImage();
|
|
|
|
+// end: 图片拖动
|
|
|
|
+
|
|
|
|
+const { addTimeout } = useTimers();
|
|
|
|
+
|
|
|
|
+// start: 缩略图定位
|
|
|
|
+watch(
|
|
|
|
+ () => store.minimapScrollTo,
|
|
|
|
+ () => {
|
|
|
|
+ const container = document.querySelector(
|
|
|
|
+ ".mark-body-container"
|
|
|
|
+ ) as HTMLDivElement;
|
|
|
|
+ addTimeout(() => {
|
|
|
|
+ if (container) {
|
|
|
|
+ const { scrollHeight } = container;
|
|
|
|
+ container.scrollTo({
|
|
|
|
+ top: scrollHeight * store.minimapScrollTo,
|
|
|
|
+ });
|
|
}
|
|
}
|
|
|
|
+ }, 10);
|
|
|
|
+ }
|
|
|
|
+);
|
|
|
|
+// end: 缩略图定位
|
|
|
|
+
|
|
|
|
+// start: 计算裁切图和裁切图上的分数轨迹和特殊标记轨迹
|
|
|
|
+let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
|
|
|
|
+let maxSliceWidth = 0; // 最大的裁切块宽度,图片容器以此为准
|
|
|
|
+let theFinalHeight = 0; // 最终宽度,用来定位轨迹在第几张图片,不包括image-seperator高度
|
|
|
|
+
|
|
|
|
+async function getImageUsingDataUrl(
|
|
|
|
+ dataUrl: string
|
|
|
|
+): Promise<HTMLImageElement> {
|
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
|
+ const image = new Image();
|
|
|
|
+ image.src = dataUrl;
|
|
|
|
+ image.onload = function () {
|
|
|
|
+ resolve(image);
|
|
|
|
+ };
|
|
|
|
+ image.onerror = reject;
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+async function processSliceConfig() {
|
|
|
|
+ let markResult = store.currentMarkResult as MarkResult;
|
|
|
|
+ if (useMarkResult) {
|
|
|
|
+ // check if have MarkResult for currentTask
|
|
|
|
+ if (!markResult) return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!store.currentTask) return;
|
|
|
|
+
|
|
|
|
+ const images = [];
|
|
|
|
+ // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
|
|
|
|
+ for (const sliceConfig of store.currentTask.sliceConfig) {
|
|
|
|
+ const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
|
|
|
|
+ const image = await loadImage(url);
|
|
|
|
+ images[sliceConfig.i] = image;
|
|
|
|
+ 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[sliceConfig.i];
|
|
|
|
+
|
|
|
|
+ const dataUrl = (await getDataUrlForSliceConfig(
|
|
|
|
+ image,
|
|
|
|
+ sliceConfig,
|
|
|
|
+ maxSliceWidth,
|
|
|
|
+ url
|
|
|
|
+ )) as string;
|
|
|
|
+
|
|
|
|
+ let trackLists = [] as Array<Track>;
|
|
|
|
+ if (useMarkResult) {
|
|
|
|
+ trackLists = markResult.trackList;
|
|
|
|
+ } else {
|
|
|
|
+ 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
|
|
);
|
|
);
|
|
|
|
|
|
- // 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, reject) => {
|
|
|
|
- const image = new Image();
|
|
|
|
- image.src = dataUrl;
|
|
|
|
- image.onload = function () {
|
|
|
|
- resolve(image);
|
|
|
|
- };
|
|
|
|
- image.onerror = reject;
|
|
|
|
- });
|
|
|
|
|
|
+ let tagLists = [] as Array<SpecialTag>;
|
|
|
|
+ if (useMarkResult) {
|
|
|
|
+ tagLists = markResult.specialTagList ?? [];
|
|
|
|
+ } else {
|
|
|
|
+ tagLists = store.currentTask.specialTagList ?? [];
|
|
}
|
|
}
|
|
|
|
+ const thisImageTagList = tagLists.filter(
|
|
|
|
+ (t) => t.offsetIndex === indexInSliceUrls
|
|
|
|
+ );
|
|
|
|
|
|
- async function processSliceConfig() {
|
|
|
|
- let markResult = store.currentMarkResult as MarkResult;
|
|
|
|
|
|
+ const sliceImage = await getImageUsingDataUrl(dataUrl);
|
|
|
|
+ tempSliceImagesWithTrackList.push({
|
|
|
|
+ url: dataUrl,
|
|
|
|
+ indexInSliceUrls: sliceConfig.i,
|
|
|
|
+ // 通过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() {
|
|
|
|
+ let markResult = store.currentMarkResult as MarkResult;
|
|
|
|
+ if (useMarkResult) {
|
|
|
|
+ // check if have MarkResult for currentTask
|
|
|
|
+ if (!markResult) return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ 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
|
|
|
|
+ )) as string;
|
|
|
|
+
|
|
|
|
+ let trackLists = [] as Array<Track>;
|
|
if (useMarkResult) {
|
|
if (useMarkResult) {
|
|
- // check if have MarkResult for currentTask
|
|
|
|
- if (!markResult) return;
|
|
|
|
|
|
+ trackLists = markResult.trackList;
|
|
|
|
+ } else {
|
|
|
|
+ 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
|
|
|
|
+ );
|
|
|
|
|
|
- if (!store.currentTask) return;
|
|
|
|
-
|
|
|
|
- const images = [];
|
|
|
|
- // 必须要先加载一遍,把“选择整图”的宽高重置后,再算总高度
|
|
|
|
- for (const sliceConfig of store.currentTask.sliceConfig) {
|
|
|
|
- const url = store.currentTask.sliceUrls[sliceConfig.i - 1];
|
|
|
|
- const image = await loadImage(url);
|
|
|
|
- images[sliceConfig.i] = image;
|
|
|
|
- if (sliceConfig.w === 0 && sliceConfig.h === 0) {
|
|
|
|
- // 选择整图时,w/h 为0
|
|
|
|
- sliceConfig.w = image.naturalWidth;
|
|
|
|
- sliceConfig.h = image.naturalHeight;
|
|
|
|
- }
|
|
|
|
|
|
+ let tagLists = [] as Array<SpecialTag>;
|
|
|
|
+ if (useMarkResult) {
|
|
|
|
+ tagLists = markResult.specialTagList ?? [];
|
|
|
|
+ } else {
|
|
|
|
+ tagLists = store.currentTask.specialTagList ?? [];
|
|
}
|
|
}
|
|
-
|
|
|
|
- theFinalHeight = store.currentTask.sliceConfig
|
|
|
|
- .map((v) => v.h)
|
|
|
|
- .reduce((acc, v) => (acc += v));
|
|
|
|
- maxSliceWidth = Math.max(
|
|
|
|
- ...store.currentTask.sliceConfig.map((v) => v.w)
|
|
|
|
|
|
+ const thisImageTagList = tagLists.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);
|
|
|
|
+}
|
|
|
|
|
|
- // 用来保存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[sliceConfig.i];
|
|
|
|
-
|
|
|
|
- const dataUrl = (await getDataUrlForSliceConfig(
|
|
|
|
- image,
|
|
|
|
- sliceConfig,
|
|
|
|
- maxSliceWidth,
|
|
|
|
- url
|
|
|
|
- )) as string;
|
|
|
|
-
|
|
|
|
- let trackLists = [] as Array<Track>;
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- trackLists = markResult.trackList;
|
|
|
|
- } else {
|
|
|
|
- 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
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- let tagLists = [] as Array<SpecialTag>;
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- tagLists = markResult.specialTagList ?? [];
|
|
|
|
- } else {
|
|
|
|
- tagLists = store.currentTask.specialTagList ?? [];
|
|
|
|
- }
|
|
|
|
- const thisImageTagList = tagLists.filter(
|
|
|
|
- (t) => t.offsetIndex === indexInSliceUrls
|
|
|
|
|
|
+// should not render twice at the same time
|
|
|
|
+let renderLock = false;
|
|
|
|
+const renderPaperAndMark = async () => {
|
|
|
|
+ if (!store) return;
|
|
|
|
+ if (renderLock) {
|
|
|
|
+ console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
|
|
|
|
+ await new Promise((res) => setTimeout(res, 1000));
|
|
|
|
+ await renderPaperAndMark();
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ renderLock = true;
|
|
|
|
+ // for (const s of sliceImagesWithTrackList) {
|
|
|
|
+ // // console.log("revoke", s.url);
|
|
|
|
+ // URL.revokeObjectURL(s.url);
|
|
|
|
+ // }
|
|
|
|
+ sliceImagesWithTrackList.splice(0);
|
|
|
|
+ // check if have MarkResult for currentTask
|
|
|
|
+ let markResult = store.currentMarkResult;
|
|
|
|
+
|
|
|
|
+ if ((useMarkResult && !markResult) || !store.currentTask) {
|
|
|
|
+ renderLock = false;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ store.globalMask = true;
|
|
|
|
+
|
|
|
|
+ const hasSliceConfig = store.currentTask?.sliceConfig?.length;
|
|
|
|
+
|
|
|
|
+ if (hasSliceConfig) {
|
|
|
|
+ await processSliceConfig();
|
|
|
|
+ } else {
|
|
|
|
+ await processSplitConfig();
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ sliceImagesWithTrackList.splice(0);
|
|
|
|
+ console.log("render error ", error);
|
|
|
|
+ // 图片加载出错,自动加载下一个任务
|
|
|
|
+ emit("error");
|
|
|
|
+ } finally {
|
|
|
|
+ renderLock = false;
|
|
|
|
+ store.globalMask = false;
|
|
|
|
+ }
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+watchEffect(renderPaperAndMark);
|
|
|
|
+// end: 计算裁切图和裁切图上的分数轨迹和特殊标记轨迹
|
|
|
|
+
|
|
|
|
+// start: 放大缩小和之后的滚动
|
|
|
|
+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 + "%";
|
|
|
|
+});
|
|
|
|
+// end: 放大缩小和之后的滚动
|
|
|
|
+
|
|
|
|
+// start: 显示评分状态和清除轨迹
|
|
|
|
+const markStatus = ref("");
|
|
|
|
+if (useMarkResult) {
|
|
|
|
+ watch(
|
|
|
|
+ () => store.currentTask,
|
|
|
|
+ () => {
|
|
|
|
+ markStatus.value = getMarkStatus();
|
|
|
|
+ }
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ // 清除分数轨迹
|
|
|
|
+ watchEffect(() => {
|
|
|
|
+ for (const track of store.removeScoreTracks) {
|
|
|
|
+ for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
+ sliceImage.trackList = sliceImage.trackList.filter(
|
|
|
|
+ (t) =>
|
|
|
|
+ !(
|
|
|
|
+ t.mainNumber === track.mainNumber &&
|
|
|
|
+ t.subNumber === track.subNumber &&
|
|
|
|
+ t.number === track.number
|
|
|
|
+ )
|
|
);
|
|
);
|
|
-
|
|
|
|
- const sliceImage = await getImageUsingDataUrl(dataUrl);
|
|
|
|
- tempSliceImagesWithTrackList.push({
|
|
|
|
- url: dataUrl,
|
|
|
|
- indexInSliceUrls: sliceConfig.i,
|
|
|
|
- // 通过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() {
|
|
|
|
- let markResult = store.currentMarkResult as MarkResult;
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- // check if have MarkResult for currentTask
|
|
|
|
- if (!markResult) return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- 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
|
|
|
|
- )) as string;
|
|
|
|
-
|
|
|
|
- let trackLists = [] as Array<Track>;
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- trackLists = markResult.trackList;
|
|
|
|
- } else {
|
|
|
|
- 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
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- let tagLists = [] as Array<SpecialTag>;
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- tagLists = markResult.specialTagList ?? [];
|
|
|
|
- } else {
|
|
|
|
- tagLists = store.currentTask.specialTagList ?? [];
|
|
|
|
- }
|
|
|
|
- const thisImageTagList = tagLists.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;
|
|
|
|
- }
|
|
|
|
|
|
+ // 清除后,删除,否则会影响下次切换
|
|
|
|
+ store.removeScoreTracks.splice(0);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 清除特殊标记轨迹
|
|
|
|
+ watchEffect(() => {
|
|
|
|
+ for (const track of store.currentMarkResult?.specialTagList || []) {
|
|
|
|
+ for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
+ sliceImage.tagList = sliceImage.tagList.filter((t) =>
|
|
|
|
+ store.currentMarkResult?.specialTagList.find(
|
|
|
|
+ (st) =>
|
|
|
|
+ st.offsetIndex === t.offsetIndex &&
|
|
|
|
+ st.offsetX === t.offsetX &&
|
|
|
|
+ st.offsetY === t.offsetY
|
|
|
|
+ )
|
|
|
|
+ );
|
|
}
|
|
}
|
|
- sliceImagesWithTrackList.push(...tempSliceImagesWithTrackList);
|
|
|
|
}
|
|
}
|
|
- const renderPaperAndMark = async () => {
|
|
|
|
- if (!store) return;
|
|
|
|
- const uid =
|
|
|
|
- store.currentTask &&
|
|
|
|
- (store.currentTask[
|
|
|
|
- uniquePropName as keyof MarkStore["currentTask"]
|
|
|
|
- ] as number);
|
|
|
|
- if (__lock) {
|
|
|
|
- if (uid === __currentLibraryId) {
|
|
|
|
- // rendering.value 会触发渲染,所以这里应取消。所以这里更好的做法是watch currentTask?
|
|
|
|
- console.log("重复渲染,返回");
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
|
|
|
|
- await new Promise((res) => setTimeout(res, 1000));
|
|
|
|
- await renderPaperAndMark();
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
- __lock = true;
|
|
|
|
- __currentLibraryId = uid ?? -1;
|
|
|
|
- // for (const s of sliceImagesWithTrackList) {
|
|
|
|
- // // console.log("revoke", s.url);
|
|
|
|
- // URL.revokeObjectURL(s.url);
|
|
|
|
- // }
|
|
|
|
- sliceImagesWithTrackList.splice(0);
|
|
|
|
- // check if have MarkResult for currentTask
|
|
|
|
- let markResult = store.currentMarkResult;
|
|
|
|
-
|
|
|
|
- if ((useMarkResult && !markResult) || !store.currentTask) {
|
|
|
|
- __lock = false;
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- try {
|
|
|
|
- // rendering.value = true;
|
|
|
|
- store.globalMask = true;
|
|
|
|
-
|
|
|
|
- const hasSliceConfig = store.currentTask?.sliceConfig?.length;
|
|
|
|
-
|
|
|
|
- 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;
|
|
|
|
- store.globalMask = 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;
|
|
|
|
|
|
+ if (store.currentMarkResult?.specialTagList.length === 0) {
|
|
|
|
+ for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
+ sliceImage.tagList = [];
|
|
}
|
|
}
|
|
-
|
|
|
|
- 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 + "%";
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- const markStatus = ref("");
|
|
|
|
- if (useMarkResult) {
|
|
|
|
- watch(
|
|
|
|
- () => store.currentTask,
|
|
|
|
- () => {
|
|
|
|
- markStatus.value = getMarkStatus();
|
|
|
|
- }
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- // 清除分数轨迹
|
|
|
|
- watchEffect(() => {
|
|
|
|
- for (const track of store.removeScoreTracks) {
|
|
|
|
- for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
- sliceImage.trackList = sliceImage.trackList.filter(
|
|
|
|
- (t) =>
|
|
|
|
- !(
|
|
|
|
- t.mainNumber === track.mainNumber &&
|
|
|
|
- t.subNumber === track.subNumber &&
|
|
|
|
- t.number === track.number
|
|
|
|
- )
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- // 清除后,删除,否则会影响下次切换
|
|
|
|
- store.removeScoreTracks.splice(0);
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- // 清除特殊标记轨迹
|
|
|
|
- watchEffect(() => {
|
|
|
|
- for (const track of store.currentMarkResult?.specialTagList || []) {
|
|
|
|
- for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
- sliceImage.tagList = sliceImage.tagList.filter((t) =>
|
|
|
|
- store.currentMarkResult?.specialTagList.find(
|
|
|
|
- (st) =>
|
|
|
|
- st.offsetIndex === t.offsetIndex &&
|
|
|
|
- st.offsetX === t.offsetX &&
|
|
|
|
- st.offsetY === t.offsetY
|
|
|
|
- )
|
|
|
|
- );
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- if (store.currentMarkResult?.specialTagList.length === 0) {
|
|
|
|
- for (const sliceImage of sliceImagesWithTrackList) {
|
|
|
|
- sliceImage.tagList = [];
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- });
|
|
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
+}
|
|
|
|
+// end: 显示评分状态和清除轨迹
|
|
|
|
|
|
- const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
|
|
|
|
- makeTrack && makeTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
|
|
- };
|
|
|
|
- return {
|
|
|
|
- dragContainer,
|
|
|
|
- store,
|
|
|
|
- // rendering,
|
|
|
|
- sliceImagesWithTrackList,
|
|
|
|
- answerPaperScale,
|
|
|
|
- markStatus,
|
|
|
|
- innerMakeTrack,
|
|
|
|
- };
|
|
|
|
- },
|
|
|
|
- // renderTriggered({ key, target, type }) {
|
|
|
|
- // console.log({ key, target, type });
|
|
|
|
- // },
|
|
|
|
-});
|
|
|
|
|
|
+// start: 评分
|
|
|
|
+const innerMakeTrack = (event: MouseEvent, item: SliceImage) => {
|
|
|
|
+ makeTrack && makeTrack(event, item, maxSliceWidth, theFinalHeight);
|
|
|
|
+};
|
|
|
|
+// end: 评分
|
|
|
|
+
|
|
|
|
+// onRenderTriggered(({ key, target, type }) => {
|
|
|
|
+// console.log({ key, target, type });
|
|
|
|
+// });
|
|
</script>
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|