RecogEditDialog.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. <template>
  2. <a-modal
  3. v-model:open="visible"
  4. width="100%"
  5. :footer="false"
  6. :closable="false"
  7. :mask="false"
  8. :maskClosable="false"
  9. :keyboard="false"
  10. wrapClassName="recog-edit-dialog"
  11. :afterClose="afterClose"
  12. >
  13. <div class="recog-edit">
  14. <div class="recog-row">
  15. <div class="recog-col is-static is-col1">
  16. <div class="modal-box">
  17. <p class="box-title">{{ recogTitle }}</p>
  18. <p class="box-cont">{{ recogTitleDesc }}</p>
  19. </div>
  20. </div>
  21. <div class="recog-col is-grow">
  22. <div class="modal-box modal-origin">
  23. <div class="modal-origin-body" :style="areaImgStyle">
  24. <img
  25. v-if="recogData.areaImg"
  26. ref="areaImgRef"
  27. :src="recogData.areaImg"
  28. alt="截图"
  29. @load="areaImgLoad"
  30. />
  31. <div
  32. v-for="(option, index) in recogData.options.slice(1)"
  33. :key="option"
  34. class="select-option"
  35. :style="getOptionStyle(index)"
  36. @click="selectOption(option)"
  37. >
  38. {{ option }}
  39. </div>
  40. </div>
  41. </div>
  42. </div>
  43. <div class="recog-col is-static is-col1">
  44. <div class="modal-box is-btn" @click="close">
  45. <p class="box-title">Esc键</p>
  46. <p class="box-cont">关闭</p>
  47. </div>
  48. </div>
  49. </div>
  50. <div class="recog-row">
  51. <div class="recog-col is-static is-col1">
  52. <div class="modal-box">
  53. <p class="box-title">识别结果</p>
  54. <p class="box-cont">{{ recogResult }}</p>
  55. </div>
  56. </div>
  57. <div class="recog-col is-grow">
  58. <div class="modal-box modal-options">
  59. <a-button
  60. v-for="option in recogData.options"
  61. :key="option"
  62. :type="selectResult.includes(option) ? 'primary' : 'default'"
  63. @click="selectOption(option)"
  64. >{{ recogResultTransform(option) }}</a-button
  65. >
  66. </div>
  67. </div>
  68. <div class="recog-col is-static is-col1">
  69. <div class="modal-box is-btn" @click="onConfirm">
  70. <p class="box-title">Enter键</p>
  71. <p class="box-cont">保存</p>
  72. </div>
  73. </div>
  74. </div>
  75. </div>
  76. </a-modal>
  77. </template>
  78. <script setup lang="ts">
  79. import { ref, computed, watch, nextTick } from "vue";
  80. import { message } from "ant-design-vue";
  81. import useModal from "@/hooks/useModal";
  82. import { RecogBlock } from "@/utils/recog/recog";
  83. import { getBoxImageSize, recogResultTransform } from "@/utils/tool";
  84. import { useUserStore, useDataCheckStore } from "@/store";
  85. import { debounce } from "lodash-es";
  86. defineOptions({
  87. name: "RecogEditDialog",
  88. });
  89. /* modal */
  90. const { visible, open, close } = useModal();
  91. defineExpose({ open, close });
  92. const props = defineProps<{
  93. recogData: RecogBlock;
  94. }>();
  95. const emit = defineEmits(["confirm", "close"]);
  96. const userStore = useUserStore();
  97. const dataCheckStore = useDataCheckStore();
  98. const selectResult = ref([] as string[]);
  99. const titles = {
  100. question: "客观题",
  101. absent: "缺考",
  102. breach: "违纪",
  103. paperType: "卷型号",
  104. };
  105. const recogTitle = computed(() => {
  106. return titles[props.recogData.type];
  107. });
  108. const recogTitleDesc = computed(() => {
  109. if (props.recogData.type === "question") {
  110. return `#${props.recogData.index + 25}`;
  111. }
  112. return "--";
  113. });
  114. const recogResult = computed(() => {
  115. if (props.recogData.type === "question") {
  116. return props.recogData.result.join("");
  117. }
  118. return "";
  119. });
  120. function getOptionStyle(index: number): Record<string, any> {
  121. const optionSize = props.recogData.optionSizes[index];
  122. const option = props.recogData.options[index + 1];
  123. const borderColor = selectResult.value.includes(option)
  124. ? userStore.recogFillSet.fillColor
  125. : userStore.recogFillSet.unfillColor;
  126. return {
  127. width: `${optionSize.w}px`,
  128. height: `${optionSize.h}px`,
  129. fontSize: `${optionSize.h * 0.8}px`,
  130. fontWeight: "bold",
  131. color: borderColor,
  132. left: `${optionSize.x}px`,
  133. top: `${optionSize.y}px`,
  134. borderColor,
  135. };
  136. }
  137. const areaImgRef = ref();
  138. const areaImgStyle = ref({});
  139. function areaImgLoad() {
  140. const areaImgDom = areaImgRef.value as HTMLImageElement;
  141. const boxDom = areaImgDom.parentNode?.parentNode as HTMLDivElement;
  142. const recogDpi = dataCheckStore.curPage.recogDpi || 150;
  143. const scaleRate = (1.5 * 150) / recogDpi;
  144. const imgSize = getBoxImageSize({
  145. box: {
  146. width: boxDom.offsetWidth - 22,
  147. height: boxDom.offsetHeight - 22,
  148. },
  149. img: {
  150. width: areaImgDom.naturalWidth * scaleRate,
  151. height: areaImgDom.naturalHeight * scaleRate,
  152. },
  153. rotate: 0,
  154. });
  155. const rate = imgSize.width / areaImgDom.naturalWidth;
  156. areaImgStyle.value = {
  157. width: `${areaImgDom.naturalWidth}px`,
  158. height: `${areaImgDom.naturalHeight}px`,
  159. transform: `scale(${rate}) translate(-50%, -50%)`,
  160. };
  161. }
  162. function selectOption(option: string) {
  163. if (!props.recogData) return;
  164. // 单选直接赋值
  165. if (!props.recogData.multiple) {
  166. selectResult.value = [option];
  167. return;
  168. }
  169. // 多选情况
  170. // 空直接赋值,空值与其他互斥
  171. if (option === "#") {
  172. selectResult.value = ["#"];
  173. return;
  174. }
  175. let result = selectResult.value.filter((item) => item !== "#");
  176. if (result.includes(option)) {
  177. result = result.filter((item) => item !== option);
  178. } else {
  179. result.push(option);
  180. }
  181. // 保证result的顺序和options的顺序是一致的
  182. selectResult.value = props.recogData.options.filter((item) =>
  183. result.includes(item)
  184. );
  185. }
  186. // save
  187. const onConfirm = debounce(save, 300);
  188. function save() {
  189. // console.log(`save:${Date.now()}`);
  190. if (!selectResult.value.length) {
  191. message.error("请选择答案");
  192. return;
  193. }
  194. emit("confirm", selectResult.value);
  195. close();
  196. }
  197. // 键盘事件
  198. function registKeyEvent() {
  199. document.addEventListener("keydown", keyEventHandle);
  200. }
  201. function removeKeyEvent() {
  202. document.removeEventListener("keydown", keyEventHandle);
  203. }
  204. function keyEventHandle(e: KeyboardEvent) {
  205. if (e.repeat) return;
  206. if (e.keyCode == 13) {
  207. e.preventDefault();
  208. onConfirm();
  209. return;
  210. } else if (e.code === "Escape") {
  211. e.preventDefault();
  212. close();
  213. return;
  214. }
  215. }
  216. function afterClose() {
  217. emit("close");
  218. }
  219. // init
  220. watch(
  221. () => visible.value,
  222. (val) => {
  223. if (val) {
  224. modalOpenHandle();
  225. } else {
  226. removeKeyEvent();
  227. }
  228. },
  229. {
  230. immediate: true,
  231. }
  232. );
  233. watch(
  234. () => props.recogData,
  235. (val) => {
  236. console.log(`props.recogData:${Date.now()}`, val);
  237. if (!val) return;
  238. selectResult.value = [...props.recogData.result];
  239. }
  240. );
  241. async function modalOpenHandle() {
  242. selectResult.value = [...props.recogData.result];
  243. await nextTick();
  244. registKeyEvent();
  245. }
  246. </script>