MarkBody.vue 5.1 KB

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