InfoPrintTask.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. <template>
  2. <div class="info-print-task">
  3. <p v-if="IS_MODEL2" class="tips-info mb-2">
  4. 考试需要命题老师提交具体印刷份数
  5. </p>
  6. <p v-else class="tips-info mb-2">
  7. 考试需要命题老师提交完整的考务数据,每个卷袋表示一个考场,人数字段为应考人数,备份数量为该考场试卷题卡备用数量
  8. </p>
  9. <el-form ref="modalFormComp" label-position="top">
  10. <el-form-item label="考试时间:" required>
  11. <el-date-picker
  12. v-model="createDate"
  13. type="date"
  14. value-format="timestamp"
  15. placeholder="考试日期"
  16. style="width: 150px"
  17. :editable="false"
  18. @change="timeChange"
  19. >
  20. </el-date-picker>
  21. <el-time-picker
  22. is-range
  23. v-model="createTime"
  24. range-separator="至"
  25. start-placeholder="考试开始时间"
  26. end-placeholder="考试结束时间"
  27. placeholder="选择时间范围"
  28. value-format="timestamp"
  29. :editable="false"
  30. @change="timeChange"
  31. >
  32. </el-time-picker>
  33. </el-form-item>
  34. <el-form-item
  35. label="考试对象:"
  36. required
  37. style="margin-bottom: 0"
  38. ></el-form-item>
  39. </el-form>
  40. <div v-if="IS_MODEL2" class="part-box">
  41. <div class="box-justify mb-1">
  42. <div></div>
  43. <el-button type="primary" @click="toSelectClass">选择班级</el-button>
  44. </div>
  45. <table class="table">
  46. <colgroup>
  47. <col width="90" />
  48. <col width="120" />
  49. <col width="120" />
  50. <col width="200" />
  51. <col width="300" />
  52. </colgroup>
  53. <tr>
  54. <th>卷袋序号</th>
  55. <th>印刷份数</th>
  56. <th>备份数量</th>
  57. <th>印刷室</th>
  58. <th>班级</th>
  59. </tr>
  60. <tr>
  61. <td>1</td>
  62. <td>
  63. <el-input-number
  64. v-model="modalForm.totalSubjects"
  65. class="width-full"
  66. :min="1"
  67. :max="999999"
  68. :step="1"
  69. step-strictly
  70. :controls="false"
  71. ></el-input-number>
  72. </td>
  73. <td>
  74. <el-input-number
  75. v-model="modalForm.backupCount"
  76. class="width-full"
  77. :min="infoExamPrintPlan.backupCount"
  78. :max="999999"
  79. :step="1"
  80. step-strictly
  81. :controls="false"
  82. ></el-input-number>
  83. </td>
  84. <td>
  85. <el-select
  86. v-model="modalForm.printHouseId"
  87. placeholder="请选择"
  88. class="width-full"
  89. filterable
  90. >
  91. <el-option
  92. v-for="room in printHouses"
  93. :key="room.printHouseId"
  94. :value="room.printHouseId"
  95. :label="room.printHouseName"
  96. ></el-option>
  97. </el-select>
  98. </td>
  99. <td>
  100. {{ model2ClassList.map((item) => item.clazzName).join(",") }}
  101. </td>
  102. </tr>
  103. </table>
  104. </div>
  105. <div v-else class="part-box">
  106. <div class="box-justify mb-2">
  107. <div>
  108. <p>
  109. 每个卷袋包含一个考场全部文件(试卷、题卡、卷袋贴、签到表等文件)
  110. </p>
  111. <p>
  112. 共{{ packageInfos.packageCount }}个卷袋,{{
  113. packageInfos.studentCount
  114. }}个考生,共印试卷{{ packageInfos.paperCount }}份(正式{{
  115. packageInfos.paperReleaseCount
  116. }}份,备用{{ packageInfos.paperBackupCount }}份)
  117. </p>
  118. </div>
  119. <div>
  120. <el-button
  121. v-if="checkPrivilege('button', 'ExamTaskStudentObjectImport')"
  122. type="success"
  123. icon="el-icon-download"
  124. @click="downloadTemplate('examTaskStudent')"
  125. >模板下载</el-button
  126. >
  127. <upload-button
  128. v-if="checkPrivilege('button', 'ExamTaskStudentObjectImport')"
  129. btn-icon="el-icon-circle-plus-outline"
  130. btn-content="批量导入"
  131. btn-type="success"
  132. :upload-url="uploadUrl"
  133. :format="['xls', 'xlsx']"
  134. accept=".xls,.xlsx"
  135. @valid-error="validError"
  136. @upload-success="uploadSuccess"
  137. >
  138. </upload-button>
  139. <el-button
  140. v-if="checkPrivilege('button', 'ExamTaskStudentObject')"
  141. type="primary"
  142. :disabled="studentUploaded && !!tableData.length"
  143. @click="toAdd"
  144. >新增考试对象</el-button
  145. >
  146. </div>
  147. </div>
  148. <el-table ref="TableList" :data="tableData" border>
  149. <el-table-column type="index" width="80" label="卷袋序号">
  150. </el-table-column>
  151. <el-table-column prop="className" label="考试对象" width="160">
  152. </el-table-column>
  153. <el-table-column prop="studentCount" label="人数" width="60">
  154. </el-table-column>
  155. <el-table-column prop="backupCount" label="备份数量" width="90">
  156. <template slot-scope="scope">
  157. <el-input-number
  158. v-model="scope.row.backupCount"
  159. style="width: 60px"
  160. :min="scope.row.minBackupCount"
  161. :max="99999"
  162. :step="1"
  163. step-strictly
  164. :controls="false"
  165. @change="backupCountChange"
  166. ></el-input-number>
  167. </template>
  168. </el-table-column>
  169. <el-table-column prop="examPlace" label="校区(考点)" width="140">
  170. <template slot-scope="scope">
  171. <el-input
  172. v-model.trim="scope.row.examPlace"
  173. :maxlength="20"
  174. clearable
  175. ></el-input>
  176. </template>
  177. </el-table-column>
  178. <el-table-column prop="examRoom" label="考试教室(考场)" width="140">
  179. <template slot-scope="scope">
  180. <el-input
  181. v-model.trim="scope.row.examRoom"
  182. :maxlength="20"
  183. clearable
  184. ></el-input>
  185. </template>
  186. </el-table-column>
  187. <el-table-column prop="printHouseName" label="印刷室" width="140">
  188. <template slot-scope="scope">
  189. <el-select
  190. v-model="scope.row.printHouseId"
  191. placeholder="请选择"
  192. filterable
  193. @change="() => printHouseChange(scope.row)"
  194. >
  195. <el-option
  196. v-for="room in printHouses"
  197. :key="room.printHouseId"
  198. :value="room.printHouseId"
  199. :label="room.printHouseName"
  200. ></el-option>
  201. </el-select>
  202. </template>
  203. </el-table-column>
  204. <el-table-column
  205. v-for="item in extendFields"
  206. :key="item.code"
  207. :label="item.name"
  208. width="120"
  209. >
  210. <div slot-scope="scope">
  211. <el-input
  212. v-model="scope.row.extends[item.code]"
  213. placeholder="请输入"
  214. clearable
  215. ></el-input>
  216. </div>
  217. </el-table-column>
  218. <el-table-column
  219. label="操作"
  220. width="120"
  221. class-name="action-column"
  222. align="center"
  223. fixed="right"
  224. >
  225. <template slot-scope="scope">
  226. <el-button
  227. class="btn-danger"
  228. type="text"
  229. @click="toDelete(scope.$index)"
  230. >取消</el-button
  231. >
  232. <el-button
  233. class="btn-primary"
  234. type="text"
  235. @click="toViewStudent(scope.row)"
  236. >查看考生</el-button
  237. >
  238. </template>
  239. </el-table-column>
  240. </el-table>
  241. </div>
  242. <!-- ModifyExamTaskStudent -->
  243. <modify-exam-task-student
  244. ref="ModifyExamTaskStudent"
  245. :disabled-ids="disabledStudentIds"
  246. :filter-params="{
  247. courseCode: infoExamTask.courseCode,
  248. examId: infoExamTask.examId,
  249. teachingRoomId: infoExamTask.teachingRoomId,
  250. }"
  251. :show-student="showStudent"
  252. :selected-ids="IS_MODEL2 ? model2ClassIds : null"
  253. @modified="examStudentModified"
  254. ></modify-exam-task-student>
  255. <!-- PreviewTaskStudent -->
  256. <preview-task-student
  257. ref="PreviewTaskStudent"
  258. :student-list="curRow.examTaskStudentObjectParamList"
  259. :show-student="showStudent"
  260. @close="previewStudentClosed"
  261. ></preview-task-student>
  262. </div>
  263. </template>
  264. <script>
  265. import { calcSum, getTimeDatestamp } from "@/plugins/utils";
  266. import { examRuleDetail } from "../../../base/api";
  267. import { listTaskPrintHouse } from "../../api";
  268. import ModifyExamTaskStudent from "./ModifyExamTaskStudent.vue";
  269. import PreviewTaskStudent from "./PreviewTaskStudent.vue";
  270. import { mapState, mapMutations } from "vuex";
  271. import UploadButton from "@/components/UploadButton";
  272. import templateDownload from "@/mixins/templateDownload";
  273. const initModalForm = {
  274. examStartTime: "",
  275. examEndTime: "",
  276. paperNumber: "",
  277. courseName: "",
  278. courseCode: "",
  279. totalSubjects: 1,
  280. printHouseId: "",
  281. backupCount: null,
  282. classId: "",
  283. };
  284. export default {
  285. name: "info-print-task",
  286. components: { ModifyExamTaskStudent, PreviewTaskStudent, UploadButton },
  287. mixins: [templateDownload],
  288. data() {
  289. return {
  290. modalForm: {
  291. ...initModalForm,
  292. },
  293. tableData: [],
  294. model2ClassList: [],
  295. model2ClassIds: [],
  296. curRow: {},
  297. printHouses: [],
  298. extendFields: [],
  299. packageInfos: {
  300. packageCount: 0,
  301. studentCount: 0,
  302. paperCount: 0,
  303. paperReleaseCount: 0,
  304. paperBackupCount: 0,
  305. },
  306. selectedStudentIds: [],
  307. disabledStudentIds: [],
  308. examStudentList: [],
  309. showStudent: false,
  310. // date-picker
  311. curCreateTime: [],
  312. createDate: "",
  313. createTime: [],
  314. // import
  315. uploadUrl: "/api/admin/exam/task/exam_task_exam_student_import",
  316. dfilename: "学生导入模板.xlsx",
  317. studentUploaded: false,
  318. };
  319. },
  320. computed: {
  321. ...mapState("exam", [
  322. "infoExamTask",
  323. "infoExamTaskDetail",
  324. "infoPrintTask",
  325. "infoExamPrintPlan",
  326. ]),
  327. IS_MODEL2() {
  328. return this.infoExamTask.examModel === "MODEL2";
  329. },
  330. },
  331. watch: {
  332. "infoExamTask.examId": function (val, oldval) {
  333. if (val !== oldval) this.initData();
  334. },
  335. "infoExamTask.courseCode": function (val, oldval) {
  336. if (val !== oldval) this.initData();
  337. },
  338. "infoExamPrintPlan.backupCount": function (val, oldval) {
  339. if (val !== oldval) this.planBackupCountChange();
  340. },
  341. },
  342. mounted() {
  343. this.getExtendFields();
  344. this.getPrintHouses();
  345. const curDate = getTimeDatestamp(Date.now());
  346. const hour = 60 * 60 * 1000;
  347. this.curCreateTime = [curDate + 8 * hour, curDate + 10 * hour];
  348. this.createTime = [...this.curCreateTime];
  349. },
  350. methods: {
  351. ...mapMutations("exam", ["updateTaskInfo"]),
  352. initData() {
  353. this.modalForm = Object.assign(
  354. { ...initModalForm },
  355. {
  356. paperNumber: this.infoExamTask.paperNumber,
  357. courseName: this.infoExamTask.courseName,
  358. courseCode: this.infoExamTask.courseCode,
  359. }
  360. );
  361. if (this.IS_MODEL2) {
  362. this.modalForm.backupCount = this.infoExamPrintPlan.backupCount;
  363. this.model2ClassList = [];
  364. this.model2ClassIds = [];
  365. return;
  366. }
  367. const { examStartTime, examEndTime } = this.infoPrintTask;
  368. if (examStartTime && examEndTime) {
  369. this.createTime = [examStartTime, examEndTime];
  370. this.createDate = getTimeDatestamp(examStartTime);
  371. this.modalForm.examStartTime = this.createTime[0];
  372. this.modalForm.examEndTime = this.createTime[1];
  373. }
  374. this.tableData = [];
  375. this.updatePackageInfos();
  376. this.updateData();
  377. },
  378. planBackupCountChange() {
  379. this.tableData.forEach((item) => {
  380. Object.assign(item, this.getBackupCount(item.studentCount));
  381. });
  382. this.updatePackageInfos();
  383. },
  384. getBackupCount(studentCount) {
  385. const { backupCount } = this.infoExamPrintPlan;
  386. if (backupCount < 1) {
  387. const count = Math.ceil(backupCount * studentCount);
  388. return { backupCount: count, minBackupCount: count };
  389. }
  390. return { backupCount, minBackupCount: backupCount };
  391. },
  392. checkTime() {
  393. if (!this.modalForm.examStartTime || !this.modalForm.examEndTime) {
  394. this.$message.error("请选择考试时间!");
  395. return false;
  396. }
  397. if (this.modalForm.examStartTime >= this.modalForm.examEndTime) {
  398. this.$message.error("考试开始时间必须小于考试结束时间!");
  399. return false;
  400. }
  401. return true;
  402. },
  403. checkData() {
  404. if (!this.checkTime()) return Promise.reject();
  405. if (this.IS_MODEL2) {
  406. if (!this.modalForm.totalSubjects) {
  407. this.$message.error("请输入印刷份数!");
  408. return Promise.reject();
  409. }
  410. if (!this.modalForm.printHouseId) {
  411. this.$message.error("请选择印刷室!");
  412. return Promise.reject();
  413. }
  414. if (!this.modalForm.classId) {
  415. this.$message.error("请选择班级!");
  416. return Promise.reject();
  417. }
  418. return Promise.resolve(true);
  419. }
  420. if (!this.tableData.length) {
  421. this.$message.error("请添加考试对象!");
  422. return Promise.reject();
  423. }
  424. let errorMsg = [];
  425. this.tableData.forEach((row) => {
  426. let errorFields = [];
  427. this.extendFields.forEach((field) => {
  428. if (!row.extends[field.code]) {
  429. errorFields.push(field.name);
  430. }
  431. });
  432. if (!row.backupCount) {
  433. errorFields.push("备份数量");
  434. }
  435. if (!row.examPlace) {
  436. errorFields.push("校区(考点)");
  437. }
  438. if (!row.examRoom) {
  439. errorFields.push("考试教室(考场)");
  440. }
  441. if (!row.printHouseId) {
  442. errorFields.push("印刷室");
  443. }
  444. if (errorFields.length) {
  445. errorMsg.push(
  446. `考试对象${row.className}中,${errorFields.join("、")}必须填写`
  447. );
  448. }
  449. });
  450. if (errorMsg.length) {
  451. this.$message.error(errorMsg.join("。"));
  452. return Promise.reject();
  453. }
  454. return Promise.resolve(true);
  455. },
  456. updateData() {
  457. const tableData = this.tableData.map((row) => {
  458. let nrow = { ...row };
  459. let extendFields = this.extendFields.map((field) => {
  460. let info = { ...field };
  461. info.value = row.extends[field.code];
  462. return info;
  463. });
  464. nrow.extendFields = JSON.stringify(extendFields);
  465. nrow.examStartTime = this.modalForm.examStartTime;
  466. nrow.examEndTime = this.modalForm.examEndTime;
  467. return nrow;
  468. });
  469. this.updateTaskInfo({
  470. infoPrintTask: {
  471. ...this.modalForm,
  472. list: tableData,
  473. },
  474. });
  475. },
  476. updatePackageInfos() {
  477. this.packageInfos.packageCount = this.tableData.length;
  478. this.packageInfos.studentCount = calcSum(
  479. this.tableData.map((item) => item.studentCount)
  480. );
  481. this.packageInfos.paperReleaseCount = this.packageInfos.studentCount;
  482. this.packageInfos.paperBackupCount = calcSum(
  483. this.tableData.map((item) => item.backupCount || 0)
  484. );
  485. this.packageInfos.paperCount =
  486. this.packageInfos.paperReleaseCount +
  487. this.packageInfos.paperBackupCount;
  488. },
  489. async getExtendFields() {
  490. const examRule = await examRuleDetail();
  491. this.extendFields = examRule.extendFields
  492. ? JSON.parse(examRule.extendFields)
  493. : [];
  494. this.extendFields = this.extendFields.filter((item) => item.enable);
  495. },
  496. async getPrintHouses() {
  497. this.printHouses = await listTaskPrintHouse();
  498. },
  499. timeChange() {
  500. if (!this.createDate || !this.createTime) {
  501. this.modalForm.examStartTime = null;
  502. this.modalForm.examEndTime = null;
  503. return;
  504. }
  505. const curDate = getTimeDatestamp(this.createDate);
  506. const timeDate = getTimeDatestamp(this.createTime[0]);
  507. this.modalForm.examStartTime = curDate + this.createTime[0] - timeDate;
  508. this.modalForm.examEndTime = curDate + this.createTime[1] - timeDate;
  509. },
  510. backupCountChange() {
  511. this.updatePackageInfos();
  512. },
  513. printHouseChange(row) {
  514. const curHouse = this.printHouses.find(
  515. (item) => item.printHouseId === row.printHouseId
  516. );
  517. if (curHouse) {
  518. row.printHouseName = curHouse.printHouseName;
  519. }
  520. },
  521. toAdd() {
  522. if (!this.infoExamTask.courseCode) {
  523. this.$message.error("请先选择课程");
  524. return;
  525. }
  526. this.disabledStudentIds = this.getTabelStudentIds();
  527. this.showStudent = this.checkPrivilege("button", "SelectStudent");
  528. this.$refs.ModifyExamTaskStudent.open();
  529. },
  530. getTabelStudentIds() {
  531. let ids = [];
  532. this.tableData.forEach((item) => {
  533. item.examTaskStudentObjectParamList.forEach((elem) => {
  534. ids.push(elem.id);
  535. });
  536. });
  537. return ids;
  538. },
  539. getInitTableRow() {
  540. let modalFormData = { ...this.modalForm };
  541. delete modalFormData.printHouseId;
  542. let data = {
  543. id: this.$randomCode(),
  544. examPlace: "",
  545. examRoom: "",
  546. classId: "",
  547. className: "",
  548. studentCount: "",
  549. printHouseId: "",
  550. printHouseName: "",
  551. extendFields: "",
  552. backupCount: 0,
  553. minBackupCount: 0,
  554. isSelectStudent: true,
  555. examTaskStudentObjectParamList: [],
  556. ...modalFormData,
  557. };
  558. let extendFieldModal = {};
  559. this.extendFields.forEach((field) => {
  560. extendFieldModal[field.code] = "";
  561. });
  562. data.extends = extendFieldModal;
  563. return data;
  564. },
  565. examStudentModified({ selectedStudents, isSelectStudent }) {
  566. if (this.IS_MODEL2) {
  567. this.model2ClassList = selectedStudents.map((item) => {
  568. return {
  569. clazzId: item.clazzId,
  570. clazzName: item.clazzName,
  571. };
  572. });
  573. this.model2ClassIds = this.model2ClassList.map((item) => item.clazzId);
  574. this.modalForm.classId = this.model2ClassIds.join();
  575. return;
  576. }
  577. if (this.studentUploaded) {
  578. this.tableData = [];
  579. this.studentUploaded = false;
  580. }
  581. let examTaskStudentObjectParamList = [];
  582. selectedStudents.forEach((item) => {
  583. item.children.forEach((student) => {
  584. examTaskStudentObjectParamList.push({ ...student, enable: true });
  585. });
  586. });
  587. let tableRow = this.$objAssign(this.getInitTableRow(), {
  588. classId: selectedStudents.map((item) => item.clazzId).join(),
  589. className: selectedStudents.map((item) => item.clazzName).join(),
  590. studentCount: examTaskStudentObjectParamList.length,
  591. examTaskStudentObjectParamList,
  592. isSelectStudent,
  593. ...this.getBackupCount(examTaskStudentObjectParamList.length),
  594. });
  595. this.tableData.push(tableRow);
  596. this.updatePackageInfos();
  597. },
  598. toDelete(index) {
  599. this.tableData.splice(index, 1);
  600. this.updatePackageInfos();
  601. },
  602. toViewStudent(row) {
  603. // console.log(row);
  604. this.curRow = row;
  605. this.$refs.PreviewTaskStudent.open();
  606. },
  607. previewStudentClosed() {
  608. const studentCount = this.curRow.examTaskStudentObjectParamList.filter(
  609. (item) => item.enable
  610. ).length;
  611. Object.assign(this.curRow, {
  612. ...this.getBackupCount(studentCount),
  613. studentCount,
  614. });
  615. },
  616. // import
  617. validError(errorData) {
  618. this.$message.error(errorData.message);
  619. },
  620. uploadSuccess(res) {
  621. this.studentUploaded = true;
  622. this.$message.success("导入成功!");
  623. this.tableData = res.data.map((item) => {
  624. const initRow = this.getInitTableRow();
  625. let examTaskStudentObjectParamList = [];
  626. item.examTaskStudentObjectResultList.forEach((elem) => {
  627. elem.studentInfoList.forEach((std) => {
  628. examTaskStudentObjectParamList.push({
  629. ...std,
  630. studentClazzType: elem.studentClazzType,
  631. id: `${elem.clazzId}_${std.studentId}`,
  632. });
  633. });
  634. });
  635. let tableRow = this.$objAssign(initRow, {
  636. classId: item.examTaskStudentObjectResultList
  637. .map((item) => item.clazzId)
  638. .join(),
  639. className: item.examTaskStudentObjectResultList
  640. .map((item) => item.clazzName)
  641. .join(),
  642. examRoom: item.examRoom,
  643. examPlace: item.examPlace,
  644. studentCount: examTaskStudentObjectParamList.length,
  645. examTaskStudentObjectParamList,
  646. ...this.getBackupCount(examTaskStudentObjectParamList.length),
  647. });
  648. return tableRow;
  649. });
  650. this.updatePackageInfos();
  651. },
  652. // model2 select class
  653. toSelectClass() {
  654. if (!this.infoExamTask.courseCode) {
  655. this.$message.error("请先选择课程");
  656. return;
  657. }
  658. this.disabledStudentIds = [];
  659. this.showStudent = false;
  660. this.$refs.ModifyExamTaskStudent.open();
  661. },
  662. },
  663. };
  664. </script>