PaperTemplateBuild.vue 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861
  1. <template>
  2. <div class="paper-template-build">
  3. <div class="paper-template-build-body">
  4. <div class="margin_top_10">
  5. <el-select
  6. v-model="seqMode"
  7. class="margin-right-10"
  8. @change="seqModeChange"
  9. >
  10. <el-option value="MODE1" label="单题型连续"></el-option>
  11. <el-option value="MODE2" label="客观题整体连续"></el-option>
  12. <el-option value="MODE3" label="按大题独立"></el-option>
  13. <el-option value="MODE5" label="整卷连续"></el-option>
  14. </el-select>
  15. <el-select
  16. v-model="curPaperTemp"
  17. class="margin-right-10"
  18. placeholder="请选择"
  19. value-key="id"
  20. @change="paperTempChange"
  21. >
  22. <el-option
  23. v-for="item in paperTempList"
  24. :key="item.id"
  25. :label="item.name"
  26. :value="item"
  27. >
  28. </el-option>
  29. </el-select>
  30. <el-button type="primary" @click="toDownload">下载试卷</el-button>
  31. </div>
  32. <paper-template-view
  33. ref="PaperTemplateView"
  34. class="preview-body"
  35. :pages="pages"
  36. :page-config="paperTempJson.pageConfig"
  37. ></paper-template-view>
  38. </div>
  39. </div>
  40. </template>
  41. <script>
  42. import PaperTemplateView from "../components/PaperTemplateView.vue";
  43. import { getModel as getRichTextModel } from "../elements/rich-text/model";
  44. import { getModel as getPageModel } from "../elements/page/model";
  45. import { getElementId, randomCode, deepCopy } from "../../card/plugins/utils";
  46. import { calcSum, maxNum } from "@/plugins/utils";
  47. import previewTemp from "../previewTemp";
  48. import { paperDetailInfoApi } from "../../paper/api";
  49. import { paperTemplateListApi, paperPdfDownloadApi } from "../api";
  50. import { downloadByApi } from "@/plugins/download";
  51. // import paperJson from "./data/paper.json";
  52. // import paperTempJson from "./data/paper-temp.json";
  53. const numberToUpperCase = function (val) {
  54. if (val < 1 || val > 26) return;
  55. return String.fromCharCode(64 + val);
  56. };
  57. const checkRichTextHasCont = function (data) {
  58. if (!data) return false;
  59. if (!data.sections || !data.sections.length) return false;
  60. if (!data.sections[0].blocks || !data.sections[0].blocks.length) return false;
  61. return true;
  62. };
  63. export default {
  64. name: "PaperTemplateBuild",
  65. components: { PaperTemplateView },
  66. data() {
  67. return {
  68. paperId: this.$route.params.paperId,
  69. viewType: this.$route.params.viewType,
  70. seqMode: "MODE1",
  71. renderStructList: [],
  72. pages: [],
  73. paperJson: {},
  74. paperTempJson: {
  75. pages: [],
  76. pageConfig: {},
  77. },
  78. maxColumnWidth: 200,
  79. maxColumnHeight: 200,
  80. paperTempList: [],
  81. curPaperTemp: {},
  82. downloading: false,
  83. fieldData: {},
  84. paperStructs: [],
  85. TEXT_INDENT_SIZE: 28,
  86. textIndent: 28,
  87. };
  88. },
  89. mounted() {
  90. if (this.viewType === "frame") {
  91. this.initFrame();
  92. return;
  93. }
  94. this.initData();
  95. },
  96. methods: {
  97. getTextIndexStyle() {
  98. return {
  99. textIndent: `-${this.textIndent}px`,
  100. paddingLeft: `${this.textIndent}px`,
  101. };
  102. },
  103. async initFrame() {
  104. try {
  105. const paperSet = window.parent.paperSet;
  106. if (!paperSet) {
  107. this.emitFrameResult(false, "数据缺失");
  108. return;
  109. }
  110. this.seqMode = paperSet.seqMode;
  111. this.curPaperTemp = paperSet.paperTemp;
  112. await this.getPaperJson();
  113. let paperTempJson = this.curPaperTemp.content
  114. ? JSON.parse(this.curPaperTemp.content)
  115. : { pages: [], pageConfig: {} };
  116. this.paperTempJson = paperTempJson;
  117. this.pages = paperTempJson.pages;
  118. this.updaterFieldInfo();
  119. } catch (error) {
  120. this.emitFrameResult(false, "数据错误");
  121. }
  122. this.$nextTick(async () => {
  123. try {
  124. this.maxColumnWidth =
  125. document.getElementById("column-0-0").offsetWidth;
  126. this.maxColumnHeight =
  127. document.getElementById("column-0-0").offsetHeight - 10;
  128. this.parseRenderStructList();
  129. this.buildPrePages();
  130. } catch (error) {
  131. this.emitFrameResult(false, "构建错误");
  132. }
  133. const loadRes = await this.waitAllImgLoaded().catch(() => {});
  134. if (!loadRes) {
  135. this.emitFrameResult(false, "数据缓存错误");
  136. return;
  137. }
  138. this.$nextTick(() => {
  139. try {
  140. this.buildReleasePages();
  141. } catch (error) {
  142. this.emitFrameResult(false, "构建pdf错误");
  143. }
  144. this.$nextTick(() => {
  145. this.emitFrameResult(true, "", this.getPreviewTemp());
  146. });
  147. });
  148. });
  149. },
  150. emitFrameResult(success = true, errorMsg = "", htmlCont = "") {
  151. window.parent &&
  152. window.parent.submitPaperTemp &&
  153. window.parent.submitPaperTemp({
  154. success,
  155. errorMsg,
  156. htmlCont,
  157. templateId: this.curPaperTemp.id,
  158. });
  159. },
  160. async initData() {
  161. await this.getPaperJson();
  162. await this.getPaperTempList();
  163. if (!this.paperTempList.length) {
  164. this.$message.error("导出模板缺失!");
  165. return;
  166. }
  167. this.paperTempChange(this.paperTempList[0]);
  168. // test--->
  169. // this.paperJson = paperJson;
  170. // this.paperTempJson = paperTempJson;
  171. // this.pages = paperTempJson.pages;
  172. // this.$nextTick(() => {
  173. // this.buildData();
  174. // });
  175. },
  176. async getPaperJson() {
  177. const res = await paperDetailInfoApi({
  178. paperId: this.paperId,
  179. seqMode: this.seqMode,
  180. });
  181. this.paperJson = res.data;
  182. this.resetClozeSerialNo(this.paperJson);
  183. this.fieldData = {
  184. paperName: res.data.name,
  185. courseName: `${res.data.course.name}(${res.data.course.code})`,
  186. totalScore: res.data.totalScore,
  187. rootOrgName: res.data.rootOrgName,
  188. };
  189. this.paperStructs = this.paperJson.paperDetails.map((detail) => {
  190. return {
  191. detailName: detail.name,
  192. questionCount: detail.unitCount,
  193. totalScore: detail.score,
  194. };
  195. });
  196. },
  197. resetClozeSerialNo(paperData) {
  198. const clozeQuestionTypes = ["CLOZE", "BANKED_CLOZE"];
  199. paperData.paperDetails.forEach((detail) => {
  200. detail.paperDetailUnits.forEach((question) => {
  201. if (!clozeQuestionTypes.includes(question.questionType)) return;
  202. question.question.quesBody.sections.forEach((section) => {
  203. section.blocks.forEach((block) => {
  204. if (block.type !== "cloze") return;
  205. block.value =
  206. question.question.subQuestions[block.value - 1].questionSeq;
  207. });
  208. });
  209. });
  210. });
  211. },
  212. async seqModeChange() {
  213. await this.getPaperJson();
  214. this.$nextTick(() => {
  215. this.buildData();
  216. });
  217. },
  218. async getPaperTempList() {
  219. const res = await paperTemplateListApi("PAPER_EXPORT");
  220. this.paperTempList = res.data;
  221. },
  222. paperTempChange(paperTemp) {
  223. // console.log(paperTemp);
  224. this.curPaperTemp = paperTemp;
  225. let paperTempJson = paperTemp.content
  226. ? JSON.parse(paperTemp.content)
  227. : { pages: [], pageConfig: {} };
  228. this.paperTempJson = paperTempJson;
  229. this.pages = paperTempJson.pages;
  230. this.updaterFieldInfo();
  231. this.$nextTick(() => {
  232. this.buildData();
  233. });
  234. },
  235. updaterFieldInfo() {
  236. const VALID_ELEMENTS_FOR_EXTERNAL = ["FIELD_TEXT"];
  237. this.paperTempJson.pages.forEach((page) => {
  238. page.columns.forEach((column) => {
  239. column.elements.forEach((elem) => {
  240. if (elem.type === "PAPER_STRUCT") {
  241. elem.structs = this.paperStructs;
  242. }
  243. if (!elem.elements || !elem.elements.length) return;
  244. elem.elements.forEach((element) => {
  245. if (!VALID_ELEMENTS_FOR_EXTERNAL.includes(element.type)) return;
  246. if (element.type === "FIELD_TEXT" && element.field) {
  247. element.content = this.fieldData[element.field];
  248. }
  249. });
  250. });
  251. });
  252. });
  253. },
  254. async buildData() {
  255. this.maxColumnWidth = document.getElementById("column-0-0").offsetWidth;
  256. this.maxColumnHeight =
  257. document.getElementById("column-0-0").offsetHeight - 10;
  258. this.parseRenderStructList();
  259. this.buildPrePages();
  260. const loadRes = await this.waitAllImgLoaded().catch(() => {});
  261. if (!loadRes) {
  262. this.$message.error("图片加载有误!");
  263. return;
  264. }
  265. this.$nextTick(() => {
  266. this.buildReleasePages();
  267. });
  268. },
  269. parseRenderStructList() {
  270. let renderStructList = [];
  271. this.paperJson.paperDetails.forEach((detail) => {
  272. renderStructList.push(this.parseDetailTitle(detail));
  273. if (checkRichTextHasCont(detail.description)) {
  274. const descData = this.parseTitleOption(detail.description, "");
  275. renderStructList.push(...descData);
  276. }
  277. detail.paperDetailUnits.forEach((question) => {
  278. let questionInfo = question.question;
  279. if (questionInfo.subQuestions && questionInfo.subQuestions.length) {
  280. const bodys = this.parseTitleOption(questionInfo.quesBody, "");
  281. renderStructList.push(...bodys);
  282. const isMatches = this.checkIsMatches(questionInfo.questionType);
  283. if (
  284. isMatches &&
  285. questionInfo.quesOptions &&
  286. questionInfo.quesOptions.length
  287. ) {
  288. questionInfo.quesOptions.forEach((op) => {
  289. const obodys = this.parseTitleOption(
  290. op.optionBody,
  291. `${numberToUpperCase(op.number)}、`,
  292. true,
  293. {
  294. contType: "option",
  295. textStyles: { paddingLeft: `${this.textIndent}px` },
  296. }
  297. );
  298. renderStructList.push(...obodys);
  299. });
  300. }
  301. // 选词填空不展示小题
  302. if (questionInfo.questionType === "BANKED_CLOZE") {
  303. renderStructList.push(this.parseLineGap());
  304. return;
  305. }
  306. questionInfo.subQuestions.forEach((sq, sqindex) => {
  307. sq.subNumber = sqindex + 1;
  308. if (isMatches) sq.quesOptions = []; // 选词填空、段落匹配小题中不展示选项
  309. const contents = this.parseSimpleQuestion(sq, false);
  310. renderStructList.push(...contents);
  311. renderStructList.push(this.parseLineGap());
  312. });
  313. } else {
  314. questionInfo.number = question.number;
  315. const datas = this.parseSimpleQuestion(questionInfo, true);
  316. renderStructList.push(...datas);
  317. renderStructList.push(this.parseLineGap());
  318. }
  319. });
  320. });
  321. // 去掉最后一题的间隔行
  322. // console.log(renderStructList);
  323. this.renderStructList = renderStructList.slice(0, -1);
  324. },
  325. getRichStruct(blocks) {
  326. return {
  327. sections: [
  328. {
  329. blocks: [...blocks],
  330. },
  331. ],
  332. };
  333. },
  334. transformRichJson(richJson) {
  335. if (!richJson || !richJson.sections) return [];
  336. let contents = [];
  337. let curBlock = [];
  338. const checkNeedSplitSection = (block) => {
  339. if (block.type !== "image") return false;
  340. if (block.param) {
  341. if (block.param.width)
  342. return block.param.width > this.maxColumnWidth / 2;
  343. if (block.param.height) return block.param.height > 150;
  344. }
  345. return true;
  346. };
  347. richJson.sections.forEach((section) => {
  348. section.blocks.forEach((block) => {
  349. if (checkNeedSplitSection(block) && curBlock.length) {
  350. contents.push(this.getRichStruct(curBlock));
  351. curBlock = [];
  352. }
  353. curBlock.push(block);
  354. });
  355. if (curBlock.length) {
  356. contents.push(this.getRichStruct(curBlock));
  357. curBlock = [];
  358. }
  359. });
  360. return contents;
  361. },
  362. changeRichTextCloze(richText) {
  363. return {
  364. sections: richText.sections.map((section) => {
  365. return {
  366. blocks: section.blocks.map((item) => {
  367. if (item.type === "cloze") {
  368. return {
  369. type: "text",
  370. value: "_____",
  371. };
  372. } else {
  373. return { ...item };
  374. }
  375. }),
  376. };
  377. }),
  378. };
  379. },
  380. parseSimpleQuestion(question) {
  381. let contents = [];
  382. let quesBody = question.quesBody;
  383. if (question.questionType === "FILL_BLANK_QUESTION") {
  384. quesBody = this.changeRichTextCloze(quesBody);
  385. }
  386. const tbodys = this.parseTitleOption(
  387. quesBody,
  388. `${question.questionSeq}、`,
  389. true,
  390. {
  391. textStyles: { paddingLeft: `${this.textIndent}px` },
  392. }
  393. );
  394. contents.push(...tbodys);
  395. const hasNobody = !tbodys.length;
  396. if (question.quesOptions && question.quesOptions.length) {
  397. question.quesOptions.forEach((op, oIndex) => {
  398. let noVal = `${numberToUpperCase(op.number)}、`;
  399. if (!hasNobody) {
  400. const obodys = this.parseTitleOption(op.optionBody, noVal, false, {
  401. contType: "option",
  402. textStyles: { paddingLeft: `${this.textIndent}px` },
  403. });
  404. contents.push(...obodys);
  405. return;
  406. }
  407. if (!oIndex) {
  408. // 针对如完形填空的小题做的特殊处理
  409. noVal = `${question.questionSeq}、${noVal}`;
  410. }
  411. this.textIndent = 2 * this.TEXT_INDENT_SIZE;
  412. const obodys = this.parseTitleOption(op.optionBody, noVal, true, {
  413. contType: "option",
  414. textStyles: { paddingLeft: `${this.textIndent}px` },
  415. });
  416. contents.push(...obodys);
  417. this.textIndent = this.TEXT_INDENT_SIZE;
  418. });
  419. }
  420. return contents;
  421. },
  422. parseDetailTitle(data) {
  423. let content = this.getRichStruct([
  424. {
  425. type: "text",
  426. value: `${data.cnNum}、`,
  427. },
  428. {
  429. type: "text",
  430. value: `${data.name}(共${data.unitCount}小题,满分${data.score}分)`,
  431. },
  432. ]);
  433. return getRichTextModel({
  434. styles: { width: "100%", fontWeight: 600 },
  435. content,
  436. });
  437. },
  438. parseTitleOption(
  439. richJson,
  440. noVal,
  441. needIndent = false,
  442. modelData = { textStyles: null, styles: null, contType: "" }
  443. ) {
  444. if (!richJson) return [];
  445. const { textStyles, styles, contType } = modelData;
  446. const bodys = this.transformRichJson(richJson);
  447. return bodys.map((body, index) => {
  448. let presetData = {
  449. content: body,
  450. };
  451. if (contType) presetData.contType = contType;
  452. if (textStyles) presetData.textStyles = textStyles;
  453. if (styles) {
  454. presetData.styles = styles;
  455. } else {
  456. presetData.styles = {
  457. width: contType === "option" ? "auto" : "100%",
  458. };
  459. }
  460. if (index === 0 && noVal) {
  461. let cont = {
  462. type: "text",
  463. value: noVal,
  464. };
  465. if (needIndent) {
  466. cont.param = {
  467. width: this.textIndent,
  468. };
  469. presetData.textStyles = {
  470. ...(presetData.textStyles || {}),
  471. ...this.getTextIndexStyle(),
  472. };
  473. }
  474. body.sections[0].blocks.unshift(cont);
  475. }
  476. return getRichTextModel(presetData);
  477. });
  478. },
  479. parseLineGap() {
  480. return getRichTextModel({
  481. contType: "gap",
  482. styles: { width: "100%", height: "10px" },
  483. content: this.getRichStruct([{ type: "text", value: "" }]),
  484. });
  485. },
  486. checkIsMatches(structType) {
  487. const matchesTypes = ["BANKED_CLOZE", "PARAGRAPH_MATCHING"];
  488. return matchesTypes.includes(structType);
  489. },
  490. buildPrePages() {
  491. let pages = deepCopy(this.paperTempJson.pages);
  492. const firstPageNo = pages.findIndex((p) => p.pageType !== "cover");
  493. pages[firstPageNo].columns[0].texts = [];
  494. pages[firstPageNo].columns[0].texts.push(...this.renderStructList);
  495. this.pages = pages;
  496. },
  497. buildReleasePages() {
  498. this.resetRenderStructSize();
  499. // console.log(this.renderStructList);
  500. this.buildPageAutoPage();
  501. },
  502. resetRenderStructSize() {
  503. let curOptions = [];
  504. this.renderStructList.forEach((elem, eindex) => {
  505. const elemDom = document.getElementById(`rich-text-${elem.id}`);
  506. elem.w = elemDom.offsetWidth;
  507. elem.h = elemDom.offsetHeight;
  508. if (elem.contType !== "option") return;
  509. curOptions.push(elem);
  510. // 全选选项逻辑
  511. const nextElem = this.renderStructList[eindex + 1];
  512. if (nextElem && nextElem.contType === "option") return;
  513. curOptions.forEach((optionElem) => {
  514. optionElem._percent = this.getSizePercent(
  515. optionElem.w,
  516. this.maxColumnWidth
  517. );
  518. });
  519. const optionCount = curOptions.length;
  520. // 奇数选项,全部一行
  521. if (optionCount % 2 > 0) {
  522. curOptions.forEach((optionElem) => {
  523. optionElem._percent = 1;
  524. optionElem.styles.width = "100%";
  525. });
  526. curOptions = [];
  527. return;
  528. }
  529. const percents = curOptions.map((item) => item._percent);
  530. const maxPercent = maxNum(percents);
  531. let aveOptionPercent = 1;
  532. if (optionCount % 4 === 0) {
  533. aveOptionPercent = this.calcAveOptionPercent(maxPercent);
  534. } else {
  535. aveOptionPercent = maxPercent > 0.5 ? 1 : 0.5;
  536. }
  537. curOptions.forEach((optionElem) => {
  538. optionElem._percent = aveOptionPercent;
  539. optionElem.styles.width = aveOptionPercent * 100 + "%";
  540. });
  541. curOptions = [];
  542. });
  543. this.renderStructList.forEach((elem) => {
  544. if (elem.styles.width === "100%") {
  545. this.$set(elem.styles, "display", "block");
  546. }
  547. });
  548. },
  549. buildPageAutoPage() {
  550. let pages = [];
  551. let curPage = null,
  552. curElem = null;
  553. let curColumn = null,
  554. curColumnNo = 0,
  555. curColumnHeight = 0;
  556. let curLinePercent = 0;
  557. let groups = [],
  558. curGroup = [];
  559. // 分组自动分页 选项分组
  560. const getNextElem = () => {
  561. return this.renderStructList.shift();
  562. };
  563. curElem = getNextElem();
  564. while (curElem) {
  565. if (
  566. curElem.contType !== "option" ||
  567. (curElem.contType === "option" && curElem._percent === 1)
  568. ) {
  569. if (curGroup.length) {
  570. groups.push(curGroup);
  571. curGroup = [];
  572. curLinePercent = 0;
  573. }
  574. groups.push([curElem]);
  575. curElem = getNextElem();
  576. } else {
  577. if (curLinePercent + curElem._percent > 1) {
  578. groups.push(curGroup);
  579. curGroup = [];
  580. curLinePercent = 0;
  581. } else {
  582. curGroup.push(curElem);
  583. curLinePercent += curElem._percent;
  584. curElem = getNextElem();
  585. }
  586. }
  587. }
  588. if (curGroup.length) {
  589. groups.push(curGroup);
  590. curGroup = [];
  591. }
  592. const getNextGroup = () => {
  593. return groups.shift();
  594. };
  595. curGroup = getNextGroup();
  596. while (curGroup) {
  597. if (!curPage) {
  598. curPage = this.getNewPageModel(pages.length);
  599. }
  600. if (!curColumn) {
  601. curColumn = curPage.columns[curColumnNo++];
  602. curColumnHeight = this.calcInitColumnHeight(curColumn);
  603. }
  604. let curGroupHeigth =
  605. groups.length === 1
  606. ? curGroup[0].h
  607. : Math.max.apply(
  608. null,
  609. curGroup.map((item) => item.h)
  610. );
  611. if (curGroupHeigth + curColumnHeight > this.maxColumnHeight) {
  612. // 当前栏满了
  613. if (curColumnNo >= curPage.columnNumber) {
  614. // 当前页满了
  615. pages.push(curPage);
  616. curPage = null;
  617. curColumnNo = null;
  618. }
  619. curColumn = null;
  620. curColumnHeight = 0;
  621. } else {
  622. // 当前栏未满
  623. curColumnHeight += curGroupHeigth;
  624. curColumn.texts.push(...curGroup);
  625. curGroup = getNextGroup();
  626. }
  627. }
  628. if (curPage) {
  629. pages.push(curPage);
  630. curPage = null;
  631. }
  632. // 正文部分保证偶数页
  633. if (pages.length % 2) {
  634. pages.push(this.getNewPageModel(pages.length));
  635. }
  636. if (this.paperTempJson.pageConfig.showCover) {
  637. // 封面自动插入反面空白页
  638. let coverPages = deepCopy(
  639. this.paperTempJson.pages.filter((p) => p.pageType === "cover")
  640. );
  641. let coverBackPage = deepCopy(coverPages[0]);
  642. coverBackPage.columns.forEach((column) => {
  643. column.elements = [];
  644. });
  645. let nCoverPages = [];
  646. coverPages.forEach((cpage) => {
  647. nCoverPages.push(cpage);
  648. nCoverPages.push(coverBackPage);
  649. });
  650. pages = [...nCoverPages, ...pages];
  651. }
  652. this.pages = pages;
  653. },
  654. getNewPageModel(pageNo) {
  655. let contentPages = this.paperTempJson.pages.slice(-2);
  656. let pNo = pageNo % 2;
  657. const pageTemp = contentPages[pNo];
  658. let newPage = getPageModel({
  659. ...this.paperTempJson.pageConfig,
  660. pageType: pageTemp.pageType,
  661. });
  662. newPage.sides = pageTemp.sides.map((elem) => {
  663. let nelem = deepCopy(elem);
  664. nelem.id = getElementId();
  665. nelem.key = randomCode();
  666. // if (pNo === 1 && nelem.type === "GUTTER") {
  667. // nelem.direction = "right";
  668. // }
  669. return nelem;
  670. });
  671. newPage.columns.forEach((column) => {
  672. column.texts = [];
  673. });
  674. if (pageNo > 1) return newPage;
  675. newPage.columns.forEach((column, cindex) => {
  676. column.elements = pageTemp.columns[cindex].elements.map((elem) => {
  677. let nelem = deepCopy(elem);
  678. nelem.id = getElementId();
  679. nelem.key = randomCode();
  680. nelem.h = this.getElementHeight(`preview-${elem.id}`);
  681. if (nelem.elements && nelem.elements.length) {
  682. nelem.elements.forEach((celem) => {
  683. celem.id = getElementId();
  684. celem.key = randomCode();
  685. });
  686. }
  687. return nelem;
  688. });
  689. column.texts = [];
  690. });
  691. return newPage;
  692. },
  693. getElementHeight(elementId) {
  694. const dom = document.getElementById(elementId);
  695. return dom ? dom.offsetHeight : 0;
  696. },
  697. calcAveOptionPercent(maxPercent) {
  698. if (maxPercent > 0.5) return 1;
  699. if (maxPercent > 0.25) return 0.5;
  700. return 0.25;
  701. },
  702. calcInitColumnHeight(column) {
  703. return calcSum(column.elements.map((item) => item.h));
  704. },
  705. getSizePercent(size, fullSize) {
  706. const rate = size / fullSize;
  707. if (rate <= 0.25) return 0.25;
  708. if (rate <= 0.5) return 0.5;
  709. return 1;
  710. },
  711. // img
  712. loadImg(url) {
  713. return new Promise((resolve, reject) => {
  714. const img = new Image();
  715. img.onload = function () {
  716. resolve(true);
  717. };
  718. img.onerror = function () {
  719. reject();
  720. };
  721. img.src = url;
  722. });
  723. },
  724. getRichJsonImgUrls(richJson) {
  725. let urls = [];
  726. if (!richJson) return urls;
  727. richJson.sections.forEach((section) => {
  728. section.blocks.forEach((elem) => {
  729. if (elem.type === "image" && elem.value.startsWith("http")) {
  730. urls.push(elem.value);
  731. }
  732. });
  733. });
  734. return urls;
  735. },
  736. async waitAllImgLoaded() {
  737. let imgUrls = [];
  738. this.renderStructList.forEach((item) => {
  739. if (item.contType === "gap") return;
  740. imgUrls.push(...this.getRichJsonImgUrls(item.content));
  741. });
  742. // console.log(imgUrls);
  743. if (!imgUrls.length) return Promise.resolve(true);
  744. const imgLoads = imgUrls.map((item) => this.loadImg(item));
  745. const imgLoadResult = await Promise.all(imgLoads).catch(() => {});
  746. if (imgLoadResult && imgLoadResult.length) {
  747. return Promise.resolve(true);
  748. } else {
  749. return Promise.reject();
  750. }
  751. },
  752. getPreviewTemp() {
  753. const elementDoms =
  754. this.$refs.PaperTemplateView.$el.querySelectorAll(".elem-rich-text");
  755. elementDoms.forEach((eDom) => {
  756. const width = eDom.offsetWidth;
  757. if (eDom.firstChild && eDom.firstChild.nodeName === "DIV") {
  758. eDom.firstChild.style.width = width + "px";
  759. if (
  760. eDom.firstChild.firstChild &&
  761. eDom.firstChild.firstChild.nodeName === "DIV"
  762. )
  763. eDom.firstChild.firstChild.style.width = width + "px";
  764. }
  765. });
  766. return previewTemp(this.$refs.PaperTemplateView.$el.outerHTML);
  767. },
  768. async toDownload() {
  769. const htmlCont = this.getPreviewTemp();
  770. if (this.downloading) return;
  771. this.downloading = true;
  772. const res = await downloadByApi(() => {
  773. return paperPdfDownloadApi({
  774. content: htmlCont,
  775. templateId: this.curPaperTemp.id,
  776. paperId: this.paperId,
  777. });
  778. }).catch((e) => {
  779. this.$message.error(e || "下载失败,请重新尝试!");
  780. });
  781. this.downloading = false;
  782. if (!res) return;
  783. this.$message.success("下载成功!");
  784. },
  785. },
  786. };
  787. </script>
  788. <style>
  789. .paper-template-build {
  790. text-align: center;
  791. }
  792. .paper-template-build-body {
  793. display: inline-block;
  794. text-align: initial;
  795. }
  796. .paper-template-build .page-box {
  797. margin-top: 10px;
  798. margin-bottom: 10px;
  799. }
  800. </style>