TaskPaper.vue 35 KB

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