123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- <template>
- <div
- ref="dragContainer"
- class="mark-body-container tw-flex-auto tw-p-2 tw-pt-0"
- @scroll="viewScroll"
- >
- <div v-if="!store.currentTask" class="tw-text-center none-tip">
- {{ store.message }}
- </div>
- <div
- v-else-if="!sliceImagesWithTrackList.length"
- class="tw-text-center none-tip"
- style="color: red"
- >
- 考生答卷未上传
- </div>
- <div v-else :style="{ width: answerPaperScale }" class="tw-pt-2">
- <div
- v-for="(item, index) in sliceImagesWithTrackList"
- :key="index"
- class="single-image-container"
- :style="{
- width: item.width,
- }"
- >
- <img :src="item.url" draggable="false" />
- <MarkDrawTrack
- :trackList="item.trackList"
- :specialTagList="item.tagList"
- :sliceImageHeight="item.originalImageHeight"
- :sliceImageWidth="item.originalImageWidth"
- :dx="0"
- :dy="0"
- />
- <hr class="image-seperator" />
- </div>
- </div>
- <ZoomPaper v-if="store.isScanImage && sliceImagesWithTrackList.length" />
- </div>
- </template>
- <script setup lang="ts">
- import { reactive, watch } from "vue";
- import { store } from "@/store/app";
- import MarkDrawTrack from "@/features/mark/MarkDrawTrack.vue";
- import type { SpecialTag, Track, ColorMap } from "@/types";
- import { useTimers } from "@/setups/useTimers";
- import { loadImage, addHeaderTrackColorAttr } from "@/utils/utils";
- import useDraggable from "@/features/mark/composables/useDraggable";
- import ZoomPaper from "@/components/ZoomPaper.vue";
- interface SliceImage {
- url: string;
- trackList: Array<Track>;
- tagList: Array<SpecialTag>;
- originalImageWidth: number;
- originalImageHeight: number;
- width: string; // 图片在整个图片列表里面的宽度比例
- }
- const { origImageUrls = "sliceUrls" } = defineProps<{
- origImageUrls?: "sheetUrls" | "sliceUrls";
- }>();
- const emit = defineEmits(["error", "getIsMultComments", "getScrollStatus"]);
- const { dragContainer } = useDraggable();
- const viewScroll = () => {
- if (
- dragContainer.value.scrollTop + dragContainer.value.offsetHeight + 50 >=
- dragContainer.value.scrollHeight
- ) {
- emit("getScrollStatus");
- }
- };
- const { addTimeout } = useTimers();
- let sliceImagesWithTrackList: SliceImage[] = reactive([]);
- let maxImageWidth = 0;
- function addTrackColorAttr(tList: Track[]): Track[] {
- let markerIds: (number | undefined)[] = tList
- .map((v) => v.markerId)
- .filter((x) => !!x);
- markerIds = Array.from(new Set(markerIds));
- // markerIds.sort();
- let colorMap: ColorMap = {};
- for (let i = 0; i < markerIds.length; i++) {
- const mId: any = markerIds[i];
- if (i == 0) {
- colorMap[mId + ""] = "red";
- } else if (i == 1) {
- colorMap[mId + ""] = "blue";
- } else if (i > 1) {
- colorMap[mId + ""] = "gray";
- }
- }
- if (Object.keys(colorMap).length > 1) {
- emit("getIsMultComments", true);
- }
- tList = tList.map((item: Track) => {
- item.color = colorMap[item.markerId + ""] || "gray";
- item.isByMultMark = markerIds.length > 1;
- return item;
- });
- return tList;
- }
- function addTagColorAttr(tList: SpecialTag[]): SpecialTag[] {
- let markerIds: (number | undefined)[] = tList
- .map((v) => v.markerId)
- .filter((x) => !!x);
- markerIds = Array.from(new Set(markerIds));
- // markerIds.sort();
- let colorMap: ColorMap = {};
- for (let i = 0; i < markerIds.length; i++) {
- const mId: any = markerIds[i];
- if (i == 0) {
- colorMap[mId + ""] = "red";
- } else if (i == 1) {
- colorMap[mId + ""] = "blue";
- } else if (i > 1) {
- colorMap[mId + ""] = "gray";
- }
- }
- tList = tList.map((item: SpecialTag) => {
- item.color = colorMap[item.markerId + ""] || "gray";
- item.isByMultMark = markerIds.length > 1;
- return item;
- });
- return tList;
- }
- async function processImage() {
- if (!store.currentTask) return;
- const images = [];
- const urls = store.currentTask[origImageUrls] || [];
- for (const url of urls) {
- const image = await loadImage(url);
- images.push(image);
- }
- maxImageWidth = Math.max(...images.map((i) => i.naturalWidth));
- for (const url of urls) {
- const indexInSliceUrls = urls.indexOf(url) + 1;
- const image = images[indexInSliceUrls - 1];
- const trackLists = (store.currentTask.questionList || [])
- // .map((q) => q.trackList)
- .map((q) => {
- let tList = q.trackList;
- return q.headerTrack?.length
- ? addHeaderTrackColorAttr(q.headerTrack)
- : addTrackColorAttr(tList);
- })
- .flat();
- const thisImageTrackList = trackLists.filter(
- (t) => t.offsetIndex === indexInSliceUrls
- );
- const thisImageTagList = store.currentTask.headerTagList?.length
- ? addHeaderTrackColorAttr(
- (store.currentTask.headerTagList || []).filter(
- (t) => t.offsetIndex === indexInSliceUrls
- )
- )
- : addTagColorAttr(
- (store.currentTask.specialTagList || []).filter(
- (t) => t.offsetIndex === indexInSliceUrls
- )
- );
- // const thisImageTagList = addTagColorAttr(
- // (store.currentTask.specialTagList || []).filter(
- // (t) => t.offsetIndex === indexInSliceUrls
- // )
- // );
- sliceImagesWithTrackList.push({
- url,
- trackList: thisImageTrackList,
- tagList: thisImageTagList,
- originalImageWidth: image.naturalWidth,
- originalImageHeight: image.naturalHeight,
- width: (image.naturalWidth / maxImageWidth) * 100 + "%",
- });
- }
- }
- // should not render twice at the same time
- let renderLock = false;
- const renderPaperAndMark = async () => {
- if (renderLock) {
- console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
- await new Promise((res) => setTimeout(res, 1000));
- await renderPaperAndMark();
- return;
- }
- renderLock = true;
- sliceImagesWithTrackList.splice(0);
- if (!store.currentTask) {
- renderLock = false;
- return;
- }
- try {
- store.globalMask = true;
- await processImage();
- } catch (error) {
- sliceImagesWithTrackList.splice(0);
- console.log("render error ", error);
- // 图片加载出错,自动加载下一个任务
- emit("error");
- } finally {
- await new Promise((res) => setTimeout(res, 500));
- store.globalMask = false;
- renderLock = false;
- }
- };
- watch(() => store.currentTask, renderPaperAndMark);
- watch(
- (): (number | undefined)[] => [
- store.minimapScrollToX,
- store.minimapScrollToY,
- ],
- () => {
- const container = document.querySelector<HTMLDivElement>(
- ".mark-body-container"
- );
- addTimeout(() => {
- if (
- container &&
- typeof store.minimapScrollToX === "number" &&
- typeof store.minimapScrollToY === "number"
- ) {
- const { scrollWidth, scrollHeight } = container;
- container.scrollTo({
- top: scrollHeight * store.minimapScrollToY,
- left: scrollWidth * store.minimapScrollToX,
- behavior: "smooth",
- });
- }
- }, 10);
- }
- );
- const answerPaperScale = $computed(() => {
- // 放大、缩小不影响页面之前的滚动条定位
- let percentWidth = 0;
- let percentTop = 0;
- const container = document.querySelector(".mark-body-container");
- 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 + "%";
- });
- </script>
- <style scoped>
- .mark-body-container .none-tip {
- height: 100%;
- display: flex;
- justify-content: center;
- align-items: center;
- font-size: 28px;
- }
- .mark-body-container {
- height: calc(100vh - 56px);
- overflow: auto;
- background-color: var(--app-container-bg-color);
- background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
- linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
- linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
- linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
- background-size: 20px 20px;
- background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
- transform: inherit;
- cursor: grab;
- user-select: none;
- }
- .mark-body-container img {
- width: 100%;
- }
- .single-image-container {
- position: relative;
- }
- .image-seperator {
- border: 2px solid rgba(120, 120, 120, 0.1);
- }
- </style>
|