CardDesign.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578
  1. <template>
  2. <div class="card-design">
  3. <div class="design-top">
  4. <div class="design-top-logo">
  5. <h1><i class="icon icon-back" @click="goback"></i>答题卡制作</h1>
  6. </div>
  7. <div class="design-top-info">
  8. <div class="info-help"><i class="icon icon-help"></i>帮助</div>
  9. </div>
  10. </div>
  11. <div class="design-main">
  12. <!-- menus -->
  13. <div class="design-head">
  14. <div class="design-steps">
  15. <div class="step-item" v-for="(step, index) in steps" :key="index">
  16. <i>{{ index + 1 }}</i>
  17. <span>{{ step }}</span>
  18. </div>
  19. </div>
  20. <div class="design-control">
  21. <div class="control-right">
  22. <el-button
  23. class="btn-white"
  24. @click="toPreview"
  25. :disabled="!pages.length"
  26. >预览</el-button
  27. >
  28. <save-page
  29. @confirm="toSave"
  30. :disabled="!canSave"
  31. ref="SavePage"
  32. ></save-page>
  33. <el-button type="primary" @click="toSubmit">提交</el-button>
  34. </div>
  35. <div class="control-left">
  36. <el-button
  37. v-for="(page, pageNo) in pages"
  38. :key="pageNo"
  39. :class="{ 'btn-white': curPageNo === pageNo }"
  40. @click="swithPage(pageNo)"
  41. >第{{ pageNo + 1 }}页</el-button
  42. >
  43. </div>
  44. </div>
  45. </div>
  46. <!-- actions -->
  47. <div class="design-action">
  48. <div class="action-part">
  49. <div class="action-part-title"><h2>基本设置</h2></div>
  50. <div class="action-part-body">
  51. <page-prop-edit @init-page="initPageData"></page-prop-edit>
  52. </div>
  53. </div>
  54. <div class="action-part">
  55. <div class="action-part-title"><h2>试题配置</h2></div>
  56. <div class="action-part-body">
  57. <div class="type-list">
  58. <div
  59. class="type-item"
  60. v-for="(item, index) in TOPIC_LIST"
  61. :key="index"
  62. >
  63. <el-button @click="addNewTopic(item)"
  64. ><i class="el-icon-plus"></i>{{ item.name }}</el-button
  65. >
  66. </div>
  67. </div>
  68. </div>
  69. </div>
  70. <div class="action-part">
  71. <div class="action-part-title"><h2>插入元素</h2></div>
  72. <div class="action-part-body">
  73. <div class="type-list">
  74. <div
  75. class="type-item"
  76. v-for="(item, index) in ELEMENT_LIST"
  77. :key="index"
  78. draggable="true"
  79. @dragstart="dragstart(item)"
  80. >
  81. <el-button
  82. ><i class="el-icon-plus"></i>{{ item.name }}</el-button
  83. >
  84. </div>
  85. </div>
  86. </div>
  87. <!-- Develop btns -->
  88. <!-- <card-config-prop-edit></card-config-prop-edit> -->
  89. <!-- <br /><br /> -->
  90. <!-- <el-button @click="initCard">新建页面</el-button> -->
  91. </div>
  92. <div class="action-part">
  93. <div class="action-part-title"><h2>阅卷参数</h2></div>
  94. <div class="action-part-body">
  95. <el-button type="primary" @click="modifyParams"
  96. >上传阅卷参数<span class="color-danger"
  97. >({{ paperParams["pageSumScore"] || 0 }}分)</span
  98. ></el-button
  99. >
  100. </div>
  101. </div>
  102. </div>
  103. <!-- edit body -->
  104. <div class="design-body">
  105. <template v-for="(page, pageNo) in pages">
  106. <div
  107. :class="['page-box', `page-box-${pageNo % 2}`]"
  108. v-if="curPageNo === pageNo"
  109. :key="pageNo"
  110. >
  111. <div
  112. :class="[
  113. 'page-locators',
  114. `page-locators-${page.locators.length}`
  115. ]"
  116. >
  117. <ul
  118. class="page-locator-group"
  119. v-for="(locator, iind) in page.locators"
  120. :key="iind"
  121. >
  122. <li
  123. v-for="(elem, eindex) in locator"
  124. :key="eindex"
  125. :id="elem.id"
  126. ></li>
  127. </ul>
  128. </div>
  129. <!-- inner edit area -->
  130. <div class="page-main-inner">
  131. <div
  132. :class="['page-main', `page-main-${page.columns.length}`]"
  133. :style="{ margin: `0 -${page.columnGap / 2}px` }"
  134. >
  135. <div
  136. class="page-column"
  137. v-for="(column, columnNo) in page.columns"
  138. :key="columnNo"
  139. :style="{ padding: `0 ${page.columnGap / 2}px` }"
  140. >
  141. <div
  142. class="page-column-main"
  143. :id="[`column-${pageNo}-${columnNo}`]"
  144. >
  145. <div class="page-column-body" v-if="column.elements.length">
  146. <topic-element-edit
  147. class="page-column-element"
  148. :data-h="element.h"
  149. v-for="element in column.elements"
  150. :key="element.id"
  151. :data="element"
  152. ></topic-element-edit>
  153. </div>
  154. <div class="page-column-body" v-else>
  155. <div
  156. class="page-column-forbid-area"
  157. v-if="cardConfig.showForbidArea"
  158. >
  159. <p>该区域严禁作答</p>
  160. </div>
  161. </div>
  162. </div>
  163. </div>
  164. </div>
  165. </div>
  166. <!-- outer edit area -->
  167. <div class="page-main-outer">
  168. <page-number
  169. type="rect"
  170. :total="pages.length"
  171. :current="pageNo + 1"
  172. ></page-number>
  173. <page-number
  174. type="text"
  175. :total="pages.length"
  176. :current="pageNo + 1"
  177. ></page-number>
  178. </div>
  179. </div>
  180. </template>
  181. </div>
  182. <!-- design other pages -->
  183. <div class="design-other-pages">
  184. <template v-for="(page, pageNo) in pages">
  185. <div
  186. :class="['page-box', `page-box-${pageNo % 2}`]"
  187. v-if="curPageNo !== pageNo"
  188. :key="pageNo"
  189. >
  190. <div
  191. :class="[
  192. 'page-locators',
  193. `page-locators-${page.locators.length}`
  194. ]"
  195. >
  196. <ul
  197. class="page-locator-group"
  198. v-for="(locator, iind) in page.locators"
  199. :key="iind"
  200. >
  201. <li
  202. v-for="(elem, eindex) in locator"
  203. :key="eindex"
  204. :id="elem.id"
  205. ></li>
  206. </ul>
  207. </div>
  208. <!-- inner edit area -->
  209. <div class="page-main-inner">
  210. <div
  211. :class="['page-main', `page-main-${page.columns.length}`]"
  212. :style="{ margin: `0 -${page.columnGap / 2}px` }"
  213. >
  214. <div
  215. class="page-column"
  216. v-for="(column, columnNo) in page.columns"
  217. :key="columnNo"
  218. :style="{ padding: `0 ${page.columnGap / 2}px` }"
  219. >
  220. <div
  221. class="page-column-main"
  222. :id="[`column-${pageNo}-${columnNo}`]"
  223. >
  224. <div class="page-column-body">
  225. <topic-element-edit
  226. class="page-column-element"
  227. v-for="(element, eindex) in column.elements"
  228. :key="eindex"
  229. :data="element"
  230. ></topic-element-edit>
  231. </div>
  232. </div>
  233. </div>
  234. </div>
  235. </div>
  236. </div>
  237. </template>
  238. </div>
  239. </div>
  240. <!-- element-prop-edit -->
  241. <element-prop-edit></element-prop-edit>
  242. <!-- right-click-menu -->
  243. <right-click-menu></right-click-menu>
  244. <!-- card-head-sample -->
  245. <card-head-sample v-if="pages.length"></card-head-sample>
  246. <!-- card-view-frame -->
  247. <div class="design-preview-frame" v-if="cardPreviewUrl">
  248. <iframe :src="cardPreviewUrl" frameborder="0"></iframe>
  249. </div>
  250. <!-- paper-params -->
  251. <paper-params
  252. :pages="pages"
  253. :paper-params="paperParams"
  254. @confirm="paperParamsModified"
  255. ref="PaperParams"
  256. ></paper-params>
  257. </div>
  258. </template>
  259. <script>
  260. import { mapState, mapMutations, mapActions } from "vuex";
  261. import {
  262. cardConfigInfos,
  263. cardDetailEdit,
  264. cardTempDetail,
  265. saveCard,
  266. submitCard
  267. } from "../api";
  268. import {
  269. getElementModel,
  270. getCardHeadModel,
  271. getNewPage,
  272. getTopicModel,
  273. ELEMENT_LIST,
  274. TOPIC_LIST
  275. } from "../elementModel";
  276. import { transformField, getAOrBSystem } from "../enumerate";
  277. // import CardConfigPropEdit from "../components/CardConfigPropEdit";
  278. import TopicElementEdit from "../components/TopicElementEdit";
  279. import PagePropEdit from "../components/PagePropEdit";
  280. import ElementPropEdit from "../components/elementPropEdit/ElementPropEdit";
  281. import RightClickMenu from "../components/RightClickMenu";
  282. import CardHeadSample from "../components/elementEdit/CardHeadSample";
  283. import SavePage from "../components/SavePage";
  284. import PageNumber from "../components/pageInfo/PageNumber";
  285. import PaperParams from "../components/PaperParams";
  286. export default {
  287. name: "card-design",
  288. components: {
  289. // CardConfigPropEdit,
  290. TopicElementEdit,
  291. PagePropEdit,
  292. ElementPropEdit,
  293. RightClickMenu,
  294. CardHeadSample,
  295. SavePage,
  296. PageNumber,
  297. PaperParams
  298. },
  299. data() {
  300. return {
  301. cardId: this.$route.params.cardId || this.$ls.get("cardId"),
  302. cardDetailId: this.$ls.get("cardDetailId"),
  303. prepareTcPCard: this.$ls.get("prepareTcPCard", {}),
  304. ELEMENT_LIST,
  305. TOPIC_LIST,
  306. steps: ["添加标题", "基本设置", "试题配置", "设置阅卷参数", "预览生成"],
  307. columnWidth: 0,
  308. cardPreviewUrl: "",
  309. canSave: true
  310. };
  311. },
  312. computed: {
  313. ...mapState("card", [
  314. "cardConfig",
  315. "pages",
  316. "paperParams",
  317. "curElement",
  318. "curPageNo",
  319. "topicNos"
  320. ]),
  321. isEdit() {
  322. return !!this.cardId;
  323. }
  324. },
  325. mounted() {
  326. this.initCard();
  327. this.registWindowSubmit();
  328. },
  329. methods: {
  330. ...mapMutations("card", [
  331. "addPage",
  332. "setCurPageNo",
  333. "setCurElement",
  334. "setCardConfig",
  335. "setOpenElementEditDialog",
  336. "setCurDragElement",
  337. "setPages",
  338. "setPaperParams",
  339. "initTopicNos",
  340. "initState"
  341. ]),
  342. ...mapActions("card", [
  343. "removePage",
  344. "addElement",
  345. "modifyCardHead",
  346. "modifyElement",
  347. "rebuildPages"
  348. ]),
  349. async initCard() {
  350. await this.getCardConfig();
  351. if (this.isEdit) {
  352. this.getCardTempDetail();
  353. } else {
  354. this.initPageData();
  355. }
  356. this.addWatch();
  357. },
  358. async getCardTempDetail() {
  359. const detData = await cardDetailEdit(this.cardId);
  360. const tempData = await cardTempDetail(this.cardId);
  361. this.canSave = !detData.operateStatus;
  362. this.prepareTcPCard = Object.assign(this.prepareTcPCard, detData);
  363. // 可能存在题卡内容没有记录的情况
  364. if (tempData) {
  365. const cont = JSON.parse(tempData.content);
  366. this.cardDetailId = tempData.id;
  367. this.setPages(cont.pages);
  368. this.setCardConfig(cont.cardConfig);
  369. this.setPaperParams(cont.paperParams);
  370. this.initTopicNos();
  371. } else {
  372. // 没有题卡内容时,直接创建新的内容
  373. this.setCardConfig({ cardName: detData.title });
  374. this.initPageData();
  375. }
  376. },
  377. initPageData() {
  378. this.addNewPage();
  379. this.addNewPage();
  380. this.$nextTick(() => {
  381. this.modifyCardHead({
  382. ...getCardHeadModel(this.cardConfig)
  383. });
  384. });
  385. },
  386. async getCardConfig() {
  387. const data = await cardConfigInfos();
  388. if (!data) {
  389. this.$message.error("请先配置题卡规则!");
  390. return;
  391. }
  392. const aOrBSystem = getAOrBSystem(this.prepareTcPCard);
  393. let config = {
  394. ...transformField(data),
  395. pageSize: "A3",
  396. columnNumber: 2,
  397. columnGap: 20,
  398. showForbidArea: true,
  399. cardName: ""
  400. };
  401. if (aOrBSystem !== null) config.aOrBSystem = aOrBSystem;
  402. config.aOrB = !!config["aOrBSystem"];
  403. this.setCardConfig(config);
  404. },
  405. addNewTopic(item) {
  406. let element = getTopicModel(item.type);
  407. element.w = document.getElementById("column-0-0").offsetWidth;
  408. this.setCurElement(element);
  409. this.setOpenElementEditDialog(true);
  410. // to elementPropEdit/ElementPropEdit open topic edit dialog
  411. },
  412. addNewPage() {
  413. const page = getNewPage(this.pages.length, this.cardConfig.columnNumber);
  414. this.addPage(page);
  415. },
  416. // 元件编辑
  417. dragstart(element) {
  418. this.setCurDragElement(getElementModel(element.type));
  419. },
  420. addWatch() {
  421. this.$watch("cardConfig", val => {
  422. const element = getCardHeadModel(val);
  423. this.modifyCardHead(element);
  424. this.$nextTick(() => {
  425. this.rebuildPages();
  426. });
  427. });
  428. },
  429. // 操作
  430. async toPreview() {
  431. const result = await this.save();
  432. if (!result) return;
  433. window.open(
  434. this.getRouterPath({
  435. name: "CardPreview",
  436. params: {
  437. cardId: this.cardId,
  438. viewType: "view"
  439. }
  440. })
  441. );
  442. },
  443. swithPage(pindex) {
  444. if (this.curPageNo === pindex) return;
  445. this.setCurPageNo(pindex);
  446. this.setCurElement({});
  447. },
  448. // paper-params
  449. modifyParams() {
  450. this.$refs.PaperParams.open();
  451. },
  452. paperParamsModified(paperParams) {
  453. this.setPaperParams(paperParams);
  454. },
  455. // save
  456. getCardData(contentTemp = "") {
  457. const tcPCard = this.$objAssign(
  458. {
  459. examId: "",
  460. enablePaperType: "A",
  461. // paperAttachmentId: "",
  462. courseName: "",
  463. courseCode: "",
  464. cardSource: "",
  465. title: "",
  466. id: this.cardId
  467. },
  468. {
  469. ...this.prepareTcPCard,
  470. title: this.cardConfig.cardName,
  471. // todo:A,B最好前端配置一下,以便支持扩展
  472. enablePaperType: this.cardConfig.aOrB ? "A,B" : "A"
  473. }
  474. );
  475. let data = {
  476. tcPCard,
  477. tcPCardDetail: {
  478. content: this.$refs.SavePage.getPageModel(),
  479. subjectiveAttachmentId:
  480. this.paperParams["subjectiveAttachmentId"] || "",
  481. contentTemp
  482. }
  483. };
  484. if (this.cardDetailId) data.tcPCardDetail.id = this.cardDetailId;
  485. if (this.prepareTcPCard.taskId && this.prepareTcPCard.cardSource === "1")
  486. data.tcPExamTaskDetail = {
  487. ...this.prepareTcPCard,
  488. cardId: this.cardId
  489. };
  490. return data;
  491. },
  492. checkElementCovered() {
  493. let elements = [];
  494. this.pages.forEach(page => {
  495. page.columns.forEach(column => {
  496. column.elements.forEach(element => {
  497. if (element.isCovered) {
  498. elements.push(element.id);
  499. }
  500. });
  501. });
  502. });
  503. return elements.length;
  504. },
  505. async save() {
  506. if (!this.cardConfig.cardName) {
  507. this.$message.error("题卡标题不能为空!");
  508. this.setCurPageNo(0);
  509. setTimeout(() => {
  510. document.getElementById("cardNameInput").focus();
  511. });
  512. return false;
  513. }
  514. if (this.checkElementCovered()) {
  515. this.$message.error("题卡中存在被遮挡的元件,请注意调整!");
  516. return false;
  517. }
  518. const result = await saveCard(this.getCardData());
  519. this.cardDetailId = result.cardDetailId;
  520. this.cardId = result.cardId;
  521. this.$ls.set("cardDetailId", this.cardDetailId);
  522. this.$ls.set("cardId", this.cardId);
  523. return true;
  524. },
  525. async toSave() {
  526. const result = await this.save();
  527. if (result) this.$message.success("保存成功!");
  528. },
  529. toSubmit() {
  530. this.$confirm("确定要提交当前题卡吗?", "提示", {
  531. confirmButtonText: "确定",
  532. cancelButtonText: "取消",
  533. type: "warning"
  534. })
  535. .then(async () => {
  536. const result = await this.save();
  537. if (result) {
  538. this.cardPreviewUrl = `/#/card/preview/${this.cardId}/frame`;
  539. }
  540. })
  541. .catch(() => {});
  542. },
  543. registWindowSubmit() {
  544. window.submitCardTemp = async cardContentTemp => {
  545. const result = await submitCard(
  546. this.getCardData(cardContentTemp)
  547. ).catch(() => {});
  548. this.cardPreviewUrl = "";
  549. if (result) {
  550. this.canSave = false;
  551. this.$message.success("提交成功!");
  552. } else {
  553. this.$message.error("提交失败,请重新尝试!");
  554. }
  555. };
  556. }
  557. },
  558. beforeDestroy() {
  559. this.$ls.remove("cardId");
  560. this.$ls.remove("cardDetailId");
  561. this.$ls.remove("prepareTcPCard");
  562. this.initState();
  563. }
  564. };
  565. </script>