CheckAction.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419
  1. <template>
  2. <div class="check-action">
  3. <a-collapse
  4. v-model:activeKey="panelKey"
  5. :bordered="false"
  6. expandIconPosition="end"
  7. >
  8. <a-collapse-panel key="1">
  9. <template #header><FilterFilled />搜索条件 </template>
  10. <a-form :label-col="{ style: { width: '83px' } }">
  11. <a-form-item label="科目">
  12. <a-select
  13. v-model:value="searchModel.subjectCode"
  14. placeholder="请选择"
  15. :options="courses"
  16. :field-names="fieldNames"
  17. filter-option
  18. allow-clear
  19. style="width: 150px"
  20. ></a-select>
  21. </a-form-item>
  22. <a-form-item label="查找条件">
  23. <a-select
  24. v-model:value="searchDataCheckType"
  25. placeholder="请选择"
  26. :options="dataCheckOptions"
  27. allow-clear
  28. @change="dataCheckTypeChange"
  29. ></a-select>
  30. </a-form-item>
  31. </a-form>
  32. <div class="box-justify">
  33. <div></div>
  34. <div>
  35. <a-button class="m-r-8px" type="primary" @click="onSearch"
  36. >查询</a-button
  37. >
  38. <a-button @click="onExport('common')">导出</a-button>
  39. </div>
  40. </div>
  41. </a-collapse-panel>
  42. <a-collapse-panel key="2">
  43. <template #header><FilterFilled />搜索条件 (自定义)</template>
  44. <a-form :label-col="{ style: { width: '83px' } }">
  45. <a-form-item label="考号">
  46. <div class="exam-number">
  47. <a-textarea
  48. v-model:value="customSearchModel.examNumber"
  49. placeholder="请输入"
  50. :auto-size="{ minRows: 1, maxRows: 1 }"
  51. ></a-textarea>
  52. <div class="number-suffix">{{ examNumberCountCont }}</div>
  53. </div>
  54. <a-button
  55. class="ant-simple m-l-8px"
  56. type="link"
  57. @click="onEditExamNumber"
  58. >查看全部</a-button
  59. >
  60. </a-form-item>
  61. <a-form-item label="姓名">
  62. <a-input
  63. v-model:value="customSearchModel.name"
  64. placeholder="请输入"
  65. style="width: 150px"
  66. ></a-input>
  67. </a-form-item>
  68. <a-form-item label="科目">
  69. <a-select
  70. v-model:value="customSearchModel.subjectCode"
  71. placeholder="请选择"
  72. :options="courses"
  73. :field-names="fieldNames"
  74. filter-option
  75. allow-clear
  76. style="width: 150px"
  77. ></a-select>
  78. </a-form-item>
  79. <a-row>
  80. <a-col :span="12">
  81. <a-form-item label="客观题作答">
  82. <a-select
  83. v-model:value="customSearchModel.questionFilled"
  84. placeholder="请选择"
  85. :options="booleanOptions"
  86. style="width: 85px"
  87. allow-clear
  88. ></a-select>
  89. </a-form-item>
  90. </a-col>
  91. <a-col :span="12">
  92. <a-form-item label="主观题作答">
  93. <a-select
  94. v-model:value="customSearchModel.subjectiveFilled"
  95. placeholder="请选择"
  96. :options="booleanOptions"
  97. style="width: 100%"
  98. allow-clear
  99. ></a-select>
  100. </a-form-item>
  101. </a-col>
  102. <a-col :span="12">
  103. <a-form-item label="有作答">
  104. <a-select
  105. v-model:value="customSearchModel.hasFilled"
  106. placeholder="请选择"
  107. :options="booleanOptions"
  108. style="width: 85px"
  109. allow-clear
  110. ></a-select>
  111. </a-form-item>
  112. </a-col>
  113. <a-col :span="12">
  114. <a-form-item label="试卷类型">
  115. <a-select
  116. v-model:value="customSearchModel.paperTypeStatus"
  117. placeholder="请选择"
  118. :options="paperTypeOptions"
  119. style="width: 100%"
  120. allow-clear
  121. ></a-select>
  122. </a-form-item>
  123. </a-col>
  124. </a-row>
  125. <a-row>
  126. <a-col :span="16">
  127. <a-form-item label="缺考">
  128. <a-select
  129. v-model:value="customExamStatus"
  130. placeholder="请选择"
  131. :options="examStatusOptions"
  132. style="width: 85px"
  133. allow-clear
  134. @change="customExamStatusChange"
  135. ></a-select>
  136. </a-form-item>
  137. </a-col>
  138. <a-col :span="8">
  139. <a-form-item>
  140. <a-button class="m-r-8px" type="primary" @click="onCustomSearch"
  141. >查询</a-button
  142. >
  143. <a-button @click="onExport('custom')">导出</a-button>
  144. </a-form-item>
  145. </a-col>
  146. </a-row>
  147. </a-form>
  148. </a-collapse-panel>
  149. <a-collapse-panel key="3">
  150. <template #header><PictureFilled />图片类别 </template>
  151. <a-radio-group v-model:value="imageType" @change="onImageTypeChange">
  152. <a-radio
  153. v-for="item in imageTypeOptions"
  154. :key="item.value"
  155. :value="item.value"
  156. >{{ item.label }}</a-radio
  157. >
  158. </a-radio-group>
  159. </a-collapse-panel>
  160. <a-collapse-panel key="4">
  161. <template #header><IdcardFilled />题卡信息 </template>
  162. <QuestionPanel
  163. v-model:questions="questions"
  164. :info="questionInfo"
  165. :simple="isSliceImage"
  166. @change="onQuestionsChange"
  167. @exam-status-change="onExamStatusChange"
  168. />
  169. </a-collapse-panel>
  170. </a-collapse>
  171. </div>
  172. <!-- ExportTypeDialog -->
  173. <ExportTypeDialog ref="exportTypeDialogRef" @confirm="onExportConfirm" />
  174. <!-- MoreExamNumberDialog -->
  175. <MoreExamNumberDialog
  176. ref="moreExamNumberDialogRef"
  177. :data="customSearchModel.examNumber"
  178. @confirm="onExamNumberEdited"
  179. />
  180. </template>
  181. <script setup lang="ts">
  182. import { computed, reactive, ref, watch } from "vue";
  183. import { getSubjectList } from "@/ap/base";
  184. import {
  185. FilterFilled,
  186. PictureFilled,
  187. IdcardFilled,
  188. } from "@ant-design/icons-vue";
  189. import { message } from "ant-design-vue";
  190. import { useUserStore, useDataCheckStore } from "@/store";
  191. import { DataCheckListFilter, ExamStatus } from "@/ap/types/dataCheck";
  192. import { SubjectItem } from "@/ap/types/base";
  193. import { ReviewExportType } from "@/ap/types/review";
  194. import { dataCheckStudentExport, dataCheckRoomExport } from "@/ap/dataCheck";
  195. import { examStatusSave } from "@/ap/absentCheck";
  196. import useDictOption from "@/hooks/dictOption";
  197. import useLoading from "@/hooks/useLoading";
  198. import { ImageType, booleanOptionList } from "@/constants/enumerate";
  199. import { objModifyAssign } from "@/utils/tool";
  200. import ExportTypeDialog from "../Review/ExportTypeDialog.vue";
  201. import QuestionPanel from "./QuestionPanel.vue";
  202. import MoreExamNumberDialog from "./MoreExamNumberDialog.vue";
  203. defineOptions({
  204. name: "CheckAction",
  205. });
  206. const emit = defineEmits(["search"]);
  207. const userStore = useUserStore();
  208. const dataCheckStore = useDataCheckStore();
  209. const { optionList: dataCheckOptions } = useDictOption("DATA_CHECK_TYPE");
  210. const { optionList: paperTypeOptions } = useDictOption("PAPER_TYPE_STATUS");
  211. const { optionList: imageTypeOptions } = useDictOption("IMAGE_TYPE");
  212. const { optionList: examStatusOptions } = useDictOption("EXAM_STATUS");
  213. const booleanOptions = ref(booleanOptionList);
  214. const panelKey = ref(["1", "2", "3", "4"]);
  215. // course data
  216. const courses = ref<SubjectItem[]>([]);
  217. async function getCourses() {
  218. const res = await getSubjectList({ examId: userStore.curExam.id });
  219. courses.value = res || [];
  220. }
  221. getCourses();
  222. const fieldNames = { label: "name", value: "code" };
  223. // search
  224. const initSearchModel = {
  225. examId: userStore.curExam.id,
  226. status: "",
  227. examStatus: [],
  228. examNumber: "",
  229. studentCode: "",
  230. name: "",
  231. packageCode: "",
  232. campusCode: "",
  233. subjectCode: "",
  234. examSite: "",
  235. examRoom: "",
  236. province: "",
  237. paperTypeStatus: "",
  238. device: "",
  239. absentSuspect: null,
  240. omrAbsent: null,
  241. assigned: null,
  242. incomplete: null,
  243. questionFilled: null,
  244. subjectiveFilled: null,
  245. hasFilled: null,
  246. withOmrDetail: null,
  247. };
  248. const searchModel = reactive<DataCheckListFilter>({ ...initSearchModel });
  249. const customSearchModel = reactive<DataCheckListFilter>({ ...initSearchModel });
  250. const searchDataCheckType = ref();
  251. const customExamStatus = ref<ExamStatus>();
  252. const imageType = ref(dataCheckStore.imageType);
  253. const actionType = ref("common");
  254. // imageType
  255. const isSliceImage = computed(() => {
  256. return dataCheckStore.imageType === "SLICE";
  257. });
  258. const examNumberCountCont = computed(() => {
  259. const examNumbers = (customSearchModel.examNumber || "")
  260. .split("\n")
  261. .filter((item) => item);
  262. return `${examNumbers.length}/100`;
  263. });
  264. const moreExamNumberDialogRef = ref();
  265. function onEditExamNumber() {
  266. moreExamNumberDialogRef.value?.open();
  267. }
  268. function onExamNumberEdited(val: string) {
  269. customSearchModel.examNumber = val;
  270. }
  271. function dataCheckTypeChange() {
  272. switch (searchDataCheckType.value) {
  273. // 缺考有作答
  274. case "1":
  275. searchModel.examStatus = ["ABSENT"];
  276. searchModel.hasFilled = true;
  277. break;
  278. // 客观题无作答,主观题有作答;
  279. case "2":
  280. searchModel.questionFilled = false;
  281. searchModel.subjectiveFilled = true;
  282. break;
  283. // 不缺考,无条码,有作答;(正常或待审核考生,条码为空,有作答)
  284. case "3":
  285. searchModel.examStatus = ["OK", "UNCHECK"];
  286. searchModel.paperTypeStatus = "BLANK";
  287. searchModel.hasFilled = true;
  288. break;
  289. // 不缺考,无条码,无作答;(正常或待审核考生,条码为空,没有作答)
  290. case "4":
  291. searchModel.examStatus = ["OK", "UNCHECK"];
  292. searchModel.paperTypeStatus = "BLANK";
  293. searchModel.hasFilled = false;
  294. break;
  295. // 条码异常(识别的条码没与库中匹配上)
  296. case "5":
  297. searchModel.paperTypeStatus = "ERROR";
  298. break;
  299. // 缺考有条码
  300. case "6":
  301. searchModel.paperTypeStatus = "OK";
  302. searchModel.examStatus = ["ABSENT"];
  303. break;
  304. default:
  305. objModifyAssign(searchModel, {
  306. ...initSearchModel,
  307. subjectCode: searchModel.subjectCode,
  308. });
  309. break;
  310. }
  311. }
  312. function customExamStatusChange() {
  313. customSearchModel.examStatus = customExamStatus.value
  314. ? [customExamStatus.value]
  315. : [];
  316. }
  317. function onSearch() {
  318. emit("search", searchModel);
  319. }
  320. function onCustomSearch() {
  321. emit("search", customSearchModel);
  322. }
  323. function onImageTypeChange() {
  324. dataCheckStore.setInfo({
  325. imageType: imageType.value,
  326. });
  327. }
  328. // question panel
  329. const questionInfo = computed(() => {
  330. return {
  331. examNumber: dataCheckStore.curStudent?.examNumber,
  332. name: dataCheckStore.curStudent?.name,
  333. examSite: dataCheckStore.curStudent?.examSite,
  334. seatNumber: dataCheckStore.curStudent?.seatNumber,
  335. paperType: dataCheckStore.curStudent?.paperType,
  336. };
  337. });
  338. const questions = ref([]);
  339. watch(
  340. () => dataCheckStore.curPageIndex,
  341. (val, oldval) => {
  342. if (val !== oldval) {
  343. if (!dataCheckStore.curPage || !dataCheckStore.curPage.question) {
  344. questions.value = [];
  345. return;
  346. }
  347. questions.value = [...dataCheckStore.curPage.question.result];
  348. }
  349. }
  350. );
  351. async function onQuestionsChange() {
  352. if (!dataCheckStore.curPage) return;
  353. dataCheckStore.curPage.question.result = [...questions.value];
  354. await dataCheckStore
  355. .updateField({
  356. field: "QUESTION",
  357. value: JSON.stringify(dataCheckStore.curPage.question),
  358. })
  359. .catch(() => {});
  360. }
  361. async function onExamStatusChange(val: ExamStatus) {
  362. if (!dataCheckStore.curStudent) return;
  363. await examStatusSave({ id: dataCheckStore.curStudent.id, examStatus: val });
  364. dataCheckStore.curStudent.examStatus = val;
  365. }
  366. // 导出
  367. const { loading: downloading, setLoading } = useLoading();
  368. const exportTypeDialogRef = ref();
  369. function onExport(type: string) {
  370. if (downloading.value) return;
  371. actionType.value = type;
  372. exportTypeDialogRef.value?.open();
  373. }
  374. async function onExportConfirm(type: ReviewExportType) {
  375. if (downloading.value) return;
  376. setLoading(true);
  377. const func =
  378. type === "STUDENT_CODE" ? dataCheckStudentExport : dataCheckRoomExport;
  379. const params =
  380. actionType.value === "common" ? searchModel : customSearchModel;
  381. const res = await func(params).catch((e: Error) => {
  382. message.error(e.message || "下载失败,请重新尝试!");
  383. });
  384. setLoading(false);
  385. if (!res) return;
  386. message.success("导出成功!");
  387. }
  388. </script>