ImageView.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. <template>
  2. <div class="image-view h-full">
  3. <div class="tree-wrap">
  4. <div class="top">
  5. <div class="result-list">
  6. {{ curSubject.label }}{{ curDevice.label && ";" }}{{ curDevice.label
  7. }}{{ curBatch.label && ";" }}{{ curBatch.label }}
  8. </div>
  9. <div class="bread">
  10. <span
  11. :class="{ active: chooseLevel >= 1 }"
  12. @click="toggleListType('level1', 1)"
  13. >科目</span
  14. >
  15. <i>&gt;</i>
  16. <span
  17. :class="{ active: chooseLevel >= 2 }"
  18. @click="toggleListType('level2', 2)"
  19. >机器</span
  20. >
  21. <i>&gt;</i>
  22. <span
  23. :class="{ active: chooseLevel >= 3 }"
  24. @click="toggleListType('level3', 3)"
  25. >批次</span
  26. >
  27. <i>&gt;</i>
  28. <span :class="{ active: chooseLevel >= 4 }">考生</span>
  29. </div>
  30. </div>
  31. <div class="bottom">
  32. <div
  33. v-for="(item, index) in leftList"
  34. :key="index"
  35. class="list-row"
  36. :class="{ active: activeIndex === index }"
  37. @click="chooseLeft(item, index)"
  38. >
  39. {{ item[fieldNames.label] }}
  40. </div>
  41. </div>
  42. </div>
  43. <div class="info-wrap">
  44. <Accordion :arr="arr">
  45. <template #one>
  46. <div class="flex accordion-body items-center justify-between">
  47. <VerticalDateRange v-model="timeArr" />
  48. <qm-button size="large" type="primary" @click="search"
  49. >搜索</qm-button
  50. >
  51. </div>
  52. </template>
  53. <template #two>
  54. <div class="accordion-body">
  55. <a-radio-group
  56. v-model:value="imageType"
  57. @change="onImageTypeChange"
  58. >
  59. <a-radio
  60. v-for="item in imageTypeOptions"
  61. :key="item.value"
  62. :value="item.value"
  63. >{{ item.label }}</a-radio
  64. >
  65. </a-radio-group>
  66. </div>
  67. </template>
  68. <template #three>
  69. <div class="accordion-body">
  70. <QuestionPanel
  71. v-if="dataCheckStore.curStudent && dataCheckStore.curPage"
  72. v-model:questions="questions"
  73. :info="questionInfo"
  74. :simple="isSliceImage"
  75. :editable="false"
  76. @change="onQuestionsChange"
  77. />
  78. </div>
  79. </template>
  80. </Accordion>
  81. </div>
  82. <div class="image-wrap">
  83. <ScanImage
  84. v-if="dataCheckStore.curPage && isOriginImage"
  85. :cant-change-img="true"
  86. @prev="onPrevPage"
  87. @next="onNextPage"
  88. />
  89. <SliceImage
  90. :cant-change-img="true"
  91. v-if="dataCheckStore.curPage && !isOriginImage"
  92. />
  93. </div>
  94. </div>
  95. </template>
  96. <script name="ImageView" lang="ts" setup>
  97. import { ref, computed, onMounted, watch, onBeforeUnmount } from "vue";
  98. import VerticalDateRange from "@/components/VerticalDateRange/index.vue";
  99. import { useUserStore, useDataCheckStore } from "@/store";
  100. import { IMAGE_TYPE, enum2Options } from "@/constants/enums";
  101. import QuestionPanel from "../DataCheck/QuestionPanel.vue";
  102. import ScanImage from "../DataCheck/ScanImage/index.vue";
  103. import SliceImage from "../DataCheck/SliceImage/index.vue";
  104. import {
  105. batchSubjectList,
  106. batchDeviceList,
  107. batchList,
  108. batchStudentList,
  109. getStuCardDetail,
  110. } from "@/ap/scanManage";
  111. import { QuestionInfo } from "../DataCheck/types";
  112. import dayjs from "dayjs";
  113. const imageTypeOptions = enum2Options(IMAGE_TYPE);
  114. const dataCheckStore = useDataCheckStore();
  115. dataCheckStore.resetInfo();
  116. function onImageTypeChange() {
  117. dataCheckStore.setInfo({
  118. imageType: imageType.value,
  119. });
  120. }
  121. const timeArr = ref([Date.now() - 1000 * 60 * 60, Date.now()]);
  122. const timeParams = computed<any>(() => {
  123. return { startTime: timeArr.value[0], endTime: timeArr.value[1] };
  124. });
  125. const userStore = useUserStore();
  126. const examId = computed(() => userStore.curExam?.id);
  127. const _batchSubjectList = () => {
  128. batchSubjectList({ examId: examId.value, ...timeParams.value }).then(
  129. (res: any) => {
  130. leftList.value = res || [];
  131. listType.value = "level1";
  132. }
  133. );
  134. };
  135. const _batchDeviceList = () => {
  136. batchDeviceList({
  137. examId: examId.value,
  138. subjectCode: curSubject.value.value,
  139. ...timeParams.value,
  140. }).then((res: any) => {
  141. leftList.value = res || [];
  142. listType.value = "level2";
  143. });
  144. };
  145. const _batchList = () => {
  146. batchList({
  147. examId: examId.value,
  148. subjectCode: curSubject.value.value,
  149. device: curDevice.value.value,
  150. ...timeParams.value,
  151. }).then((res: any) => {
  152. leftList.value = res || [];
  153. listType.value = "level3";
  154. });
  155. };
  156. const _batchStudentList = () => {
  157. batchStudentList({ batchId: curBatch.value.value }).then((res: any) => {
  158. leftList.value = res || [];
  159. listType.value = "level4";
  160. if (res?.length) {
  161. chooseLeft(res[0], 0);
  162. }
  163. });
  164. };
  165. function parseStudentPage(student: any) {
  166. dataList.value = [];
  167. student.papers.forEach((paper: any, paperIndex: number) => {
  168. if (!paper.pages) return;
  169. paper.pages.forEach((page: any, pageIndex: number) => {
  170. let row = {
  171. ...page,
  172. paperId: paper.id as number,
  173. pageIndex: page.index,
  174. paperIndex,
  175. paperNumber: paper.number,
  176. studentIndex: 0,
  177. studentId: student.id,
  178. examId: userStore.curExam?.id,
  179. kid: `${student.id}-${0}-${paperIndex}-${pageIndex}`,
  180. };
  181. if (row.question?.result?.length && Array.isArray(row.question?.result)) {
  182. row.question.result = row.question.result.map((item: string) =>
  183. item.replace(/[^#a-zA-Z]/g, "")
  184. );
  185. }
  186. dataList.value.push(row);
  187. });
  188. });
  189. }
  190. const loading = ref(false);
  191. const _getStuCardDetail = () => {
  192. loading.value = true;
  193. getStuCardDetail({
  194. batchId: curBatch.value.value,
  195. studentId: curStu.value.studentId,
  196. })
  197. .then((res: any) => {
  198. curStuCardData.value = res || {};
  199. parseStudentPage(curStuCardData.value);
  200. selectPage(0);
  201. })
  202. .finally(() => {
  203. loading.value = false;
  204. });
  205. };
  206. function selectPage(index: number) {
  207. dataCheckStore.setInfo({
  208. curPage: dataList.value[index],
  209. curPageIndex: index,
  210. });
  211. if (!dataCheckStore.curPage) return;
  212. dataCheckStore.setInfo({ curStudent: curStuCardData.value as any });
  213. }
  214. const arr = ref([
  215. { title: "搜索条件", name: "one", open: true },
  216. { title: "图片类别", name: "two", open: true },
  217. { title: "题卡信息", name: "three", open: true },
  218. ]);
  219. const listType = ref("level1");
  220. const fieldNames = computed(() => {
  221. let obj: any = {
  222. level1: { label: "subjectName", value: "subjectCode" },
  223. level2: { label: "deviceName", value: "device" },
  224. level3: { label: "batchId", value: "batchId" },
  225. level4: { label: "examNumber", value: "studentId" },
  226. };
  227. return obj[listType.value];
  228. });
  229. const chooseLevel = computed(() => {
  230. return listType.value === "level1"
  231. ? 1
  232. : listType.value === "level2"
  233. ? 2
  234. : listType.value === "level3"
  235. ? 3
  236. : 4;
  237. });
  238. const leftList = ref<any>([]);
  239. const curSubject = ref({ label: "", value: "" });
  240. const curDevice = ref({ label: "", value: "" });
  241. const curBatch = ref({ label: "", value: "" });
  242. const curStu = ref({
  243. examNumber: "",
  244. studentId: void 0,
  245. studentName: "",
  246. });
  247. const curStuCardData = ref({});
  248. const dataList = ref<any>([]);
  249. const chooseLeft = (item: any, index: number) => {
  250. if (loading.value) return;
  251. if (listType.value === "level1") {
  252. curSubject.value = { value: item.subjectCode, label: item.subjectName };
  253. _batchDeviceList();
  254. } else if (listType.value === "level2") {
  255. curDevice.value = { value: item.device, label: item.deviceName };
  256. _batchList();
  257. } else if (listType.value === "level3") {
  258. curBatch.value = { value: item.batchId, label: item.batchId };
  259. _batchStudentList();
  260. } else if (listType.value === "level4") {
  261. activeIndex.value = index;
  262. curStu.value = {
  263. examNumber: item.examNumber,
  264. studentId: item.studentId,
  265. studentName: item.studentName,
  266. };
  267. _getStuCardDetail();
  268. }
  269. };
  270. const toggleListType = (type: string, num: number) => {
  271. if (chooseLevel.value < num) {
  272. return;
  273. }
  274. const clearObj = { value: "", label: "" };
  275. leftList.value = [];
  276. if (type === "level1") {
  277. curSubject.value = { ...clearObj };
  278. curDevice.value = { ...clearObj };
  279. curBatch.value = { ...clearObj };
  280. _batchSubjectList();
  281. } else if (type === "level2") {
  282. curDevice.value = { ...clearObj };
  283. curBatch.value = { ...clearObj };
  284. _batchDeviceList();
  285. } else if (type === "level3") {
  286. curBatch.value = { ...clearObj };
  287. _batchList();
  288. }
  289. };
  290. const search = () => {
  291. listType.value = "level1";
  292. curStu.value = {
  293. examNumber: "",
  294. studentId: void 0,
  295. studentName: "",
  296. };
  297. dataCheckStore.resetInfo();
  298. toggleListType(listType.value, 0);
  299. };
  300. const activeIndex = ref();
  301. const imageType = ref(dataCheckStore.imageType);
  302. // imageType
  303. const isOriginImage = computed(() => {
  304. return dataCheckStore.imageType === "ORIGIN";
  305. });
  306. // imageType
  307. const isSliceImage = computed(() => {
  308. return dataCheckStore.imageType === "SLICE";
  309. });
  310. // question panel
  311. const questionInfo = computed(() => {
  312. if (!dataCheckStore.curStudent) return {} as QuestionInfo;
  313. return {
  314. examNumber: dataCheckStore.curStudent.examNumber,
  315. name: dataCheckStore.curStudent.name,
  316. examSite: dataCheckStore.curStudent.examSite,
  317. seatNumber: dataCheckStore.curStudent.seatNumber,
  318. paperType: dataCheckStore.curStudent.paperType,
  319. examStatus: dataCheckStore.curStudent.examStatus as string,
  320. packageCode: dataCheckStore.curStudent.packageCode as string,
  321. };
  322. });
  323. const questions = ref<any>([]);
  324. watch(
  325. () => dataCheckStore.curPageIndex,
  326. (val, oldval) => {
  327. if (val !== oldval) {
  328. if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) return;
  329. questions.value = [...(dataCheckStore.curPage?.question?.result || [])];
  330. }
  331. }
  332. );
  333. watch(
  334. () => dataCheckStore.imageType,
  335. (val) => {
  336. imageType.value = val;
  337. }
  338. );
  339. async function onQuestionsChange() {
  340. if (!dataCheckStore.curPage) return;
  341. // dataCheckStore.curPage.question = [...questions.value];
  342. dataCheckStore.curPage.question.result = [...questions.value];
  343. await dataCheckStore
  344. .updateField({
  345. field: "QUESTION",
  346. value: JSON.stringify({
  347. type: dataCheckStore.curPage?.question?.type,
  348. result: questions.value,
  349. }),
  350. })
  351. .catch(() => {});
  352. }
  353. async function onPrevPage() {
  354. if (dataCheckStore.curPageIndex <= 0) {
  355. if (activeIndex.value == 0) {
  356. window.$message.error("没有上一张了");
  357. return;
  358. } else {
  359. chooseLeft(leftList.value[activeIndex.value - 1], activeIndex.value - 1);
  360. return;
  361. }
  362. }
  363. selectPage(dataCheckStore.curPageIndex - 1);
  364. }
  365. async function onNextPage() {
  366. if (dataCheckStore.curPageIndex >= dataList.value.length - 1) {
  367. if (activeIndex.value == leftList.value.length - 1) {
  368. window.$message.error("没有下一张了");
  369. return;
  370. } else {
  371. chooseLeft(leftList.value[activeIndex.value + 1], activeIndex.value + 1);
  372. return;
  373. }
  374. }
  375. selectPage(dataCheckStore.curPageIndex + 1);
  376. }
  377. function registShortcut() {
  378. document.addEventListener("keydown", shortcutHandle);
  379. }
  380. function removeShortcut() {
  381. document.removeEventListener("keydown", shortcutHandle);
  382. }
  383. function onPrevStudent() {
  384. if (activeIndex.value == 0) {
  385. window.$message.error("没有上一个学生了");
  386. return;
  387. }
  388. chooseLeft(leftList.value[activeIndex.value - 1], activeIndex.value - 1);
  389. }
  390. function onNextStudent() {
  391. if (activeIndex.value == leftList.value.length - 1) {
  392. window.$message.error("没有下一个学生了");
  393. return;
  394. }
  395. chooseLeft(leftList.value[activeIndex.value + 1], activeIndex.value + 1);
  396. }
  397. function shortcutHandle(e: KeyboardEvent) {
  398. if (!leftList.value.length || listType.value !== "level4") {
  399. return;
  400. }
  401. const moveAction = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
  402. if (!moveAction.includes(e.code) || e.repeat) {
  403. return;
  404. }
  405. e.preventDefault();
  406. if (e.code === "ArrowUp") {
  407. onPrevStudent();
  408. return;
  409. }
  410. if (e.code === "ArrowDown") {
  411. onNextStudent();
  412. return;
  413. }
  414. if (e.code === "ArrowLeft") {
  415. onPrevPage();
  416. return;
  417. }
  418. if (e.code === "ArrowRight") {
  419. onNextPage();
  420. return;
  421. }
  422. }
  423. onMounted(() => {
  424. _batchSubjectList();
  425. registShortcut();
  426. });
  427. onBeforeUnmount(() => {
  428. removeShortcut();
  429. });
  430. </script>
  431. <style lang="less" scoped>
  432. .image-view {
  433. .accordion-body {
  434. padding: 12px 16px;
  435. }
  436. .image-wrap {
  437. height: 100%;
  438. border-left: 1px solid #e5e6eb;
  439. border-right: 1px solid #e5e6eb;
  440. background: #eceef1;
  441. margin-left: 284px;
  442. margin-right: 400px;
  443. }
  444. .tree-wrap {
  445. width: 284px;
  446. float: left;
  447. height: 100%;
  448. display: flex;
  449. flex-direction: column;
  450. .bottom {
  451. flex: 1;
  452. overflow: auto;
  453. padding: 10px 8px;
  454. .list-row {
  455. padding: 6px 12px;
  456. line-height: 22px;
  457. color: @text-color1;
  458. cursor: pointer;
  459. &.active {
  460. font-weight: bold;
  461. background: #e8f3ff !important;
  462. border-radius: 6px;
  463. }
  464. &:hover {
  465. background: #f6f6f6;
  466. border-radius: 6px;
  467. }
  468. }
  469. }
  470. .top {
  471. border-bottom: 1px solid #e5e5e5;
  472. padding: 16px;
  473. .bread {
  474. margin-top: 16px;
  475. i {
  476. font-style: normal;
  477. color: @text-color3;
  478. margin: 0 4px;
  479. }
  480. span {
  481. color: @text-color3;
  482. &:not(:last-child) {
  483. cursor: pointer;
  484. }
  485. &.active {
  486. color: @text-color1;
  487. font-weight: bold;
  488. }
  489. }
  490. }
  491. .result-list {
  492. background: #f2f3f5;
  493. border-radius: 6px;
  494. padding: 0 8px;
  495. height: 32px;
  496. line-height: 32px;
  497. color: @text-color1;
  498. }
  499. }
  500. }
  501. .info-wrap {
  502. width: 400px;
  503. float: right;
  504. height: 100%;
  505. }
  506. }
  507. </style>