MarkBody.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  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 filters from "@/filters";
  34. import MarkDrawTrack from "./MarkDrawTrack.vue";
  35. import { SpecialTag, Track } from "@/types";
  36. import { useTimers } from "@/setups/useTimers";
  37. import { loadImage } from "@/utils/utils";
  38. import { dragImage } from "@/features/mark/use/draggable";
  39. interface SliceImage {
  40. url: string;
  41. indexInSliceUrls: number;
  42. trackList: Array<Track>;
  43. tagList: Array<SpecialTag>;
  44. originalImage: HTMLImageElement;
  45. }
  46. // should not render twice at the same time
  47. let __lock = false;
  48. let __currentStudentId = -1; // save __currentStudentIdof lock
  49. export default defineComponent({
  50. name: "MarkBody",
  51. components: { MarkDrawTrack },
  52. emits: ["error"],
  53. setup(props, { emit }) {
  54. const { dragContainer } = dragImage();
  55. const { addTimeout } = useTimers();
  56. let rendering = ref(false);
  57. let sliceImagesWithTrackList: Array<SliceImage> = reactive([]);
  58. async function processImage() {
  59. if (!store.currentTask) return;
  60. const images = [];
  61. for (const url of store.currentTask.sliceUrls) {
  62. const image = await loadImage(
  63. filters.toCompleteUrlWithFileServer(store.setting.fileServer, url)
  64. );
  65. images.push(image);
  66. }
  67. for (const url of store.currentTask.sliceUrls) {
  68. const completeUrl = filters.toCompleteUrlWithFileServer(
  69. store.setting.fileServer,
  70. url
  71. );
  72. const indexInSliceUrls = store.currentTask.sliceUrls.indexOf(url) + 1;
  73. const image = images[indexInSliceUrls - 1];
  74. const trackLists = store.currentTask.questionList
  75. .map((q) => q.trackList)
  76. .reduce((acc, t) => {
  77. acc = acc.concat(t);
  78. return acc;
  79. }, [] as Array<Track>);
  80. const thisImageTrackList = trackLists.filter(
  81. (t) => t.offsetIndex === indexInSliceUrls
  82. );
  83. const thisImageTagList = store.currentTask.specialTagList?.filter(
  84. (t) => t.offsetIndex === indexInSliceUrls
  85. );
  86. sliceImagesWithTrackList.push({
  87. url: completeUrl,
  88. indexInSliceUrls,
  89. trackList: thisImageTrackList,
  90. tagList: thisImageTagList,
  91. originalImage: image,
  92. });
  93. }
  94. }
  95. const renderPaperAndMark = async () => {
  96. if (__lock) {
  97. if (store.currentTask?.studentId === __currentStudentId) {
  98. console.log("重复渲染,返回");
  99. return;
  100. }
  101. console.log("上个任务还未渲染完毕,稍等一秒再尝试渲染");
  102. await new Promise((res) => setTimeout(res, 1000));
  103. await renderPaperAndMark();
  104. return;
  105. }
  106. __lock = true;
  107. __currentStudentId = store.currentTask?.studentId ?? -1;
  108. sliceImagesWithTrackList.splice(0);
  109. if (!store.currentTask) {
  110. __lock = 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. __lock = false;
  123. rendering.value = false;
  124. }
  125. };
  126. watchEffect(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. return {
  152. dragContainer,
  153. store,
  154. rendering,
  155. sliceImagesWithTrackList,
  156. answerPaperScale,
  157. };
  158. },
  159. });
  160. </script>
  161. <style scoped>
  162. .mark-body-container {
  163. height: calc(100vh - 41px);
  164. overflow: scroll;
  165. background-size: 8px 8px;
  166. background-image: linear-gradient(to right, #e7e7e7 4px, transparent 4px),
  167. linear-gradient(to bottom, transparent 4px, #e7e7e7 4px);
  168. cursor: grab;
  169. user-select: none;
  170. }
  171. .grabbing {
  172. cursor: grabbing;
  173. }
  174. .mark-body-container img {
  175. width: 100%;
  176. }
  177. .single-image-container {
  178. position: relative;
  179. }
  180. .image-seperator {
  181. border: 2px solid rgba(120, 120, 120, 0.1);
  182. }
  183. </style>