index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <div class="exam-manage">
  3. <Block class="header-block tw-flex tw-items-center tw-justify-between">
  4. <a-form layout="inline">
  5. <a-form-item label="学校名称">
  6. <!-- <a-select
  7. v-model:value="query.schoolId"
  8. show-search
  9. :filterOption="false"
  10. @search="(name:string) => querySchoolList(name, 'list')"
  11. placeholder="学校名称"
  12. > -->
  13. <a-select
  14. v-model:value="query.schoolId"
  15. show-search
  16. placeholder="学校名称"
  17. :options="schoolTableData.result"
  18. :fieldNames="{ label: 'name', value: 'id' }"
  19. optionFilterProp="name"
  20. >
  21. <!-- <a-select-option
  22. v-for="school in schoolTableData.result"
  23. :key="school.id"
  24. :value="school.id"
  25. >{{ school.name }}</a-select-option
  26. > -->
  27. </a-select>
  28. </a-form-item>
  29. <a-form-item label="考试批次">
  30. <a-input
  31. v-model:value="query.name"
  32. placeholder="考试批次"
  33. :maxlength="50"
  34. ></a-input>
  35. </a-form-item>
  36. <a-form-item>
  37. <a-button
  38. class="search-button"
  39. type="primary"
  40. @click="
  41. query.pageNumber == 1 ? queryExamList() : (query.pageNumber = 1)
  42. "
  43. >查询</a-button
  44. >
  45. </a-form-item>
  46. </a-form>
  47. <!-- <a-button
  48. type="primary"
  49. class="tw-flex tw-items-center operation-button"
  50. @click="syncExamData"
  51. >
  52. <template #icon>
  53. <CloudSyncOutlined />
  54. </template>
  55. 同步
  56. </a-button> -->
  57. <a-button
  58. type="primary"
  59. class="tw-flex tw-items-center operation-button"
  60. @click="toggleAddExamModal"
  61. >
  62. <template #icon>
  63. <PlusCircleOutlined />
  64. </template>
  65. 新增
  66. </a-button>
  67. </Block>
  68. <Block class="exam-table">
  69. <a-table
  70. :columns="columns"
  71. :data-source="examTableData.result"
  72. emptyText="暂无考试信息"
  73. :loading="tableLoading"
  74. :pagination="pagination"
  75. @change="currentPageChange"
  76. :row-class-name="
  77. (_:any, index:number) => (index % 2 === 1 ? 'table-striped' : null)
  78. "
  79. >
  80. <template #bodyCell="{ column, record, index }">
  81. <template v-if="column.dataIndex === 'index'">
  82. {{ index + 1 }}
  83. </template>
  84. <template v-else-if="column.dataIndex === 'examStatus'">
  85. {{ EXAM_STATUS[record.examStatus as keyof typeof EXAM_STATUS] }}
  86. </template>
  87. <template v-else-if="column.dataIndex === 'operation'">
  88. <div class="tw-flex tw-items-center">
  89. <span
  90. class="tw-cursor-pointer tw-p-2 tw-ml-1"
  91. @click="onEdit(record)"
  92. >编辑</span
  93. >
  94. </div>
  95. </template>
  96. </template>
  97. </a-table>
  98. </Block>
  99. <a-modal
  100. v-model:visible="showModal"
  101. :title="`${examInfo.id ? '编辑' : '新增'}考试`"
  102. okText="确定"
  103. cancelText="取消"
  104. :maskClosable="false"
  105. @ok="onAddNewExam"
  106. :afterClose="resetFields"
  107. >
  108. <a-form :labelCol="{ span: 6 }">
  109. <a-form-item label="学校名称" v-bind="validateInfos.schoolId">
  110. <!-- <a-select
  111. v-model:value="examInfo.schoolId"
  112. show-search
  113. :disabled="!!examInfo.id"
  114. @search="(name: string) => querySchoolList(name,'form')"
  115. :filterOption="false"
  116. placeholder="学校名称"
  117. > -->
  118. <a-select
  119. v-model:value="examInfo.schoolId"
  120. show-search
  121. placeholder="学校名称"
  122. :options="examInfo.schoolTableData.result"
  123. :fieldNames="{ label: 'name', value: 'id' }"
  124. optionFilterProp="name"
  125. >
  126. <!-- <a-select-option
  127. v-for="school in examInfo.schoolTableData.result"
  128. :key="school.id"
  129. :value="school.id"
  130. >{{ school.name }}</a-select-option
  131. > -->
  132. </a-select>
  133. </a-form-item>
  134. <a-form-item label="考试批次" v-bind="validateInfos.name">
  135. <a-input
  136. v-model:value="examInfo.name"
  137. :maxlength="50"
  138. placeholder="请输入考试批次"
  139. ></a-input>
  140. </a-form-item>
  141. <a-form-item label="状态" v-bind="validateInfos.examStatus">
  142. <a-select
  143. v-model:value="examInfo.examStatus"
  144. placeholder="选择考试状态"
  145. >
  146. <a-select-option value="EDIT">{{
  147. EXAM_STATUS.EDIT
  148. }}</a-select-option>
  149. <a-select-option value="FINISH">{{
  150. EXAM_STATUS.FINISH
  151. }}</a-select-option>
  152. <a-select-option value="CLOSE">{{
  153. EXAM_STATUS.CLOSE
  154. }}</a-select-option>
  155. </a-select>
  156. </a-form-item>
  157. </a-form>
  158. </a-modal>
  159. </div>
  160. </template>
  161. <script setup lang="ts" name="PageExam">
  162. import { reactive, ref, watch, unref, markRaw, toRaw, computed } from "vue";
  163. import { PlusCircleOutlined } from "@ant-design/icons-vue";
  164. import { getSchoolListHttp } from "@/apis/school";
  165. import {
  166. getExamListHttp,
  167. editExamInfoHttp,
  168. syncExamDataHttp,
  169. } from "@/apis/exam";
  170. import Block from "@/components/block/index.vue";
  171. import { message, TableColumnType } from "ant-design-vue";
  172. import { Form } from "ant-design-vue";
  173. import { throttle } from "lodash-es";
  174. import { EXAM_STATUS } from "@/constants/dicts";
  175. import { useMainStore } from "@/store/main";
  176. const mainStore = useMainStore();
  177. const showModal = ref(false);
  178. const tableLoading = ref(false);
  179. /** 请求参数 */
  180. const query = reactive<FetchExamListQuery>({
  181. name: "",
  182. // schoolId: mainStore.systemUserInfo?.schoolId || "",
  183. schoolId: "",
  184. pageNumber: 1,
  185. pageSize: 10,
  186. });
  187. /** table配置 */
  188. const columns: TableColumnType[] = [
  189. { title: "序号", dataIndex: "index", align: "center", width: 60 },
  190. { title: "考试ID", dataIndex: "id", width: 100, ellipsis: true },
  191. { title: "考试批次", dataIndex: "name", ellipsis: true },
  192. {
  193. title: "状态",
  194. dataIndex: "examStatus",
  195. align: "center",
  196. width: 160,
  197. ellipsis: true,
  198. },
  199. {
  200. title: "科目数量",
  201. dataIndex: "paperCount",
  202. align: "center",
  203. width: 100,
  204. ellipsis: true,
  205. },
  206. { title: "操作", dataIndex: "operation", align: "center", width: 100 },
  207. ];
  208. /** 学校列表信息 */
  209. const schoolTableData = reactive<MultiplePageData<SchoolListInfo>>({
  210. totalCount: 0,
  211. result: [],
  212. });
  213. /** 考试列表信息 */
  214. const examTableData = reactive<MultiplePageData<ExamListInfo>>({
  215. totalCount: 0,
  216. result: [],
  217. });
  218. const examInfo = reactive<
  219. BaseExamInfo & { schoolTableData: MultiplePageData<SchoolListInfo> }
  220. >({
  221. schoolTableData: { totalCount: 0, result: [] },
  222. examStatus: void 0,
  223. name: "",
  224. schoolId: void 0,
  225. id: void 0,
  226. });
  227. const examRules = {
  228. name: [{ required: true, message: "请填写考试批次" }],
  229. examStatus: [{ required: true, message: "请选择考试状态" }],
  230. schoolId: [{ required: true, message: "请选择学校" }],
  231. };
  232. const { validate, validateInfos, resetFields } = Form.useForm(
  233. examInfo,
  234. examRules
  235. );
  236. /** 查询学校列表 */
  237. const querySchoolList = throttle(
  238. async (name: string = "", type: "list" | "form" = "list") => {
  239. const isList = type === "list";
  240. try {
  241. const { result = [], totalCount } = await getSchoolListHttp({
  242. pageNumber: 1,
  243. pageSize: 1000,
  244. name,
  245. });
  246. Object.assign(isList ? schoolTableData : examInfo.schoolTableData, {
  247. result,
  248. totalCount,
  249. });
  250. } catch (error) {
  251. console.error(error);
  252. }
  253. },
  254. 100
  255. );
  256. /** 显示新增考试弹窗 */
  257. const toggleAddExamModal = (show: boolean = true) => {
  258. showModal.value = show;
  259. if (show) {
  260. if (!examInfo.id) {
  261. Object.assign(examInfo, {
  262. schoolId: query.schoolId || mainStore.systemUserInfo?.schoolId,
  263. });
  264. }
  265. querySchoolList("", "form");
  266. }
  267. };
  268. /** 查询考试列表 */
  269. const queryExamList = async () => {
  270. try {
  271. tableLoading.value = true;
  272. const { result = [], totalCount } = await getExamListHttp(query);
  273. Object.assign(examTableData, { result, totalCount });
  274. } catch (error) {
  275. console.error(error);
  276. }
  277. tableLoading.value = false;
  278. };
  279. watch(() => query.pageNumber, queryExamList);
  280. watch(
  281. () => query.pageSize,
  282. () => {
  283. query.pageNumber = 1;
  284. queryExamList();
  285. }
  286. );
  287. /** 编辑考试 */
  288. const onEdit = (record: ExamListInfo) => {
  289. Object.assign(examInfo, {
  290. id: record.id,
  291. examStatus: record.examStatus,
  292. name: record.name,
  293. schoolId: record.schoolId,
  294. });
  295. toggleAddExamModal(true);
  296. };
  297. /** 新增考试 */
  298. const onAddNewExam = () => {
  299. validate().then((valid) => {
  300. if (valid) {
  301. const { schoolTableData, ...info } = examInfo;
  302. editExamInfoHttp(info).then(() => {
  303. message.success(`${info.id ? "修改" : "添加"}成功`);
  304. toggleAddExamModal(false);
  305. query.schoolId && queryExamList();
  306. });
  307. }
  308. });
  309. };
  310. const currentPageChange = ({
  311. current,
  312. pageSize,
  313. }: {
  314. current: number;
  315. pageSize: number;
  316. }) => {
  317. console.log(current, pageSize);
  318. query.pageNumber = current;
  319. query.pageSize = pageSize;
  320. };
  321. /** 同步考试数据 */
  322. const syncExamData = () => {
  323. if (!query.schoolId) {
  324. return message.error("请选择需要同步数据的学校");
  325. }
  326. syncExamDataHttp({ schoolId: query.schoolId }).then(queryExamList);
  327. };
  328. querySchoolList();
  329. /** effect */
  330. if (query.schoolId) {
  331. queryExamList();
  332. }
  333. const pagination = computed(() => {
  334. return {
  335. showSizeChanger: true,
  336. total: examTableData.totalCount,
  337. pageSize: query.pageSize,
  338. current: query.pageNumber,
  339. showTotal: (total: any) => `共 ${total} 条`,
  340. };
  341. });
  342. </script>
  343. <style scoped lang="less">
  344. .exam-manage {
  345. .header-block {
  346. :deep(.ant-select-selector) {
  347. width: 160px;
  348. }
  349. .search-button {
  350. background-color: @font-color;
  351. color: @white;
  352. border: none;
  353. width: 56px;
  354. padding: 0;
  355. &:after {
  356. display: none;
  357. opacity: 0;
  358. }
  359. }
  360. .operation-button {
  361. width: 72px;
  362. padding: 0;
  363. margin-left: auto;
  364. & ~ .operation-button {
  365. margin-left: 8px;
  366. }
  367. }
  368. }
  369. .exam-table {
  370. }
  371. }
  372. </style>