CardDesign.vue 18 KB

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