MarkBody.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <template>
  2. <div ref="dragContainer" class="mark-body-container tw-flex-auto tw-p-2 tw-pt-0">
  3. <div v-if="!store.currentTask" class="tw-text-center">
  4. {{ store.message }}
  5. </div>
  6. <div v-else :style="{ width: answerPaperScale }" class="tw-pt-2">
  7. <div
  8. v-for="(item, index) in sliceImagesWithTrackList"
  9. :key="index"
  10. class="single-image-container"
  11. :style="{
  12. width: item.width,
  13. }"
  14. >
  15. <img :src="item.url" draggable="false" />
  16. <MarkDrawTrack
  17. :trackList="item.trackList"
  18. :specialTagList="item.tagList"
  19. :sliceImageHeight="item.originalImageHeight"
  20. :sliceImageWidth="item.originalImageWidth"
  21. :dx="0"
  22. :dy="0"
  23. />
  24. <hr class="image-seperator" />
  25. </div>
  26. </div>
  27. <ZoomPaper v-if="store.isScanImage && sliceImagesWithTrackList.length" />
  28. </div>
  29. </template>
  30. <script setup lang="ts">
  31. import { reactive, watch } from "vue";
  32. import { store } from "@/store/store";
  33. import MarkDrawTrack from "@/features/mark/MarkDrawTrack.vue";
  34. import type { SpecialTag, Track } from "@/types";
  35. import { useTimers } from "@/setups/useTimers";
  36. import { loadImage } from "@/utils/utils";
  37. import { dragImage } from "@/features/mark/use/draggable";
  38. import ZoomPaper from "@/components/ZoomPaper.vue";
  39. interface SliceImage {
  40. url: string;
  41. trackList: Array<Track>;
  42. tagList: Array<SpecialTag>;
  43. originalImageWidth: number;
  44. originalImageHeight: number;
  45. width: string; // 图片在整个图片列表里面的宽度比例
  46. }
  47. const { origImageUrls = "sliceUrls" } = defineProps<{
  48. origImageUrls?: "sheetUrls" | "sliceUrls";
  49. }>();
  50. const emit = defineEmits(["error"]);
  51. const { dragContainer } = dragImage();
  52. const { addTimeout } = useTimers();
  53. let sliceImagesWithTrackList: SliceImage[] = reactive([]);
  54. let maxImageWidth = 0;
  55. async function processImage() {
  56. if (!store.currentTask) return;
  57. const images = [];
  58. const urls = store.currentTask[origImageUrls] || [];
  59. for (const url of urls) {
  60. const image = await loadImage(url);
  61. images.push(image);
  62. }
  63. maxImageWidth = Math.max(...images.map((i) => i.naturalWidth));
  64. for (const url of urls) {
  65. const indexInSliceUrls = urls.indexOf(url) + 1;
  66. const image = images[indexInSliceUrls - 1];
  67. const trackLists = (store.currentTask.questionList || [])
  68. .map((q) => q.trackList)
  69. .flat();
  70. const thisImageTrackList = trackLists.filter(
  71. (t) => t.offsetIndex === indexInSliceUrls
  72. );
  73. const thisImageTagList = (store.currentTask.specialTagList || []).filter(
  74. (t) => t.offsetIndex === indexInSliceUrls
  75. );
  76. sliceImagesWithTrackList.push({
  77. url,
  78. trackList: thisImageTrackList,
  79. tagList: thisImageTagList,
  80. originalImageWidth: image.naturalWidth,
  81. originalImageHeight: image.naturalHeight,
  82. width: (image.naturalWidth / maxImageWidth) * 100 + "%",
  83. });
  84. }
  85. }
  86. // should not render twice at the same time
  87. let renderLock = false;
  88. const renderPaperAndMark = async () => {
  89. if (renderLock) {
  90. console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
  91. await new Promise((res) => setTimeout(res, 1000));
  92. await renderPaperAndMark();
  93. return;
  94. }
  95. renderLock = true;
  96. sliceImagesWithTrackList.splice(0);
  97. if (!store.currentTask) {
  98. renderLock = false;
  99. return;
  100. }
  101. try {
  102. store.globalMask = true;
  103. await processImage();
  104. } catch (error) {
  105. sliceImagesWithTrackList.splice(0);
  106. console.log("render error ", error);
  107. // 图片加载出错,自动加载下一个任务
  108. emit("error");
  109. } finally {
  110. await new Promise((res) => setTimeout(res, 500));
  111. store.globalMask = false;
  112. renderLock = false;
  113. }
  114. };
  115. watch(() => store.currentTask, renderPaperAndMark);
  116. const answerPaperScale = $computed(() => {
  117. // 放大、缩小不影响页面之前的滚动条定位
  118. let percentWidth = 0;
  119. let percentTop = 0;
  120. const container = document.querySelector(
  121. ".mark-body-container"
  122. ) as HTMLDivElement;
  123. if (container) {
  124. const { scrollLeft, scrollTop, scrollWidth, scrollHeight } = container;
  125. percentWidth = scrollLeft / scrollWidth;
  126. percentTop = scrollTop / scrollHeight;
  127. }
  128. addTimeout(() => {
  129. if (container) {
  130. const { scrollWidth, scrollHeight } = container;
  131. container.scrollTo({
  132. left: scrollWidth * percentWidth,
  133. top: scrollHeight * percentTop,
  134. });
  135. }
  136. }, 10);
  137. const scale = store.setting.uiSetting["answer.paper.scale"];
  138. return scale * 100 + "%";
  139. });
  140. </script>
  141. <style scoped>
  142. .mark-body-container {
  143. height: calc(100vh - 56px);
  144. overflow: auto;
  145. background-color: var(--app-container-bg-color);
  146. background-image: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
  147. linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
  148. linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
  149. linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
  150. background-size: 20px 20px;
  151. background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
  152. transform: inherit;
  153. cursor: grab;
  154. user-select: none;
  155. }
  156. .mark-body-container img {
  157. width: 100%;
  158. }
  159. .single-image-container {
  160. position: relative;
  161. }
  162. .image-seperator {
  163. border: 2px solid rgba(120, 120, 120, 0.1);
  164. }
  165. </style>