ReviewAction.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <template>
  2. <div class="review-action">
  3. <div class="review-tabs">
  4. <div
  5. :class="[
  6. 'review-tab',
  7. { 'is-active': reviewStore.tabKey === 'review' },
  8. ]"
  9. @click="switchTab('review')"
  10. >
  11. 复核校验
  12. </div>
  13. <div
  14. :class="[
  15. 'review-tab',
  16. { 'is-active': reviewStore.tabKey === 'history' },
  17. ]"
  18. @click="switchTab('history')"
  19. >
  20. 历史记录
  21. </div>
  22. </div>
  23. <div v-show="reviewStore.tabKey === 'review'" class="review-tbody">
  24. <a-collapse v-model:activeKey="reviewKey" :bordered="false">
  25. <a-collapse-panel key="1">
  26. <template #header><FilterFilled />搜索条件 </template>
  27. <span>科目:</span>
  28. <a-select
  29. v-model:value="searchCourseCode"
  30. placeholder="请选择科目"
  31. :options="courses"
  32. :field-names="fieldNames"
  33. filter-option
  34. style="width: 140px"
  35. ></a-select>
  36. <a-button class="m-l-8px" type="primary" @click="onSearch"
  37. >搜索</a-button
  38. >
  39. </a-collapse-panel>
  40. <a-collapse-panel key="2">
  41. <template #header><WarningFilled />导出异常 </template>
  42. <span>科目:</span>
  43. <a-select
  44. v-model:value="exportCourseCode"
  45. placeholder="请选择科目"
  46. :options="courses"
  47. :field-names="fieldNames"
  48. filter-option
  49. style="width: 140px"
  50. ></a-select>
  51. <a-button class="m-l-8px" :disabled="downloading" @click="onExport">
  52. 导出
  53. </a-button>
  54. </a-collapse-panel>
  55. <a-collapse-panel key="3">
  56. <template #header><PushpinFilled />复核标记 </template>
  57. <a-radio-group v-model:value="result" @change="onMark">
  58. <a-radio :value="1">正常</a-radio>
  59. <a-radio :value="0">异常</a-radio>
  60. </a-radio-group>
  61. </a-collapse-panel>
  62. <a-collapse-panel key="4">
  63. <template #header><RightSquareFilled />重置 </template>
  64. <span>科目:</span>
  65. <a-select
  66. v-model:value="resetCourseCode"
  67. placeholder="请选择科目"
  68. :options="courses"
  69. :field-names="fieldNames"
  70. filter-option
  71. style="width: 140px"
  72. ></a-select>
  73. <a-button class="m-l-8px" type="primary" danger @click="onReset"
  74. >重置</a-button
  75. >
  76. </a-collapse-panel>
  77. </a-collapse>
  78. </div>
  79. <div
  80. v-show="reviewStore.tabKey === 'history'"
  81. class="review-tbody tbody-history"
  82. >
  83. <a-collapse :activeKey="['1']" :bordered="false">
  84. <a-collapse-panel key="1">
  85. <template #header><PushpinFilled />复核标记 </template>
  86. <a-radio-group v-model:value="historyResult" @change="onMark">
  87. <a-radio :value="1">正常</a-radio>
  88. <a-radio :value="0">异常</a-radio>
  89. </a-radio-group>
  90. </a-collapse-panel>
  91. </a-collapse>
  92. <div class="history-list">
  93. <div class="task-list">
  94. <ul class="list-head">
  95. <li class="li-grow">准考证号</li>
  96. <li style="width: 80px">状态</li>
  97. </ul>
  98. <div class="list-body">
  99. <ul
  100. v-for="(item, index) in dataList"
  101. :key="item.examNumber"
  102. :class="[
  103. 'list-row',
  104. { 'is-active': reviewStore.curTask?.id === item.id },
  105. ]"
  106. @click="setCurTask(index)"
  107. >
  108. <li class="li-grow">{{ item.examNumber }}</li>
  109. <li style="width: 80px">
  110. <span
  111. :class="
  112. item.assignedSuspect ? 'color-success' : 'color-error'
  113. "
  114. >
  115. {{ item.assignedSuspect ? "正常" : "异常" }}
  116. </span>
  117. </li>
  118. </ul>
  119. </div>
  120. </div>
  121. </div>
  122. <div class="history-footer">
  123. <SimplePagination
  124. :total="pagination.total"
  125. :page-size="pagination.pageSize"
  126. @change="toPage"
  127. />
  128. </div>
  129. </div>
  130. </div>
  131. <!-- ExportTypeDialog -->
  132. <ExportTypeDialog ref="exportTypeDialogRef" @confirm="onExportConfirm" />
  133. </template>
  134. <script setup lang="ts">
  135. import { ref } from "vue";
  136. import { getSubjectList } from "@/ap/base";
  137. import {
  138. FilterFilled,
  139. WarningFilled,
  140. RightSquareFilled,
  141. PushpinFilled,
  142. } from "@ant-design/icons-vue";
  143. import { message } from "ant-design-vue";
  144. import { showConfirm } from "@/utils/uiUtils";
  145. import { reviewWarningTaskExport, reviewTaskHistory } from "@/ap/review";
  146. import { ReviewTaskListItem } from "@/ap/types/review";
  147. import { SubjectItem } from "@/ap/types/base";
  148. import useTable from "@/hooks/useTable";
  149. import useLoading from "@/hooks/useLoading";
  150. import { useUserStore, useReviewStore } from "@/store";
  151. import SimplePagination from "@/components/SimplePagination/index.vue";
  152. import ExportTypeDialog from "./ExportTypeDialog.vue";
  153. defineOptions({
  154. name: "ReviewAction",
  155. });
  156. const emit = defineEmits(["search", "reset", "mark"]);
  157. const userStore = useUserStore();
  158. const reviewStore = useReviewStore();
  159. const fieldNames = { label: "name", value: "code" };
  160. // tab
  161. const reviewKey = ref(["1", "2", "3", "4"]);
  162. async function switchTab(key: "review" | "history") {
  163. reviewStore.setInfo({ tabKey: key });
  164. if (key === "history") {
  165. await toPage(1);
  166. setCurTask(0);
  167. }
  168. }
  169. // course data
  170. const courses = ref<SubjectItem[]>([]);
  171. async function getCourses() {
  172. const res = await getSubjectList({ examId: userStore.curExam.id });
  173. courses.value = res || [];
  174. }
  175. const searchCourseCode = ref("");
  176. const exportCourseCode = ref("");
  177. const resetCourseCode = ref("");
  178. const result = ref(1);
  179. const historyResult = ref(1);
  180. // history
  181. const curHistoryTaskIndex = ref(0);
  182. const { dataList, pagination, loading, getList, toPage, setPageSize } =
  183. useTable<ReviewTaskListItem>(
  184. reviewTaskHistory,
  185. { examId: userStore.curExam.id },
  186. false
  187. );
  188. setPageSize(30);
  189. function setCurTask(index: number) {
  190. curHistoryTaskIndex.value = index;
  191. reviewStore.setInfo({ curTask: dataList.value[index] });
  192. }
  193. // actions
  194. function onSearch() {
  195. emit("search", searchCourseCode.value);
  196. }
  197. function onReset() {
  198. let subjectData: SubjectItem | null = null;
  199. if (resetCourseCode.value) {
  200. subjectData = courses.value.find(
  201. (item) => item.subjectCode === resetCourseCode.value
  202. );
  203. subjectData = subjectData || null;
  204. }
  205. emit("reset", subjectData);
  206. }
  207. function onMark() {
  208. emit(
  209. "mark",
  210. reviewStore.tabKey === "review" ? result.value : historyResult.value
  211. );
  212. }
  213. // 导出
  214. const { loading: downloading, setLoading } = useLoading();
  215. const exportTypeDialogRef = ref();
  216. function onExport() {
  217. if (downloading.value) return;
  218. exportTypeDialogRef.value?.open();
  219. }
  220. async function onExportConfirm(type: "student" | "room") {
  221. if (downloading.value) return;
  222. setLoading(true);
  223. const res = await reviewWarningTaskExport({
  224. examId: userStore.curExam.id,
  225. subjectCode: exportCourseCode.value,
  226. type,
  227. }).catch((e: Error) => {
  228. message.error(e.message || "下载失败,请重新尝试!");
  229. });
  230. setLoading(false);
  231. if (!res) return;
  232. message.success("导出成功!");
  233. }
  234. </script>