CardDesign.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. <template>
  2. <div class="card-design">
  3. <!-- actions -->
  4. <div class="design-action">
  5. <div class="design-logo">
  6. <h1>
  7. <i class="el-icon-d-arrow-left" title="退出" @click="toExit"></i>
  8. 答题卡制作
  9. </h1>
  10. </div>
  11. <div class="action-part">
  12. <div class="action-part-title"><h2>基本设置</h2></div>
  13. <div class="action-part-body">
  14. <page-prop-edit @init-page="initPageData"></page-prop-edit>
  15. </div>
  16. </div>
  17. <div class="action-part">
  18. <div class="action-part-title"><h2>插入元素</h2></div>
  19. <div class="action-part-body">
  20. <div class="type-list">
  21. <div
  22. v-for="(item, index) in ELEMENT_LIST"
  23. :key="index"
  24. class="type-item"
  25. draggable="true"
  26. @dragstart="dragstart(item)"
  27. >
  28. <el-button><i class="el-icon-plus"></i>{{ item.name }}</el-button>
  29. </div>
  30. <p class="tips-info">提示:拖动插入元素</p>
  31. </div>
  32. </div>
  33. </div>
  34. </div>
  35. <div class="design-main">
  36. <!-- menus -->
  37. <div class="design-control">
  38. <div class="control-left tab-btns">
  39. <el-button
  40. v-for="(page, pageNo) in pages"
  41. :key="pageNo"
  42. :type="curPageNo === pageNo ? 'primary' : 'default'"
  43. @click="swithPage(pageNo)"
  44. >第{{ pageNo + 1 }}页</el-button
  45. >
  46. </div>
  47. <div class="control-right">
  48. <el-button
  49. type="success"
  50. :loading="isSubmit"
  51. :disabled="!pages.length"
  52. @click="toPreview"
  53. >预览</el-button
  54. >
  55. <el-button
  56. v-if="showSaveBtn"
  57. type="primary"
  58. :loading="isSubmit"
  59. :disabled="canSave || !pages.length"
  60. @click="toSave"
  61. >暂存</el-button
  62. >
  63. <el-button type="primary" :loading="isSubmit" @click="toSubmit"
  64. >提交</el-button
  65. >
  66. </div>
  67. </div>
  68. <!-- edit body -->
  69. <div class="design-body">
  70. <div
  71. v-if="curPage.locators"
  72. :class="[
  73. 'page-box',
  74. `page-box-${cardConfig.pageSize}`,
  75. `page-box-${curPageNo % 2}`,
  76. ]"
  77. >
  78. <div
  79. :class="[
  80. 'page-locators',
  81. `page-locators-${curPage.locators.length}`,
  82. ]"
  83. >
  84. <ul
  85. v-for="(locator, iind) in curPage.locators"
  86. :key="iind"
  87. class="page-locator-group"
  88. >
  89. <li
  90. v-for="(elem, eindex) in locator"
  91. :id="elem.id"
  92. :key="eindex"
  93. ></li>
  94. </ul>
  95. </div>
  96. <!-- inner edit area -->
  97. <div class="page-main-inner">
  98. <div
  99. :class="['page-main', `page-main-${curPage.columns.length}`]"
  100. :style="{ margin: `0 -${curPage.columnGap / 2}px` }"
  101. >
  102. <div
  103. v-for="(column, columnNo) in curPage.columns"
  104. :key="columnNo"
  105. class="page-column"
  106. :style="{ padding: `0 ${curPage.columnGap / 2}px` }"
  107. >
  108. <div
  109. :id="[`column-${curPageNo}-${columnNo}`]"
  110. class="page-column-main"
  111. >
  112. <div v-if="column.elements.length" class="page-column-body">
  113. <topic-element-edit
  114. v-for="element in column.elements"
  115. :key="element.id"
  116. class="page-column-element"
  117. :data-h="element.h"
  118. :data="element"
  119. ></topic-element-edit>
  120. </div>
  121. <div v-else class="page-column-body">
  122. <div
  123. v-if="cardConfig.showForbidArea"
  124. class="page-column-forbid-area"
  125. >
  126. <p>该区域严禁作答</p>
  127. </div>
  128. </div>
  129. </div>
  130. <page-number
  131. type="text"
  132. :total="pages.length * 2"
  133. :current="curPageNo * 2 + columnNo + 1"
  134. ></page-number>
  135. </div>
  136. </div>
  137. </div>
  138. <!-- side edit erea -->
  139. <div class="page-main-side">
  140. <free-element-preview
  141. v-for="element in curPage.sides"
  142. :key="element.id"
  143. class="page-side-element"
  144. :data="element"
  145. ></free-element-preview>
  146. </div>
  147. <!-- outer edit area -->
  148. <div class="page-main-outer">
  149. <page-number
  150. type="rect"
  151. :total="pages.length"
  152. :current="curPageNo + 1"
  153. ></page-number>
  154. </div>
  155. </div>
  156. </div>
  157. </div>
  158. <!-- all topics -->
  159. <div class="topic-list">
  160. <div :class="['page-box', `page-box-${cardConfig.pageSize}`]">
  161. <div class="page-main-inner">
  162. <div
  163. :class="['page-main', `page-main-${cardConfig.columnNumber}`]"
  164. :style="{ margin: `0 -${cardConfig.columnGap / 2}px` }"
  165. >
  166. <div
  167. class="page-column"
  168. :style="{ padding: `0 ${cardConfig.columnGap / 2}px` }"
  169. >
  170. <div id="topic-column" class="page-column-main">
  171. <div class="page-column-body">
  172. <!-- topic element -->
  173. <topic-element-preview
  174. v-for="element in topics"
  175. :key="element.id"
  176. class="page-column-element"
  177. :data="element"
  178. ></topic-element-preview>
  179. </div>
  180. </div>
  181. </div>
  182. </div>
  183. </div>
  184. </div>
  185. </div>
  186. <!-- element-prop-edit -->
  187. <element-prop-edit ref="ElementPropEdit"></element-prop-edit>
  188. <!-- right-click-menu -->
  189. <right-click-menu @inset-topic="insetNewTopic"></right-click-menu>
  190. </div>
  191. </template>
  192. <script>
  193. import { mapState, mapMutations, mapActions } from "vuex";
  194. import {
  195. getElementModel,
  196. getCardHeadModel,
  197. ELEMENT_LIST,
  198. TOPIC_LIST,
  199. } from "../elementModel";
  200. import { CARD_VERSION } from "../enumerate";
  201. import TopicElementEdit from "./TopicElementEdit";
  202. import TopicElementPreview from "./TopicElementPreview";
  203. import FreeElementPreview from "./FreeElementPreview";
  204. import PagePropEdit from "./PagePropEdit";
  205. import ElementPropEdit from "./ElementPropEdit";
  206. import RightClickMenu from "./RightClickMenu";
  207. import PageNumber from "./PageNumber";
  208. import { getPageInitElements } from "../pageModel";
  209. // test auto build
  210. import initPaperJson from "../autoBuild/paperJson.json";
  211. import { buildPaperCard } from "../autoBuild/paperCard";
  212. export default {
  213. name: "CardDesign",
  214. components: {
  215. TopicElementEdit,
  216. TopicElementPreview,
  217. FreeElementPreview,
  218. PagePropEdit,
  219. ElementPropEdit,
  220. RightClickMenu,
  221. PageNumber,
  222. },
  223. props: {
  224. content: {
  225. type: Object,
  226. default() {
  227. return {
  228. pages: [],
  229. cardConfig: {},
  230. paperParams: {},
  231. };
  232. },
  233. },
  234. showSaveBtn: {
  235. type: Boolean,
  236. default: true,
  237. },
  238. },
  239. data() {
  240. return {
  241. ELEMENT_LIST,
  242. TOPIC_LIST,
  243. topicList: [],
  244. columnWidth: 0,
  245. isSubmit: false,
  246. canSave: false,
  247. };
  248. },
  249. computed: {
  250. ...mapState("card", [
  251. "cardConfig",
  252. "pageDefaultElems",
  253. "topics",
  254. "pages",
  255. "paperParams",
  256. "curElement",
  257. "curPage",
  258. "curPageNo",
  259. ]),
  260. },
  261. mounted() {
  262. this.initCard();
  263. },
  264. beforeDestroy() {
  265. this.initState();
  266. },
  267. methods: {
  268. ...mapMutations("card", [
  269. "addPage",
  270. "setCurPage",
  271. "setCurElement",
  272. "setCardConfig",
  273. "setOpenElementEditDialog",
  274. "setCurDragElement",
  275. "setPages",
  276. "setTopics",
  277. "setInsetTarget",
  278. "setPageDefaultElems",
  279. "initState",
  280. ]),
  281. ...mapActions("card", [
  282. "resetTopicSeries",
  283. "removePage",
  284. "addElement",
  285. "modifyCardHead",
  286. "modifyElement",
  287. "rebuildPages",
  288. "initTopicsFromPages",
  289. ]),
  290. async initCard() {
  291. const { cardConfig, pages } = this.content;
  292. this.setCardConfig(cardConfig);
  293. const pageDefaultElems = getPageInitElements(cardConfig);
  294. this.setPageDefaultElems(pageDefaultElems);
  295. if (pages && pages.length) {
  296. this.setPages(pages);
  297. this.initTopicsFromPages();
  298. this.resetTopicSeries();
  299. this.setCurPage(0);
  300. } else {
  301. this.initPageData();
  302. }
  303. this.addWatch();
  304. },
  305. initPageData() {
  306. const elements = buildPaperCard(initPaperJson);
  307. this.setTopics([...this.pageDefaultElems.elements, ...elements]);
  308. // this.setTopics(this.pageDefaultElems.elements || []);
  309. this.$nextTick(() => {
  310. this.rebuildPages();
  311. this.setCurPage(0);
  312. });
  313. },
  314. addNewTopic(item) {
  315. let element = getElementModel(item.type);
  316. element.w = document.getElementById("topic-column").offsetWidth;
  317. this.setCurElement(element);
  318. this.$refs.ElementPropEdit.open();
  319. // to elementPropEdit/ElementPropEdit open topic edit dialog
  320. },
  321. insetNewTopic({ id, type }) {
  322. console.log(id, type);
  323. this.setInsetTarget({ id, type });
  324. if (type === "FILL_QUESTION") {
  325. this.topicList = this.TOPIC_LIST;
  326. } else {
  327. this.topicList = this.TOPIC_LIST.filter(
  328. (item) => item.type !== "FILL_QUESTION"
  329. );
  330. }
  331. this.$refs.TopicSelectDialog.open();
  332. },
  333. // 元件编辑
  334. dragstart(element) {
  335. this.setCurDragElement(getElementModel(element.type));
  336. },
  337. addWatch() {
  338. this.$watch("cardConfig", (val) => {
  339. const element = getCardHeadModel(val);
  340. this.modifyCardHead(element);
  341. this.$nextTick(() => {
  342. this.rebuildPages();
  343. });
  344. });
  345. },
  346. swithPage(pindex) {
  347. if (this.curPageNo === pindex) return;
  348. this.setCurPage(pindex);
  349. this.setCurElement({});
  350. },
  351. // save
  352. getCardData(htmlContent = "", model = "") {
  353. const data = {
  354. title: this.cardConfig.cardTitle,
  355. content: model,
  356. htmlContent,
  357. };
  358. return data;
  359. },
  360. checkElementCovered() {
  361. let elements = [];
  362. this.pages.forEach((page) => {
  363. page.columns.forEach((column) => {
  364. column.elements.forEach((element) => {
  365. if (element.isCovered) {
  366. elements.push(element.id);
  367. }
  368. });
  369. });
  370. });
  371. return elements.length;
  372. },
  373. checkCardValid() {
  374. if (!this.cardConfig.cardTitle) {
  375. this.$message.error("题卡标题不能为空!");
  376. this.setCurPageNo(0);
  377. setTimeout(() => {
  378. document.getElementById("cardTitleInput").focus();
  379. });
  380. return false;
  381. }
  382. // if (!this.cardConfig.cardDesc) {
  383. // this.$message.error("题卡描述信息不能为空!");
  384. // this.setCurPage(0);
  385. // setTimeout(() => {
  386. // document.getElementById("cardDescInput").focus();
  387. // });
  388. // return false;
  389. // }
  390. if (this.checkElementCovered()) {
  391. this.$message.error("题卡中存在被遮挡的元件,请注意调整!");
  392. return false;
  393. }
  394. return true;
  395. },
  396. getCardJson() {
  397. // 防止页面未渲染完成,各试题高度未及时更新,保存数据有误的问题
  398. return new Promise((resolve) => {
  399. setTimeout(() => {
  400. const data = JSON.stringify(
  401. {
  402. version: CARD_VERSION,
  403. cardType: "STANDARD",
  404. cardConfig: this.cardConfig,
  405. paperParams: this.paperParams,
  406. pages: this.pages,
  407. },
  408. (k, v) => (k.startsWith("_") ? undefined : v)
  409. );
  410. resolve(data);
  411. }, 100);
  412. });
  413. },
  414. async toPreview() {
  415. if (this.isSubmit) return;
  416. this.isSubmit = true;
  417. const model = await this.getCardJson();
  418. const datas = this.getCardData("", model);
  419. this.$emit("on-preview", datas);
  420. },
  421. async toSave() {
  422. if (!this.checkCardValid()) return;
  423. if (this.isSubmit) return;
  424. this.isSubmit = true;
  425. const model = await this.getCardJson();
  426. const datas = this.getCardData("", model);
  427. this.$emit("on-save", datas);
  428. },
  429. toSubmit() {
  430. if (this.isSubmit) return;
  431. if (!this.checkCardValid()) return;
  432. this.$emit("on-submit", {
  433. cardConfig: this.cardConfig,
  434. pages: this.pages,
  435. paperParams: this.paperParams,
  436. });
  437. },
  438. toExit() {
  439. this.$emit("on-exit");
  440. },
  441. loading() {
  442. this.isSubmit = true;
  443. },
  444. unloading() {
  445. this.isSubmit = false;
  446. },
  447. },
  448. };
  449. </script>