InfoExamTask.vue 20 KB

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