ImageView.vue 14 KB

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