MarkBody.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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 lang="ts">
  31. import { computed, defineComponent, reactive, ref, watchEffect } from "vue";
  32. import { store } from "./store";
  33. import MarkDrawTrack from "./MarkDrawTrack.vue";
  34. import { 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. // should not render twice at the same time
  46. let __lock = false;
  47. let __currentStudentId = -1; // save __currentStudentIdof lock
  48. export default defineComponent({
  49. name: "MarkBody",
  50. components: { MarkDrawTrack },
  51. emits: ["error"],
  52. setup(props, { emit }) {
  53. const { dragContainer } = dragImage();
  54. const { addTimeout } = useTimers();
  55. let rendering = ref(false);
  56. let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
  57. async function processImage() {
  58. if (!store.currentTask) return;
  59. const images = [];
  60. for (const url of store.currentTask.sliceUrls) {
  61. const image = await loadImage(url);
  62. images.push(image);
  63. }
  64. for (const url of store.currentTask.sliceUrls) {
  65. const completeUrl = url;
  66. const indexInSliceUrls = store.currentTask.sliceUrls.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: completeUrl,
  82. indexInSliceUrls,
  83. trackList: thisImageTrackList,
  84. tagList: thisImageTagList,
  85. originalImage: image,
  86. });
  87. }
  88. }
  89. const renderPaperAndMark = async () => {
  90. if (__lock) {
  91. if (store.currentTask?.studentId === __currentStudentId) {
  92. console.log("重复渲染,返回");
  93. return;
  94. }
  95. console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
  96. await new Promise((res) => setTimeout(res, 1000));
  97. await renderPaperAndMark();
  98. return;
  99. }
  100. __lock = true;
  101. __currentStudentId = store.currentTask?.studentId ?? -1;
  102. sliceImagesWithTrackList.splice(0);
  103. if (!store.currentTask) {
  104. __lock = false;
  105. return;
  106. }
  107. try {
  108. rendering.value = true;
  109. await processImage();
  110. } catch (error) {
  111. sliceImagesWithTrackList.splice(0);
  112. console.log("render error ", error);
  113. // 图片加载出错,自动加载下一个任务
  114. emit("error");
  115. } finally {
  116. __lock = false;
  117. rendering.value = false;
  118. }
  119. };
  120. watchEffect(renderPaperAndMark);
  121. const answerPaperScale = computed(() => {
  122. // 放大、缩小不影响页面之前的滚动条定位
  123. let percentWidth = 0;
  124. let percentTop = 0;
  125. const container = document.querySelector(
  126. ".mark-body-container"
  127. ) as HTMLDivElement;
  128. if (container) {
  129. const { scrollLeft, scrollTop, scrollWidth, scrollHeight } = container;
  130. percentWidth = scrollLeft / scrollWidth;
  131. percentTop = scrollTop / scrollHeight;
  132. }
  133. addTimeout(() => {
  134. if (container) {
  135. const { scrollWidth, scrollHeight } = container;
  136. container.scrollTo({
  137. left: scrollWidth * percentWidth,
  138. top: scrollHeight * percentTop,
  139. });
  140. }
  141. }, 10);
  142. const scale = store.setting.uiSetting["answer.paper.scale"];
  143. return scale * 100 + "%";
  144. });
  145. return {
  146. dragContainer,
  147. store,
  148. rendering,
  149. sliceImagesWithTrackList,
  150. answerPaperScale,
  151. };
  152. },
  153. });
  154. </script>
  155. <style scoped>
  156. .mark-body-container {
  157. height: calc(100vh - 41px);
  158. overflow: auto;
  159. background-size: 8px 8px;
  160. background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
  161. linear-gradient(to bottom, transparent 4px, #e7e7e7 4px);
  162. cursor: grab;
  163. user-select: none;
  164. }
  165. .grabbing {
  166. cursor: grabbing;
  167. }
  168. .mark-body-container img {
  169. width: 100%;
  170. }
  171. .single-image-container {
  172. position: relative;
  173. }
  174. .image-seperator {
  175. border: 2px solid rgba(120, 120, 120, 0.1);
  176. }
  177. </style>