MarkBody.vue 4.8 KB

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