MarkBody.vue 5.3 KB

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