TaskPrint.vue 21 KB

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