InfoExamTask.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. <template>
  2. <div class="info-exam-task">
  3. <div class="part-box part-box-pad part-box-border part-box-gray">
  4. <el-form
  5. ref="examTaskComp"
  6. :model="examTask"
  7. :rules="rules"
  8. label-width="120px"
  9. >
  10. <el-row>
  11. <el-col :span="12">
  12. <el-form-item prop="semesterId" label="使用学期:">
  13. <semester-select
  14. v-model="examTask.semesterId"
  15. class="width-full"
  16. @change="semesterChange"
  17. ></semester-select>
  18. </el-form-item>
  19. </el-col>
  20. <el-col :span="12">
  21. <el-form-item prop="examId" label="考试:">
  22. <exam-select
  23. v-model="examTask.examId"
  24. :semester-id="examTask.semesterId"
  25. :clearable="false"
  26. class="width-full"
  27. @change="examChange"
  28. ></exam-select>
  29. </el-form-item>
  30. </el-col>
  31. <el-col :span="12">
  32. <el-form-item prop="teachingRoomId" label="部门:">
  33. <teaching-room-select
  34. v-model="examTask.teachingRoomId"
  35. class="width-full"
  36. @change="teachingRoomChange"
  37. ></teaching-room-select>
  38. </el-form-item>
  39. </el-col>
  40. <el-col :span="12">
  41. <el-form-item prop="courseCode" label="课程(代码):">
  42. <el-select
  43. v-model="examTask.courseCode"
  44. placeholder="请选择"
  45. filterable
  46. class="width-full"
  47. :disabled="!examTask.examId"
  48. @change="courseChange"
  49. >
  50. <el-option
  51. v-for="item in courses"
  52. :key="item.code"
  53. :value="item.code"
  54. :label="`${item.name}(${item.code})`"
  55. >
  56. </el-option>
  57. </el-select>
  58. </el-form-item>
  59. </el-col>
  60. <el-col :span="12">
  61. <el-form-item prop="paperNumber" label="试卷编号:">
  62. <el-input
  63. v-model.trim="examTask.paperNumber"
  64. placeholder="试卷编号不填写时会自动生成"
  65. :maxlength="50"
  66. clearable
  67. ></el-input>
  68. </el-form-item>
  69. </el-col>
  70. <el-col :span="12">
  71. <el-form-item prop="teacherName" label="拟卷教师:">
  72. <el-input
  73. v-model.trim="examTask.teacherName"
  74. placeholder="请输入拟卷教师"
  75. :maxlength="50"
  76. clearable
  77. ></el-input>
  78. </el-form-item>
  79. </el-col>
  80. </el-row>
  81. </el-form>
  82. </div>
  83. <div class="apply-content task-detail">
  84. <div class="task-body">
  85. <div class="mb-2 text-right">
  86. <el-button
  87. type="info"
  88. icon="el-icon-circle-plus-outline"
  89. @click="addAtachment"
  90. >增加卷型</el-button
  91. >
  92. </div>
  93. <table class="table mb-2">
  94. <colgroup>
  95. <col width="100" />
  96. <col width="280" />
  97. <col />
  98. <col width="60" />
  99. </colgroup>
  100. <tr>
  101. <th>试卷类型</th>
  102. <th>试卷文件</th>
  103. <th>答题卡</th>
  104. <th>操作</th>
  105. </tr>
  106. <tr v-for="(attachment, index) in paperAttachments" :key="index">
  107. <td>{{ attachment.name }}卷</td>
  108. <td>
  109. <el-button
  110. type="text"
  111. class="btn-primary"
  112. @click="toUpload(attachment)"
  113. >
  114. <i
  115. :class="[
  116. 'icon',
  117. attachment.attachmentId ? 'icon-files-act' : 'icon-files'
  118. ]"
  119. ></i
  120. >{{
  121. attachment.attachmentId
  122. ? attachment.filename
  123. : "点击上传试卷文件"
  124. }}
  125. </el-button>
  126. </td>
  127. <td>
  128. <el-select
  129. class="mr-2"
  130. v-model="attachment.cardId"
  131. placeholder="请选择"
  132. style="width: 200px"
  133. filterable
  134. @change="cardChange(attachment)"
  135. >
  136. <el-option
  137. v-for="item in cards"
  138. :key="item.id"
  139. :value="item.id"
  140. :label="item.title"
  141. >
  142. </el-option>
  143. </el-select>
  144. <el-button
  145. class="btn-primary"
  146. type="text"
  147. :disabled="!attachment.cardId"
  148. @click="toViewCard(attachment)"
  149. >预览</el-button
  150. >
  151. <el-button
  152. class="btn-primary"
  153. type="text"
  154. :disabled="
  155. !attachment.cardId || attachment.cardType === 'GENERIC'
  156. "
  157. @click="toEditCard(attachment)"
  158. >编辑</el-button
  159. >
  160. <el-button
  161. class="btn-primary"
  162. type="text"
  163. :disabled="!canCreateCard"
  164. @click="toCreateCard(attachment)"
  165. >新建</el-button
  166. >
  167. </td>
  168. <td>
  169. <el-button
  170. class="btn-danger"
  171. type="text"
  172. @click="deleteAttachment(index)"
  173. >删除</el-button
  174. >
  175. </td>
  176. </tr>
  177. <tr v-if="!paperAttachments.length">
  178. <td colspan="5">
  179. <p class="tips-info text-center">暂无数据</p>
  180. </td>
  181. </tr>
  182. </table>
  183. <p class="tips-info tips-dark mb-2">
  184. 提示:多卷型试卷由于会绑定一个答题卡模板,因此试卷结构必须相同。多卷型试卷之间客观题要求试题内容相同,可允许大题内的小题题序不同。
  185. </p>
  186. <el-form>
  187. <el-form-item label="单次抽卷卷型数量:" label-width="150">
  188. <el-input-number
  189. v-model="examTaskDetail.drawCount"
  190. :min="1"
  191. :max="maxFetchCount"
  192. :step="1"
  193. step-strictly
  194. :controls="false"
  195. ></el-input-number>
  196. </el-form-item>
  197. </el-form>
  198. <h4 class="mb-2">附件<span>(最多4张)</span>:</h4>
  199. <div class="image-list">
  200. <div
  201. class="image-item"
  202. v-for="(img, index) in paperConfirmAttachments"
  203. :key="index"
  204. >
  205. <img
  206. :src="img.url"
  207. :alt="img.filename"
  208. title="点击查看大图"
  209. @click="toPreview(index)"
  210. />
  211. <div class="image-delete">
  212. <i
  213. class="el-icon-delete-solid"
  214. @click="deletePaperConfirmAttachment(index)"
  215. ></i>
  216. </div>
  217. </div>
  218. <div
  219. v-if="paperConfirmAttachments.length < 4"
  220. class="image-item image-add"
  221. title="上传入库审核表"
  222. @click="toUploadPaperConfirm"
  223. >
  224. <i class="el-icon-plus"></i>
  225. </div>
  226. </div>
  227. <h4 class="mb-2">附件说明:</h4>
  228. <el-input
  229. class="mb-2"
  230. v-model="examTaskDetail.remark"
  231. type="textarea"
  232. resize="none"
  233. :rows="2"
  234. :maxlength="100"
  235. clearable
  236. show-word-limit
  237. placeholder="建议不超过100个字"
  238. ></el-input>
  239. </div>
  240. </div>
  241. <!-- upload-paper-dialog -->
  242. <upload-paper-dialog
  243. :paper-attachment="curAttachment"
  244. :upload-type="curUploadType"
  245. @confirm="uploadConfirm"
  246. ref="UploadPaperDialog"
  247. ></upload-paper-dialog>
  248. <!-- image-preview -->
  249. <simple-image-preview
  250. :cur-image="curImage"
  251. @on-prev="toPrevImage"
  252. @on-next="toNextImage"
  253. ref="SimpleImagePreview"
  254. ></simple-image-preview>
  255. <!-- ModifyCard -->
  256. <modify-card ref="ModifyCard" @modified="cardModified"></modify-card>
  257. </div>
  258. </template>
  259. <script>
  260. import UploadPaperDialog from "../UploadPaperDialog";
  261. import SimpleImagePreview from "@/components/SimpleImagePreview";
  262. import ModifyCard from "../../../card/components/ModifyCard";
  263. import { COMMON_CARD_RULE_ID } from "../../../../constants/enumerate";
  264. import { cardForSelectList } from "../../api";
  265. import { courseQuery, examConfigByExamIdOrgId } from "../../../base/api";
  266. import { mapState, mapMutations } from "vuex";
  267. export default {
  268. name: "info-exam-task",
  269. components: { UploadPaperDialog, SimpleImagePreview, ModifyCard },
  270. data() {
  271. return {
  272. task: {},
  273. rules: {
  274. semesterId: [
  275. {
  276. required: true,
  277. message: "请选择使用学期",
  278. trigger: "change"
  279. }
  280. ],
  281. examId: [
  282. {
  283. required: true,
  284. message: "请选择考试",
  285. trigger: "change"
  286. }
  287. ],
  288. teachingRoomId: [
  289. {
  290. required: true,
  291. message: "请选择机构",
  292. trigger: "change"
  293. }
  294. ],
  295. courseCode: [
  296. {
  297. required: true,
  298. message: "请选择课程",
  299. trigger: "change"
  300. }
  301. ],
  302. paperNumber: [
  303. {
  304. message: "试卷编号不能超过50个字符",
  305. max: 50,
  306. trigger: "change"
  307. }
  308. ],
  309. teacherName: [
  310. {
  311. message: "拟卷教师不能超过50个字符",
  312. max: 50,
  313. trigger: "change"
  314. }
  315. ]
  316. },
  317. examTask: {},
  318. cards: [],
  319. courses: [],
  320. semesters: [],
  321. cardRuleName: "全部通卡",
  322. // exam-task-detail
  323. examTaskDetail: { makeMethod: "" },
  324. paperConfirmAttachmentId: { attachmentId: "", filename: "", url: "" },
  325. paperAttachments: [],
  326. paperConfirmAttachments: [],
  327. curAttachment: {},
  328. curUploadType: "paper",
  329. attachmentLimitCount: 26,
  330. abc: "abcdefghijklmnopqrstuvwxyz".toUpperCase(),
  331. // image-preview
  332. curImage: {},
  333. curImageIndex: 0
  334. };
  335. },
  336. computed: {
  337. ...mapState("exam", [
  338. "infoExamTask",
  339. "infoExamTaskDetail",
  340. "infoExamPrintPlan"
  341. ]),
  342. maxFetchCount() {
  343. return this.paperAttachments.length < 1
  344. ? 1
  345. : this.paperAttachments.length;
  346. },
  347. canCreateCard() {
  348. return (
  349. this.examTask.courseCode &&
  350. this.examTask.examId &&
  351. this.examTask.cardRuleId !== COMMON_CARD_RULE_ID
  352. );
  353. }
  354. },
  355. watch: {
  356. "examTask.examId": function(val, oldval) {
  357. if (val !== oldval) this.examAndRoomChange();
  358. },
  359. "examTask.teachingRoomId": function(val, oldval) {
  360. if (val !== oldval) this.examAndRoomChange();
  361. }
  362. },
  363. mounted() {
  364. this.initData();
  365. },
  366. methods: {
  367. ...mapMutations("exam", ["updateTaskInfo"]),
  368. initData() {
  369. this.examTask = { ...this.infoExamTask };
  370. this.examTaskDetail = { ...this.infoExamTaskDetail };
  371. this.paperAttachments = this.examTaskDetail.paperAttachmentIds
  372. ? JSON.parse(this.examTaskDetail.paperAttachmentIds)
  373. : [];
  374. if (!this.paperAttachments.length) {
  375. this.addAtachment();
  376. }
  377. this.paperConfirmAttachments = this.examTaskDetail
  378. .paperConfirmAttachmentIds
  379. ? JSON.parse(this.examTaskDetail.paperConfirmAttachmentIds)
  380. : [];
  381. this.getCourses();
  382. this.getCardList();
  383. this.$nextTick(() => {
  384. this.$refs.examTaskComp.clearValidate();
  385. });
  386. },
  387. async getCardList() {
  388. if (!this.examTask.courseCode || !this.examTask.examId) return;
  389. const data = await cardForSelectList({
  390. courseCode: this.examTask.courseCode,
  391. examId: this.examTask.examId
  392. });
  393. this.cards = data || [];
  394. },
  395. async getCourses() {
  396. if (!this.examTask.teachingRoomId) return;
  397. const res = await courseQuery({
  398. teachingRoomId: this.examTask.teachingRoomId
  399. });
  400. this.courses = res || [];
  401. },
  402. semesterChange(val) {
  403. this.examTask.paperName = val.name;
  404. },
  405. examChange(val) {
  406. console.log(val);
  407. this.examTask.examModel = val.examModel;
  408. this.paperAttachments.forEach(item => {
  409. item.cardId = "";
  410. item.cardType = "";
  411. item.cardTitle = "";
  412. });
  413. this.cards = [];
  414. this.getCardList();
  415. },
  416. teachingRoomChange() {
  417. this.examTask.courseCode = "";
  418. this.examTask.courseName = "";
  419. this.courses = [];
  420. this.paperAttachments.forEach(item => {
  421. item.cardId = "";
  422. item.cardType = "";
  423. item.cardTitle = "";
  424. });
  425. this.cards = [];
  426. this.getCourses();
  427. },
  428. courseChange(val) {
  429. if (val) {
  430. const course = this.courses.find(item => item.code === val);
  431. this.examTask.courseName = course.name;
  432. this.examTask.courseId = course.id;
  433. } else {
  434. this.examTask.courseName = "";
  435. this.examTask.courseId = "";
  436. }
  437. this.paperAttachments.forEach(item => {
  438. item.cardId = "";
  439. item.cardType = "";
  440. item.cardTitle = "";
  441. });
  442. this.cards = [];
  443. this.getCardList();
  444. this.updateTaskInfo({ infoExamTask: this.examTask });
  445. },
  446. async examAndRoomChange() {
  447. this.updateTaskInfo({ infoExamTask: this.examTask });
  448. const { examId, teachingRoomId } = this.examTask;
  449. if (examId && teachingRoomId) {
  450. const examPrintPlan = await examConfigByExamIdOrgId({
  451. examId,
  452. orgId: teachingRoomId
  453. });
  454. this.examTask.cardRuleId = examPrintPlan.cardRuleId;
  455. this.updateTaskInfo({
  456. infoExamPrintPlan: Object.assign(
  457. {},
  458. this.infoExamPrintPlan,
  459. examPrintPlan
  460. ),
  461. infoExamTask: this.examTask
  462. });
  463. }
  464. },
  465. cardChange(attachment) {
  466. const card = this.cards.find(item => item.id === attachment.cardId);
  467. if (card) {
  468. attachment.cardType = card.type;
  469. attachment.cardTitle = card.title;
  470. }
  471. },
  472. toCreateCard(attachment) {
  473. if (!this.examTask.cardRuleId) {
  474. this.$message.error("题卡规则缺失!");
  475. return;
  476. }
  477. this.curAttachment = { ...attachment };
  478. this.$ls.set("prepareTcPCard", {
  479. courseCode: this.examTask.courseCode,
  480. courseName: this.examTask.courseName,
  481. schoolName: this.$ls.get("schoolName"),
  482. makeMethod: "SELF",
  483. cardRuleId: this.examTask.cardRuleId,
  484. type: "CUSTOM",
  485. createMethod: "STANDARD"
  486. });
  487. this.$refs.ModifyCard.open();
  488. },
  489. toEditCard(attachment) {
  490. this.curAttachment = { ...attachment };
  491. this.$ls.set("prepareTcPCard", {
  492. id: attachment.cardId,
  493. courseCode: this.examTask.courseCode,
  494. courseName: this.examTask.courseName,
  495. schoolName: this.$ls.get("schoolName"),
  496. makeMethod: "SELF",
  497. cardRuleId: this.examTask.cardRuleId,
  498. type: "CUSTOM",
  499. createMethod: "STANDARD"
  500. });
  501. this.$refs.ModifyCard.open();
  502. },
  503. toViewCard(attachment) {
  504. window.open(
  505. this.getRouterPath({
  506. name: "CardPreview",
  507. params: {
  508. cardId: attachment.cardId,
  509. viewType: "view"
  510. }
  511. })
  512. );
  513. },
  514. async cardModified(cardId) {
  515. if (!cardId) return;
  516. let card = this.cards.find(item => item.id === cardId);
  517. if (!card) {
  518. await this.getCardList();
  519. card = this.cards.find(item => item.id === cardId);
  520. }
  521. const aind = this.paperAttachments.findIndex(
  522. item => item.name === this.curAttachment.name
  523. );
  524. if (aind !== -1 && card) {
  525. this.paperAttachments[aind].cardId = card.id;
  526. this.paperAttachments[aind].cardType = card.type;
  527. this.paperAttachments[aind].cardTitle = card.title;
  528. }
  529. },
  530. async checkData() {
  531. const valid = await this.$refs.examTaskComp.validate().catch(() => {});
  532. if (!valid) return Promise.reject();
  533. const attachmentValid = !this.paperAttachments.some(
  534. item => !item.attachmentId
  535. );
  536. // 设置了入库强制包含试卷时,校验试卷是否上传。
  537. if (this.examTaskDetail.includePaper && !attachmentValid) {
  538. this.$message.error("请完成试卷文件上传!");
  539. return Promise.reject();
  540. }
  541. // if (!this.paperConfirmAttachments.length) {
  542. // this.$message.error("请上传附件!");
  543. // return;
  544. // }
  545. const cardValid = !this.paperAttachments.some(item => !item.cardId);
  546. if (!cardValid) {
  547. this.$message.error("有试卷类型未选择题卡!");
  548. return Promise.reject();
  549. }
  550. return Promise.resolve(true);
  551. },
  552. updateData() {
  553. let data = {
  554. infoExamTask: this.examTask,
  555. infoExamTaskDetail: this.getTaskDetailData()
  556. };
  557. this.updateTaskInfo(data);
  558. },
  559. emitRelateInfo(type) {
  560. this.$emit("relate-info-change", this.getData(), type);
  561. },
  562. // exam-task-detail edit
  563. addAtachment() {
  564. if (this.paperAttachments.length >= this.attachmentLimitCount) return;
  565. const newAttachment = {
  566. name: this.abc[this.paperAttachments.length],
  567. attachmentId: "",
  568. cardId: "",
  569. cardType: "",
  570. cardTitle: "",
  571. filename: "",
  572. pages: 0
  573. };
  574. this.paperAttachments.push(newAttachment);
  575. },
  576. deleteAttachment(index) {
  577. if (this.paperAttachments.length <= 1) {
  578. this.$message.error("试卷类型数量不得少于1");
  579. return;
  580. }
  581. this.paperAttachments.splice(index, 1);
  582. this.paperAttachments.forEach((item, itemIndex) => {
  583. item.name = this.abc[itemIndex];
  584. });
  585. if (
  586. this.examTaskDetail.drawCount &&
  587. this.examTaskDetail.drawCount > this.paperAttachments.length
  588. ) {
  589. this.examTaskDetail.drawCount = this.paperAttachments.length;
  590. }
  591. },
  592. toUpload(attachment) {
  593. this.curUploadType = "paper";
  594. this.curAttachment = {
  595. ...attachment
  596. };
  597. this.$refs.UploadPaperDialog.open();
  598. },
  599. toUploadPaperConfirm() {
  600. if (this.paperConfirmAttachments.length >= 4) return;
  601. this.curUploadType = "paperConfirm";
  602. this.curAttachment = {
  603. ...this.paperConfirmAttachmentId
  604. };
  605. this.$refs.UploadPaperDialog.open();
  606. },
  607. uploadConfirm(attachment, uploadType) {
  608. if (uploadType === "paper") {
  609. const index = this.paperAttachments.findIndex(
  610. item => item.name === attachment.name
  611. );
  612. this.paperAttachments.splice(index, 1, { ...attachment });
  613. } else {
  614. this.paperConfirmAttachments.push(attachment);
  615. }
  616. },
  617. deletePaperConfirmAttachment(index) {
  618. this.paperConfirmAttachments.splice(index, 1);
  619. },
  620. // cardConfirm(data) {
  621. // this.examTaskDetail = this.$objAssign(this.examTaskDetail, data);
  622. // },
  623. getTaskDetailData() {
  624. let data = { ...this.examTaskDetail };
  625. data.paperType = this.paperAttachments.map(item => item.name).join(",");
  626. data.paperAttachmentIds = JSON.stringify(this.paperAttachments, (k, v) =>
  627. k === "url" ? undefined : v
  628. );
  629. data.paperConfirmAttachmentIds = JSON.stringify(
  630. this.paperConfirmAttachments
  631. );
  632. return data;
  633. },
  634. // image-preview
  635. toPreview(index) {
  636. this.curImageIndex = index;
  637. this.selectImage(index);
  638. this.$refs.SimpleImagePreview.open();
  639. },
  640. selectImage(index) {
  641. this.curImage = this.paperConfirmAttachments[index];
  642. },
  643. toPrevImage() {
  644. if (this.curImageIndex === 0) {
  645. this.curImageIndex = this.paperConfirmAttachments.length - 1;
  646. } else {
  647. this.curImageIndex--;
  648. }
  649. this.selectImage(this.curImageIndex);
  650. },
  651. toNextImage() {
  652. if (this.curImageIndex === this.paperConfirmAttachments.length - 1) {
  653. this.curImageIndex = 0;
  654. } else {
  655. this.curImageIndex++;
  656. }
  657. this.selectImage(this.curImageIndex);
  658. }
  659. }
  660. };
  661. </script>