InfoExamTask.vue 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265
  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. default-select-in-used
  17. @change="semesterChange"
  18. ></semester-select>
  19. </el-form-item>
  20. </el-col>
  21. <el-col :span="12">
  22. <el-form-item prop="examId" label="考试:">
  23. <exam-select
  24. v-model="examTask.examId"
  25. :semester-id="examTask.semesterId"
  26. :clearable="false"
  27. class="width-full"
  28. @change="examChange"
  29. ></exam-select>
  30. </el-form-item>
  31. </el-col>
  32. </el-row>
  33. <el-row>
  34. <el-col :span="12">
  35. <el-form-item label="部门:">
  36. {{ teachingRoomName }}
  37. </el-form-item>
  38. </el-col>
  39. <el-col :span="12">
  40. <el-form-item prop="courseId" label="课程(代码):">
  41. <el-select
  42. v-model="examTask.courseId"
  43. placeholder="请选择"
  44. filterable
  45. class="width-full"
  46. :disabled="!examTask.examId"
  47. @change="courseChange"
  48. >
  49. <el-option-group
  50. v-for="item in courses"
  51. :key="item.label"
  52. :label="item.label"
  53. >
  54. <el-option
  55. v-for="course in item.options"
  56. :key="course.id"
  57. :value="course.id"
  58. :label="`${course.name}(${course.code})`"
  59. >
  60. </el-option>
  61. </el-option-group>
  62. </el-select>
  63. </el-form-item>
  64. </el-col>
  65. </el-row>
  66. <el-row>
  67. <el-col :span="12">
  68. <el-form-item prop="paperNumber" label="试卷编号:">
  69. <el-input
  70. v-model.trim="examTask.paperNumber"
  71. placeholder="试卷编号不填写时会自动生成"
  72. :maxlength="50"
  73. clearable
  74. ></el-input>
  75. </el-form-item>
  76. </el-col>
  77. <el-col :span="12">
  78. <el-form-item prop="teacherName" label="拟卷教师:">
  79. <el-input
  80. v-model.trim="examTask.teacherName"
  81. placeholder="请输入拟卷教师"
  82. :maxlength="50"
  83. clearable
  84. ></el-input>
  85. </el-form-item>
  86. </el-col>
  87. </el-row>
  88. <el-row>
  89. <el-col :span="12">
  90. <el-form-item label="启用AB卷:">
  91. <el-switch
  92. v-model="examTask.openAb"
  93. @change="openAbChange"
  94. ></el-switch>
  95. </el-form-item>
  96. </el-col>
  97. </el-row>
  98. </el-form>
  99. </div>
  100. <div class="apply-content task-detail">
  101. <div class="task-body">
  102. <div
  103. v-if="checkPrivilege('button', 'SelectTikuPaper', 'TaskApplyManage')"
  104. class="mb-4 tab-btns"
  105. >
  106. <el-button
  107. v-for="tab in tabs"
  108. :key="tab.val"
  109. size="medium"
  110. :type="curTab == tab.val ? 'primary' : 'default'"
  111. @click="selectMenu(tab.val)"
  112. >{{ tab.name }}
  113. </el-button>
  114. </div>
  115. <table class="table mb-2">
  116. <colgroup>
  117. <col width="90" />
  118. <col width="60" />
  119. <col width="280" />
  120. <col />
  121. <col v-if="!IS_REBUILD" width="80" />
  122. </colgroup>
  123. <tr>
  124. <th>备用卷</th>
  125. <th>卷型</th>
  126. <th>试卷文件</th>
  127. <th>
  128. 答题卡
  129. <span v-if="examTaskInstr" class="tips-markedness">
  130. ({{ examTaskInstr }})
  131. </span>
  132. </th>
  133. <th v-if="!IS_REBUILD">操作</th>
  134. </tr>
  135. <template v-for="(paperAttachment, pindex) in paperAttachments">
  136. <tr
  137. v-for="(attachment, index) in paperAttachment.paperAttachmentIds"
  138. :key="`${pindex}-${index}`"
  139. >
  140. <td
  141. v-if="index === 0"
  142. :rowspan="paperAttachment.paperAttachmentIds.length"
  143. >
  144. 卷{{ paperAttachment.serialNumber }}
  145. </td>
  146. <td>{{ attachment.name }}</td>
  147. <td v-if="IS_TIKU_TAB">
  148. <div class="box-justify">
  149. <el-button
  150. type="text"
  151. class="btn-primary box-grow"
  152. @click="toSelect(attachment)"
  153. >
  154. <i
  155. :class="[
  156. 'icon',
  157. attachment.attachmentId
  158. ? 'icon-files-act'
  159. : 'icon-files',
  160. ]"
  161. ></i
  162. >{{ attachment.filename || "选择试卷" }}
  163. </el-button>
  164. <el-button
  165. type="text"
  166. class="btn-primary box-static"
  167. :disabled="!attachment.paperUrl"
  168. @click="toViewPaper(attachment)"
  169. >预览</el-button
  170. >
  171. </div>
  172. </td>
  173. <td v-else>
  174. <el-button
  175. type="text"
  176. class="btn-primary"
  177. @click="toUpload(attachment)"
  178. >
  179. <i
  180. :class="[
  181. 'icon',
  182. attachment.attachmentId ? 'icon-files-act' : 'icon-files',
  183. ]"
  184. ></i
  185. >{{
  186. attachment.attachmentId
  187. ? attachment.filename
  188. : "点击上传试卷文件"
  189. }}
  190. </el-button>
  191. </td>
  192. <template v-if="IS_TIKU_TAB">
  193. <td
  194. v-if="index === 0"
  195. :rowspan="paperAttachment.paperAttachmentIds.length"
  196. >
  197. <el-select
  198. v-model="attachment.cardId"
  199. placeholder="请选择"
  200. style="width: 260px; margin-right: 10px"
  201. @change="(val) => tkCardChange(val, attachment)"
  202. >
  203. <el-option
  204. v-for="item in attachment.cards"
  205. :key="item.id"
  206. :value="item.id"
  207. :label="item.title"
  208. >
  209. <div class="box-justify">
  210. <div class="box-grow">{{ item.title }}</div>
  211. <el-button
  212. class="btn-danger btn-icon box-static"
  213. type="text"
  214. icon="el-icon-remove"
  215. @click="toDeleteCard(item, attachment)"
  216. ></el-button>
  217. </div>
  218. </el-option>
  219. </el-select>
  220. <el-button
  221. class="btn-primary"
  222. type="text"
  223. :disabled="!attachment.cardId"
  224. @click="toViewCard(attachment)"
  225. >预览</el-button
  226. >
  227. <el-button
  228. v-if="!IS_REBUILD"
  229. class="btn-primary"
  230. type="text"
  231. :disabled="!attachment.cardId"
  232. @click="toEditCard(attachment)"
  233. >编辑</el-button
  234. >
  235. </td>
  236. </template>
  237. <template v-else>
  238. <td
  239. v-if="index === 0"
  240. :rowspan="paperAttachment.paperAttachmentIds.length"
  241. >
  242. <el-select
  243. class="mr-2"
  244. v-model="attachment.cardId"
  245. placeholder="请选择"
  246. style="width: 200px"
  247. filterable
  248. @change="cardChange(attachment)"
  249. >
  250. <el-option
  251. v-for="item in cards"
  252. :key="item.id"
  253. :value="item.id"
  254. :label="item.title"
  255. :disabled="item.disabled"
  256. >
  257. <div class="box-justify">
  258. <div class="box-grow">
  259. <span
  260. :class="[
  261. item.type === 'GENERIC'
  262. ? 'color-success'
  263. : 'color-primary',
  264. 'mr-1',
  265. {
  266. 'color-danger': item.used,
  267. },
  268. ]"
  269. >[{{ item.type === "GENERIC" ? "通" : "专" }}]</span
  270. >
  271. {{ item.title }}
  272. </div>
  273. <el-button
  274. v-if="item.type !== 'GENERIC'"
  275. class="btn-danger btn-icon box-static"
  276. type="text"
  277. icon="el-icon-remove"
  278. @click="toDeleteCard(item, attachment)"
  279. ></el-button>
  280. </div>
  281. </el-option>
  282. </el-select>
  283. <span
  284. v-if="attachment.cardId"
  285. :class="[
  286. attachment.cardType === 'GENERIC'
  287. ? 'color-success'
  288. : 'color-primary',
  289. 'mr-1',
  290. {
  291. 'color-danger': attachment.used,
  292. },
  293. ]"
  294. >[{{
  295. attachment.cardType === "GENERIC" ? "通" : "专"
  296. }}]</span
  297. >
  298. <el-button
  299. class="btn-primary"
  300. type="text"
  301. :disabled="!attachment.cardId"
  302. @click="toViewCard(attachment)"
  303. >预览</el-button
  304. >
  305. <template v-if="!IS_REBUILD">
  306. <el-button
  307. class="btn-primary"
  308. type="text"
  309. :disabled="
  310. !attachment.cardId ||
  311. (attachment.cardType === 'GENERIC' &&
  312. attachment.createMethod !== 'STANDARD')
  313. "
  314. @click="toCopyCard(attachment)"
  315. >复制</el-button
  316. >
  317. <el-button
  318. class="btn-primary"
  319. type="text"
  320. :disabled="
  321. !attachment.cardId ||
  322. attachment.cardType === 'GENERIC' ||
  323. !(!attachment.used && attachment.createId === user.id)
  324. "
  325. @click="toEditCard(attachment)"
  326. >编辑</el-button
  327. >
  328. <el-button
  329. v-if="index === 0"
  330. class="btn-primary"
  331. type="text"
  332. :disabled="!canCreateCard"
  333. @click="toCreateCard(attachment)"
  334. >新建</el-button
  335. >
  336. </template>
  337. </td>
  338. </template>
  339. <td
  340. v-if="!IS_REBUILD && index === 0"
  341. :rowspan="paperAttachment.paperAttachmentIds.length"
  342. class="text-right"
  343. >
  344. <el-button
  345. v-if="pindex === paperAttachments.length - 1"
  346. class="btn-primary btn-icon"
  347. type="text"
  348. icon="el-icon-circle-plus"
  349. @click="addAtachment"
  350. ></el-button>
  351. <el-button
  352. class="btn-danger btn-icon"
  353. type="text"
  354. icon="el-icon-remove"
  355. @click="deleteAttachment(pindex)"
  356. ></el-button>
  357. </td>
  358. </tr>
  359. </template>
  360. <tr v-if="!paperAttachments.length">
  361. <td colspan="5">
  362. <p class="tips-info text-center">暂无数据</p>
  363. </td>
  364. </tr>
  365. </table>
  366. <h4 class="mb-2">
  367. 附件<span>(最多4个,仅限{{ attachmentFormat.join("、") }}文件)</span
  368. >:
  369. </h4>
  370. <div class="image-list">
  371. <div
  372. v-for="(item, index) in imageAttachments"
  373. :key="`image${index}`"
  374. class="image-item"
  375. >
  376. <img
  377. :src="item.url"
  378. :alt="item.filename"
  379. title="点击查看大图"
  380. @click="toPreview(index)"
  381. />
  382. <div class="image-delete">
  383. <i
  384. class="el-icon-delete-solid"
  385. @click="deletePaperConfirmAttachment(item)"
  386. ></i>
  387. </div>
  388. </div>
  389. <div
  390. v-if="paperConfirmAttachments.length < 4"
  391. class="image-item image-add"
  392. title="上传附件"
  393. @click="toUploadPaperConfirm"
  394. >
  395. <i class="el-icon-plus"></i>
  396. </div>
  397. <br />
  398. <div
  399. class="audio-item"
  400. v-for="(item, index) in audioAttachments"
  401. :key="`audio${index}`"
  402. >
  403. <audio :src="item.url" :alt="item.filename" controls></audio>
  404. <div class="audio-delete">
  405. <i
  406. class="el-icon-delete-solid"
  407. @click="deletePaperConfirmAttachment(item)"
  408. ></i>
  409. </div>
  410. </div>
  411. </div>
  412. <h4 class="mb-2">附件说明:</h4>
  413. <el-input
  414. class="mb-2"
  415. v-model="examTaskDetail.remark"
  416. type="textarea"
  417. resize="none"
  418. :rows="2"
  419. :maxlength="100"
  420. clearable
  421. show-word-limit
  422. placeholder="建议不超过100个字"
  423. ></el-input>
  424. </div>
  425. </div>
  426. <!-- upload-paper-dialog -->
  427. <upload-paper-dialog
  428. :paper-attachment="curAttachment"
  429. :upload-type="curUploadType"
  430. @confirm="uploadConfirm"
  431. ref="UploadPaperDialog"
  432. ></upload-paper-dialog>
  433. <!-- image-preview -->
  434. <simple-image-preview
  435. :cur-image="curImage"
  436. @on-prev="toPrevImage"
  437. @on-next="toNextImage"
  438. ref="SimpleImagePreview"
  439. ></simple-image-preview>
  440. <!-- ModifyCard -->
  441. <modify-card ref="ModifyCard" @modified="cardModified"></modify-card>
  442. <!-- SelectTikuPaperDialog -->
  443. <select-tiku-paper-dialog
  444. ref="SelectTikuPaperDialog"
  445. :row="curAttachment"
  446. @confirm="tikuPaperSelected"
  447. ></select-tiku-paper-dialog>
  448. <!-- CardBuildDialog -->
  449. <card-build-dialog
  450. ref="CardBuildDialog"
  451. :presetData="cardBuildPresetData"
  452. @confirm="cardBuildConfirm"
  453. ></card-build-dialog>
  454. <!-- card-preview-dialog -->
  455. <card-preview-dialog
  456. ref="CardPreviewDialog"
  457. :card-id="curAttachment.cardId"
  458. show-watermark
  459. ></card-preview-dialog>
  460. </div>
  461. </template>
  462. <script>
  463. import { mapState, mapMutations } from "vuex";
  464. import UploadPaperDialog from "../UploadPaperDialog.vue";
  465. import SimpleImagePreview from "@/components/SimpleImagePreview";
  466. import ModifyCard from "../../../card/components/ModifyCard.vue";
  467. import SelectTikuPaperDialog from "./SelectTikuPaperDialog.vue";
  468. import { COMMON_CARD_RULE_ID } from "../../../../constants/enumerate";
  469. import { cardForSelectList } from "../../api";
  470. import {
  471. courseQuery,
  472. deleteCard,
  473. examConfigByExamIdOrgId,
  474. } from "../../../base/api";
  475. import { copyCard } from "../../../card/api";
  476. import CardBuildDialog from "../../../card/components/CardBuildDialog.vue";
  477. import CardPreviewDialog from "../../../card/components/CardPreviewDialog.vue";
  478. import { deepCopy } from "@/plugins/utils";
  479. // type=GENERIC时, 为通卡,不可复制,不可编辑,可预览。
  480. // type=CUSTOM时,可复制,不可编辑,如果是当前自己任务的题卡,才可编辑。
  481. export default {
  482. name: "info-exam-task",
  483. components: {
  484. UploadPaperDialog,
  485. SimpleImagePreview,
  486. ModifyCard,
  487. SelectTikuPaperDialog,
  488. CardBuildDialog,
  489. CardPreviewDialog,
  490. },
  491. data() {
  492. return {
  493. tabs: [
  494. {
  495. name: "上传本地试卷",
  496. val: "upload",
  497. },
  498. {
  499. name: "从题库选择试卷",
  500. val: "tiku",
  501. },
  502. ],
  503. curTab: "upload",
  504. user: {},
  505. task: {},
  506. rules: {
  507. semesterId: [
  508. {
  509. required: true,
  510. message: "请选择使用学期",
  511. trigger: "change",
  512. },
  513. ],
  514. examId: [
  515. {
  516. required: true,
  517. message: "请选择考试",
  518. trigger: "change",
  519. },
  520. ],
  521. teachingRoomId: [
  522. {
  523. required: true,
  524. message: "请选择机构",
  525. trigger: "change",
  526. },
  527. ],
  528. courseId: [
  529. {
  530. required: true,
  531. message: "请选择课程",
  532. trigger: "change",
  533. },
  534. ],
  535. paperNumber: [
  536. {
  537. message: "试卷编号不能超过50个字符",
  538. max: 50,
  539. trigger: "change",
  540. },
  541. ],
  542. teacherName: [
  543. {
  544. message: "拟卷教师不能超过50个字符",
  545. max: 50,
  546. trigger: "change",
  547. },
  548. ],
  549. },
  550. examTask: {},
  551. cards: [],
  552. courses: [],
  553. semesters: [],
  554. teachingRoomName: "",
  555. cardRuleName: "全部通卡",
  556. // card-build
  557. cardBuildPresetData: {},
  558. // exam-task-detail
  559. examTaskDetail: [],
  560. paperConfirmAttachmentId: {
  561. attachmentId: "",
  562. filename: "",
  563. url: "",
  564. fileType: "",
  565. },
  566. paperAttachments: [],
  567. paperConfirmAttachments: [],
  568. curAttachment: {},
  569. curUploadType: "paper",
  570. abc: "abcdefghijklmnopqrstuvwxyz".toUpperCase(),
  571. examTaskInstr: this.$ls.get("schoolInfo", { examTaskInstr: "" })
  572. .examTaskInstr,
  573. attachmentFormat: ["jpg", "png", "mp3"],
  574. // image-preview
  575. curImage: {},
  576. curImageIndex: 0,
  577. };
  578. },
  579. computed: {
  580. ...mapState("exam", [
  581. "infoExamTask",
  582. "infoExamTaskDetail",
  583. "infoExamPrintPlan",
  584. ]),
  585. maxFetchCount() {
  586. return this.paperAttachments.length < 1
  587. ? 1
  588. : this.paperAttachments.length;
  589. },
  590. canCreateCard() {
  591. return (
  592. this.examTask.courseId &&
  593. this.examTask.examId &&
  594. this.examTask.cardRuleId !== COMMON_CARD_RULE_ID
  595. );
  596. },
  597. IS_TIKU_TAB() {
  598. return this.curTab === "tiku";
  599. },
  600. IS_REBUILD() {
  601. return this.examTask.category === "REBUILD";
  602. },
  603. imageAttachments() {
  604. return this.paperConfirmAttachments.filter(
  605. (item) => item.fileType === "image"
  606. );
  607. },
  608. audioAttachments() {
  609. return this.paperConfirmAttachments.filter(
  610. (item) => item.fileType === "audio"
  611. );
  612. },
  613. },
  614. watch: {
  615. "examTask.examId": function (val, oldval) {
  616. if (val !== oldval) this.examAndRoomChange();
  617. },
  618. "examTask.teachingRoomId": function (val, oldval) {
  619. if (val !== oldval) this.examAndRoomChange();
  620. },
  621. },
  622. mounted() {
  623. this.initData();
  624. },
  625. methods: {
  626. ...mapMutations("exam", ["updateTaskInfo"]),
  627. initData() {
  628. this.user = this.$ls.get("user", {});
  629. const userOrg = this.user.orgInfo;
  630. this.teachingRoomName = userOrg.name;
  631. this.examTask = { ...this.infoExamTask, teachingRoomId: userOrg.id };
  632. this.paperAttachments = this.examTaskDetail.map((item) => {
  633. const paperAttachmentIds = item.paperAttachmentIds
  634. ? JSON.parse(item.paperAttachmentIds)
  635. : [];
  636. return {
  637. ...item,
  638. paperAttachmentIds,
  639. };
  640. });
  641. if (!this.paperAttachments.length) {
  642. this.addAtachment();
  643. }
  644. this.paperConfirmAttachments = this.examTask.paperConfirmAttachmentIds
  645. ? JSON.parse(this.examTask.paperConfirmAttachmentIds)
  646. : [];
  647. this.getCourses();
  648. this.getCardList();
  649. this.$nextTick(() => {
  650. this.$refs.examTaskComp.clearValidate();
  651. });
  652. },
  653. openAbChange() {
  654. if (this.examTask.openAb) {
  655. this.paperAttachments.forEach((paperAttachment) => {
  656. const attachment = paperAttachment.paperAttachmentIds[0];
  657. paperAttachment.paperAttachmentIds.push(
  658. Object.assign(deepCopy(attachment), {
  659. name: "B",
  660. attachmentId: "",
  661. filename: "",
  662. paperId: null,
  663. paperUrl: null,
  664. pages: 0,
  665. })
  666. );
  667. });
  668. } else {
  669. this.paperAttachments.forEach((paperAttachment) => {
  670. paperAttachment.paperAttachmentIds =
  671. paperAttachment.paperAttachmentIds.slice(0, 1);
  672. });
  673. }
  674. },
  675. async selectMenu(tab) {
  676. const result = await this.$confirm(
  677. "更改类型会清空已设置数据,确定要更改类型?",
  678. "提示",
  679. {
  680. type: "warning",
  681. }
  682. ).catch(() => {});
  683. if (result !== "confirm") return;
  684. this.paperAttachments = [];
  685. this.addAtachment();
  686. this.curTab = tab;
  687. },
  688. async getCardList() {
  689. if (!this.examTask.courseId || !this.examTask.examId) return;
  690. const data = await cardForSelectList({
  691. courseId: this.examTask.courseId,
  692. examId: this.examTask.examId,
  693. });
  694. this.cards = data || [];
  695. if (this.IS_REBUILD) {
  696. this.cards = this.cards.filter((item) => item.type === "GENERIC");
  697. }
  698. },
  699. async getTkCardList(attachment) {
  700. if (
  701. !this.examTask.courseId ||
  702. !this.examTask.examId ||
  703. !attachment.paperId
  704. )
  705. return;
  706. const data = await cardForSelectList({
  707. courseId: this.examTask.courseId,
  708. examId: this.examTask.examId,
  709. paperId: attachment.paperId,
  710. });
  711. attachment.cards = (data || []).map((item) => {
  712. return {
  713. id: item.id,
  714. title: item.title,
  715. type: item.type,
  716. createId: item.createId,
  717. makeMethod: item.makeMethod,
  718. used: item.used,
  719. };
  720. });
  721. },
  722. async getCourses() {
  723. if (!this.examTask.teachingRoomId || !this.examTask.examId) return;
  724. const res = await courseQuery({
  725. teachingRoomId: this.examTask.teachingRoomId,
  726. examId: this.examTask.examId,
  727. });
  728. const teachingCourseIds = res.teachCourseList.map((item) => item.id);
  729. this.courses = [
  730. {
  731. label: "课程管理",
  732. options: res.teachCourseList,
  733. },
  734. {
  735. label: "开课学院",
  736. options: res.basicCourseList.filter(
  737. (item) => !teachingCourseIds.includes(item.id)
  738. ),
  739. },
  740. ];
  741. },
  742. semesterChange(val) {
  743. this.examTask.paperName = val.name;
  744. },
  745. examChange(val) {
  746. if (!val.id) return;
  747. this.examTask.examModel = val.examModel;
  748. this.examTask.category = val.category;
  749. // this.clearTaskData();
  750. this.examTask.courseId = "";
  751. this.getCourses();
  752. this.courseChange();
  753. },
  754. courseChange(val) {
  755. if (val) {
  756. const courseList = this.courses.map((item) => item.options).flat();
  757. const course = courseList.find((item) => item.id === val);
  758. this.examTask.courseName = course.name;
  759. } else {
  760. this.examTask.courseName = "";
  761. }
  762. this.clearTaskData();
  763. this.updateTaskInfo({ infoExamTask: this.examTask });
  764. },
  765. clearTaskData() {
  766. this.paperAttachments = [];
  767. this.addAtachment();
  768. this.cards = [];
  769. this.getCardList();
  770. },
  771. updateExamTaskDetail() {
  772. if (this.examTask.openAb) {
  773. this.paperAttachments.forEach((paperAttachment) => {
  774. const aAttachment = paperAttachment.paperAttachmentIds[0];
  775. Object.assign(paperAttachment.paperAttachmentIds[1], {
  776. cardId: aAttachment.cardId,
  777. cardType: aAttachment.cardType,
  778. createMethod: aAttachment.createMethod,
  779. cardTitle: aAttachment.cardTitle,
  780. used: aAttachment.used,
  781. createId: aAttachment.createId,
  782. });
  783. });
  784. }
  785. this.examTaskDetail = this.paperAttachments.map((item) => {
  786. return {
  787. ...item,
  788. paperAttachmentIds: JSON.stringify(item.paperAttachmentIds),
  789. };
  790. });
  791. },
  792. async examAndRoomChange() {
  793. this.updateTaskInfo({ infoExamTask: this.examTask });
  794. const { examId, teachingRoomId } = this.examTask;
  795. if (examId && teachingRoomId) {
  796. const examPrintPlan = await examConfigByExamIdOrgId({
  797. examId,
  798. orgId: teachingRoomId,
  799. });
  800. this.examTask.cardRuleId = examPrintPlan.cardRuleId;
  801. this.examTask.review = examPrintPlan.review;
  802. this.examTask.includePaper =
  803. examPrintPlan.printContent.indexOf("PAPER") !== -1;
  804. this.updateExamTaskDetail();
  805. this.updateTaskInfo({
  806. infoExamPrintPlan: Object.assign(
  807. {},
  808. this.infoExamPrintPlan,
  809. examPrintPlan
  810. ),
  811. infoExamTask: this.examTask,
  812. infoExamTaskDetail: this.examTaskDetail,
  813. });
  814. }
  815. },
  816. async cardChange(attachment) {
  817. const card = this.cards.find((item) => item.id === attachment.cardId);
  818. if (!card) return;
  819. attachment.cardType = card.type;
  820. attachment.createMethod = card.createMethod;
  821. attachment.cardTitle = card.title;
  822. attachment.used = card.used;
  823. attachment.createId = card.createId;
  824. },
  825. updateAttachmentBFromA(attachment) {
  826. if (!this.examTask.openAb) return;
  827. const pos = this.getAttachmentPos(attachment);
  828. if (!pos) return;
  829. const paperAttachment = this.paperAttachments[pos.pindex];
  830. const aAttachment = paperAttachment.paperAttachmentIds[0];
  831. Object.assign(paperAttachment.paperAttachmentIds[1], {
  832. cardId: aAttachment.cardId,
  833. cardType: aAttachment.cardType,
  834. createMethod: aAttachment.createMethod,
  835. cardTitle: aAttachment.cardTitle,
  836. used: aAttachment.used,
  837. createId: aAttachment.createId,
  838. });
  839. },
  840. async toCreateCard(attachment) {
  841. if (!this.examTask.cardRuleId) {
  842. this.$message.error("题卡规则缺失!");
  843. return;
  844. }
  845. const res = await this.$prompt("请输入题卡名称?", "提示", {
  846. type: "warning",
  847. showInput: true,
  848. inputPlaceholder: "请输入题卡名称",
  849. inputValidator: (val) => {
  850. if (!val) return "请输入题卡名称!";
  851. if (val.length > 50) return "题卡名称不得超过50个字符!";
  852. return true;
  853. },
  854. }).catch(() => {});
  855. if (!res || res.action !== "confirm") return;
  856. this.curAttachment = { ...attachment };
  857. this.$ls.set("prepareTcPCard", {
  858. courseId: this.examTask.courseId,
  859. courseName: this.examTask.courseName,
  860. schoolName: this.$ls.get("schoolName"),
  861. makeMethod: "SELF",
  862. cardName: res.value,
  863. cardRuleId: this.examTask.cardRuleId,
  864. type: "CUSTOM",
  865. createMethod: "STANDARD",
  866. });
  867. this.$refs.ModifyCard.open();
  868. },
  869. toEditCard(attachment) {
  870. this.curAttachment = { ...attachment };
  871. // 这里只允许新建标准专卡
  872. this.$ls.set("prepareTcPCard", {
  873. id: attachment.cardId,
  874. courseId: this.examTask.courseId,
  875. courseName: this.examTask.courseName,
  876. schoolName: this.$ls.get("schoolName"),
  877. makeMethod: "SELF",
  878. cardRuleId: this.examTask.cardRuleId,
  879. type: "CUSTOM",
  880. createMethod: "STANDARD",
  881. });
  882. this.$refs.ModifyCard.open();
  883. },
  884. toViewCard(attachment) {
  885. this.curAttachment = { ...attachment };
  886. this.$refs.CardPreviewDialog.open();
  887. },
  888. async toCopyCard(attachment) {
  889. this.curAttachment = { ...attachment };
  890. const newCardId = await copyCard(
  891. attachment.toCopyCardId,
  892. this.examTask.courseId
  893. );
  894. this.cardModified({ id: newCardId });
  895. },
  896. async cardModified(data) {
  897. // data: {id,title}
  898. if (!data.id) return;
  899. const pos = this.getAttachmentPos(this.curAttachment);
  900. if (!pos) return;
  901. if (this.IS_TIKU_TAB) {
  902. this.paperAttachments[pos.pindex].paperAttachmentIds[
  903. pos.index
  904. ].cardTitle = data.title;
  905. await this.getTkCardList(this.curAttachment);
  906. return;
  907. }
  908. await this.getCardList();
  909. let card = this.cards.find((item) => item.id === data.id);
  910. if (!card) return;
  911. Object.assign(
  912. this.paperAttachments[pos.pindex].paperAttachmentIds[pos.index],
  913. {
  914. cardId: card.id,
  915. cardType: card.type,
  916. createMethod: card.createMethod,
  917. cardTitle: card.title,
  918. used: card.used,
  919. createId: card.createId,
  920. }
  921. );
  922. },
  923. tkCardChange(card, attachment) {
  924. attachment.cardTitle = card.title;
  925. },
  926. async toDeleteCard(card, attachment) {
  927. const confirm = await this.$confirm(
  928. `确定要删除题卡【${card.title}】吗?`,
  929. "提示",
  930. {
  931. type: "warning",
  932. }
  933. ).catch(() => {});
  934. if (confirm !== "confirm") return;
  935. await deleteCard(card.id);
  936. this.$message.success("删除成功!");
  937. if (attachment.cardId === card.id) {
  938. attachment.cardId = null;
  939. attachment.cardTitle = "";
  940. }
  941. if (this.IS_TIKU_TAB) {
  942. attachment.cards = attachment.cards.filter(
  943. (item) => item.id !== card.id
  944. );
  945. } else {
  946. await this.getCardList();
  947. }
  948. },
  949. async checkData() {
  950. const valid = await this.$refs.examTaskComp.validate().catch(() => {});
  951. if (!valid) return Promise.reject();
  952. this.updateExamTaskDetail();
  953. const paperAttachments = this.paperAttachments
  954. .map((item) => item.paperAttachmentIds)
  955. .flat();
  956. if (this.IS_TIKU_TAB) {
  957. const paperValid = !paperAttachments.some((item) => !item.paperId);
  958. if (!paperValid) {
  959. this.$message.error("请完成试卷选择!");
  960. return Promise.reject();
  961. }
  962. const cardValid = !paperAttachments.some((item) => !item.cardId);
  963. if (!cardValid) {
  964. this.$message.error("有试卷类型未选择题卡!");
  965. return Promise.reject();
  966. }
  967. return Promise.resolve(true);
  968. }
  969. // 设置了入库强制包含试卷时,校验试卷是否上传。
  970. // 未设置入库强制包含试卷时,若有试卷上传,则需要上传全部。若无试卷上传,则通过。
  971. if (this.examTask.includePaper) {
  972. const attachmentValid = !paperAttachments.some(
  973. (item) => !item.attachmentId
  974. );
  975. if (!attachmentValid) {
  976. this.$message.error("请完成试卷文件上传!");
  977. return Promise.reject();
  978. }
  979. } else {
  980. const hasUploadPaperAttachments = paperAttachments.filter(
  981. (item) => item.attachmentId
  982. );
  983. if (
  984. hasUploadPaperAttachments.length > 0 &&
  985. hasUploadPaperAttachments.length !== paperAttachments.length
  986. ) {
  987. this.$message.error("有试卷文件未完成上传!");
  988. return Promise.reject();
  989. }
  990. }
  991. // if (!this.paperConfirmAttachments.length) {
  992. // this.$message.error("请上传附件!");
  993. // return;
  994. // }
  995. const cardValid = !paperAttachments.some((item) => !item.cardId);
  996. if (!cardValid) {
  997. this.$message.error("有试卷类型未选择题卡!");
  998. return Promise.reject();
  999. }
  1000. // const usedCards = paperAttachments
  1001. // .filter((item) => item.cardId && item.used)
  1002. // .map((item) => item.name);
  1003. // if (usedCards.length) {
  1004. // this.$message.error(`${usedCards.join()}卷选择的题卡已经被使用过!`);
  1005. // return Promise.reject();
  1006. // }
  1007. return Promise.resolve(true);
  1008. },
  1009. updateData() {
  1010. this.updateExamTaskDetail();
  1011. let data = {
  1012. infoExamTask: this.examTask,
  1013. infoExamTaskDetail: this.examTaskDetail,
  1014. };
  1015. this.updateTaskInfo(data);
  1016. },
  1017. emitRelateInfo(type) {
  1018. this.$emit("relate-info-change", this.getData(), type);
  1019. },
  1020. // select-paper
  1021. getAttachmentPos(attachment) {
  1022. const pindex = this.paperAttachments.findIndex(
  1023. (item) => item.serialNumber === attachment.serialNumber
  1024. );
  1025. if (pindex === -1) return;
  1026. const index = this.paperAttachments[pindex].paperAttachmentIds.findIndex(
  1027. (item) => item.name === attachment.name
  1028. );
  1029. if (index === -1) return;
  1030. return { pindex, index };
  1031. },
  1032. toSelect(attachment) {
  1033. if (!this.examTask.courseId) {
  1034. this.$message.error("请先选择课程!");
  1035. return;
  1036. }
  1037. let comparePaperId = null;
  1038. if (this.examTask.openAb) {
  1039. const pos = this.getAttachmentPos(attachment);
  1040. if (!pos) return;
  1041. const paperAttachment = this.paperAttachments[pos.pindex];
  1042. const otherAttachment = paperAttachment.paperAttachmentIds.find(
  1043. (item) => item.name !== attachment.name
  1044. );
  1045. if (otherAttachment) comparePaperId = otherAttachment.paperId;
  1046. }
  1047. this.curAttachment = {
  1048. ...attachment,
  1049. comparePaperId,
  1050. courseId: this.examTask.courseId,
  1051. examId: this.examTask.examId,
  1052. uuid: this.examTask.uuid,
  1053. };
  1054. this.$refs.SelectTikuPaperDialog.open();
  1055. // this.tikuPaperSelected({ id: "1", name: "paper-name" });
  1056. },
  1057. async tikuPaperSelected(data) {
  1058. this.cardBuildPresetData = {
  1059. examId: this.examTask.examId,
  1060. courseId: this.examTask.courseId,
  1061. courseName: this.examTask.courseName,
  1062. schoolName: this.$ls.get("schoolName"),
  1063. makeMethod: "SELF",
  1064. cardName: "",
  1065. cardRuleId: this.examTask.cardRuleId,
  1066. type: "CUSTOM",
  1067. createMethod: "STANDARD",
  1068. paperId: data.id,
  1069. paperName: data.name,
  1070. uuid: this.examTask.uuid,
  1071. };
  1072. this.$refs.CardBuildDialog.open();
  1073. },
  1074. cardBuildConfirm(data) {
  1075. if (!data.success) {
  1076. this.$message.error(data.message);
  1077. return;
  1078. }
  1079. const pos = this.getAttachmentPos(this.curAttachment);
  1080. if (!pos) return;
  1081. const info = data.data;
  1082. this.curAttachment = {
  1083. ...this.paperAttachments[pos.pindex].paperAttachmentIds[pos.index],
  1084. };
  1085. Object.assign(
  1086. this.paperAttachments[pos.pindex].paperAttachmentIds[pos.index],
  1087. {
  1088. paperId: this.cardBuildPresetData.paperId,
  1089. cardType: this.cardBuildPresetData.type,
  1090. createMethod: this.cardBuildPresetData.createMethod,
  1091. filename: this.cardBuildPresetData.paperName,
  1092. cardId: info.id,
  1093. cardTitle: info.title,
  1094. uuid: info.uuid,
  1095. attachmentId: info.attachmentId,
  1096. paperUrl: info.paperUrl,
  1097. }
  1098. );
  1099. this.getTkCardList(
  1100. this.paperAttachments[pos.pindex].paperAttachmentIds[pos.index]
  1101. );
  1102. },
  1103. toViewPaper(attachment) {
  1104. if (!attachment.paperUrl) return;
  1105. window.open(attachment.paperUrl);
  1106. },
  1107. // exam-task-detail edit
  1108. addAtachment() {
  1109. const serialNumber = this.paperAttachments.length + 1;
  1110. const newAttachment = {
  1111. name: "A",
  1112. serialNumber,
  1113. attachmentId: "",
  1114. filename: "",
  1115. paperId: null,
  1116. paperUrl: null,
  1117. uuid: this.examTask.uuid,
  1118. cardId: "",
  1119. cardType: "",
  1120. createMethod: "",
  1121. cardTitle: "",
  1122. pages: 0,
  1123. used: false,
  1124. createId: null,
  1125. cards: [],
  1126. };
  1127. const paperAttachment = {
  1128. serialNumber,
  1129. paperType: "",
  1130. paperAttachmentIds: [],
  1131. };
  1132. paperAttachment.paperAttachmentIds.push(newAttachment);
  1133. if (this.examTask.openAb) {
  1134. paperAttachment.paperAttachmentIds.push(
  1135. Object.assign(deepCopy(newAttachment), { name: "B" })
  1136. );
  1137. }
  1138. this.paperAttachments.push(paperAttachment);
  1139. },
  1140. deleteAttachment(index) {
  1141. if (this.paperAttachments.length <= 1) {
  1142. this.$message.error("试卷类型数量不得少于1");
  1143. return;
  1144. }
  1145. this.paperAttachments.splice(index, 1);
  1146. this.paperAttachments.forEach((item, itemIndex) => {
  1147. item.serialNumber = itemIndex + 1;
  1148. item.paperAttachmentIds.forEach((attachment) => {
  1149. attachment.serialNumber = item.serialNumber;
  1150. });
  1151. });
  1152. },
  1153. toUpload(attachment) {
  1154. this.curUploadType = "paper";
  1155. this.curAttachment = {
  1156. ...attachment,
  1157. };
  1158. this.$refs.UploadPaperDialog.open();
  1159. },
  1160. toUploadPaperConfirm() {
  1161. if (this.paperConfirmAttachments.length >= 4) return;
  1162. this.curUploadType = "paperConfirm";
  1163. this.curAttachment = {
  1164. ...this.paperConfirmAttachmentId,
  1165. };
  1166. this.$refs.UploadPaperDialog.open();
  1167. },
  1168. uploadConfirm(attachment, uploadType) {
  1169. if (uploadType === "paper") {
  1170. const pos = this.getAttachmentPos(attachment);
  1171. if (!pos) return;
  1172. this.paperAttachments[pos.pindex].paperAttachmentIds.splice(
  1173. pos.index,
  1174. 1,
  1175. {
  1176. ...attachment,
  1177. }
  1178. );
  1179. } else {
  1180. this.paperConfirmAttachments.push(attachment);
  1181. }
  1182. },
  1183. deletePaperConfirmAttachment(data) {
  1184. const index = this.paperConfirmAttachments.findIndex(
  1185. (item) => item.url === data.url
  1186. );
  1187. this.paperConfirmAttachments.splice(index, 1);
  1188. },
  1189. // cardConfirm(data) {
  1190. // this.examTaskDetail = this.$objAssign(this.examTaskDetail, data);
  1191. // },
  1192. // image-preview
  1193. toPreview(index) {
  1194. this.curImageIndex = index;
  1195. this.selectImage(index);
  1196. this.$refs.SimpleImagePreview.open();
  1197. },
  1198. selectImage(index) {
  1199. this.curImage = this.imageAttachments[index];
  1200. },
  1201. toPrevImage() {
  1202. if (this.curImageIndex === 0) {
  1203. this.curImageIndex = this.imageAttachments.length - 1;
  1204. } else {
  1205. this.curImageIndex--;
  1206. }
  1207. this.selectImage(this.curImageIndex);
  1208. },
  1209. toNextImage() {
  1210. if (this.curImageIndex === this.imageAttachments.length - 1) {
  1211. this.curImageIndex = 0;
  1212. } else {
  1213. this.curImageIndex++;
  1214. }
  1215. this.selectImage(this.curImageIndex);
  1216. },
  1217. },
  1218. };
  1219. </script>