TaskPaper.vue 35 KB

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