card.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. import {
  2. getExplainElements,
  3. getFillQuesitonElements,
  4. getFillLineElements,
  5. getCompositionElements,
  6. getNewPage,
  7. getTopicHeadModel,
  8. } from "../elementModel";
  9. import { objAssign, deepCopy, calcSum, getElementId } from "../plugins/utils";
  10. const state = {
  11. cardConfig: {},
  12. curElement: {},
  13. curDragElement: {},
  14. curPage: {},
  15. curPageNo: 0,
  16. pages: [],
  17. topics: [],
  18. topicSeries: [], // 大题顺序号,不排重
  19. topicNoSeries: [], // 大题顺序号,按顺序排重
  20. insetTarget: {}, // 需要在其后面插入大题的大题
  21. openElementEditDialog: false,
  22. };
  23. const mutations = {
  24. setCardConfig(state, cardConfig) {
  25. state.cardConfig = Object.assign({}, state.cardConfig, cardConfig);
  26. },
  27. setCurElement(state, curElement) {
  28. state.curElement = curElement;
  29. },
  30. setCurDragElement(state, curDragElement) {
  31. state.curDragElement = curDragElement;
  32. },
  33. setCurPage(state, curPageNo) {
  34. const pageNo = state.pages[curPageNo] ? curPageNo : 0;
  35. state.curPage = state.pages[pageNo];
  36. state.curPageNo = pageNo;
  37. },
  38. setCurPageNo(state, curPageNo) {
  39. state.curPageNo = curPageNo;
  40. },
  41. setPages(state, pages) {
  42. state.pages = pages;
  43. },
  44. setTopics(state, topics) {
  45. state.topics = topics;
  46. },
  47. setTopicSeries(state, topicSeries) {
  48. state.topicSeries = topicSeries;
  49. },
  50. setTopicNoSeries(state, topicNoSeries) {
  51. state.topicNoSeries = topicNoSeries;
  52. },
  53. setInsetTarget(state, insetTarget) {
  54. state.insetTarget = insetTarget;
  55. },
  56. addPage(state, page) {
  57. state.pages.push(page);
  58. },
  59. modifyPage(state, page) {
  60. state.pages.splice(page._pageNo, 1, page);
  61. },
  62. setOpenElementEditDialog(state, openElementEditDialog) {
  63. state.openElementEditDialog = openElementEditDialog;
  64. },
  65. initState(state) {
  66. state.cardConfig = {};
  67. state.curElement = {};
  68. state.curDragElement = {};
  69. state.curPage = {};
  70. state.curPageNo = 0;
  71. state.pages = [];
  72. state.topics = [];
  73. state.topicSeries = [];
  74. state.topicNoSeries = [];
  75. state.insetTarget = {};
  76. state.openElementEditDialog = false;
  77. },
  78. };
  79. const fetchElementPositionInfos = (element, topics) => {
  80. return topics.findIndex((item) => item.id === element.id);
  81. };
  82. const fetchAllRelateParentElementPositionInfos = (parentElement, topics) => {
  83. // 当为解答题时,parentElement传入的值是EXPLAIN
  84. let postionInfos = [];
  85. topics.forEach((item, eindex) => {
  86. if (item["parent"] && item.parent.id === parentElement.id) {
  87. let pos = { _elementNo: eindex };
  88. if (parentElement.type === "EXPLAIN") {
  89. pos.serialNumber = item.serialNumber;
  90. }
  91. postionInfos.push(pos);
  92. }
  93. });
  94. return postionInfos;
  95. };
  96. const fetchFirstSubjectiveTopicPositionInfo = (topics) => {
  97. return topics.findIndex((item) => item.sign === "subjective");
  98. };
  99. const fetchSameSerialNumberChildrenPositionInfo = (
  100. elementChildernElement,
  101. topics
  102. ) => {
  103. let postionInfos = [];
  104. const elementId = elementChildernElement.parent.id;
  105. const serialNumber = elementChildernElement.serialNumber;
  106. topics.forEach((item, eindex) => {
  107. if (
  108. item.parent &&
  109. item.parent.id === elementId &&
  110. item.serialNumber === serialNumber
  111. ) {
  112. postionInfos.push({
  113. _elementNo: eindex,
  114. _elementId: item.id,
  115. });
  116. }
  117. });
  118. return postionInfos;
  119. };
  120. const groupByParams = (datas, paramKey) => {
  121. let elementGroupInfos = [];
  122. for (let i = 0, len = datas.length; i < len; i++) {
  123. if (i === 0 || datas[i][paramKey] !== datas[i - 1][paramKey]) {
  124. elementGroupInfos.push([datas[i]]);
  125. } else {
  126. elementGroupInfos[elementGroupInfos.length - 1].push(datas[i]);
  127. }
  128. }
  129. return elementGroupInfos;
  130. };
  131. const findElementById = (id, topics) => {
  132. let curElement = null;
  133. topics.forEach((element) => {
  134. if (curElement) return;
  135. if (element.id === id) {
  136. curElement = element;
  137. return;
  138. }
  139. if (element["elements"]) {
  140. element["elements"].forEach((elem) => {
  141. if (elem.id === id) curElement = elem;
  142. });
  143. }
  144. });
  145. return curElement;
  146. };
  147. const checkElementisCovered = (id, type) => {
  148. const elementDom = document.getElementById(id);
  149. if (type === "EXPLAIN") {
  150. const elemTitleDome = elementDom.querySelector(".elem-title");
  151. const limitHeight = elemTitleDome
  152. ? elementDom.offsetHeight - elemTitleDome.offsetHeight
  153. : elementDom.offsetHeight;
  154. let elementHeights = [];
  155. elementDom
  156. .querySelector(".elem-explain-elements")
  157. .childNodes.forEach((node) => {
  158. if (!node.className.includes("elem-explain-element")) return;
  159. elementHeights.push(
  160. node.firstChild.offsetHeight + node.firstChild.offsetTop
  161. );
  162. });
  163. return elementHeights.some((item) => item > limitHeight);
  164. }
  165. if (type === "COMPOSITION") {
  166. const elemTitleDome = elementDom.querySelector(".elem-title");
  167. const limitHeight = elemTitleDome
  168. ? elementDom.offsetHeight - elemTitleDome.offsetHeight
  169. : elementDom.offsetHeight;
  170. let elementHeights = [];
  171. elementDom
  172. .querySelector(".elem-composition-elements")
  173. .childNodes.forEach((node) => {
  174. if (!node.className.includes("elem-composition-element")) return;
  175. elementHeights.push(
  176. node.firstChild.offsetHeight + node.firstChild.offsetTop
  177. );
  178. });
  179. return elementHeights.some((item) => item > limitHeight);
  180. }
  181. return elementDom.offsetHeight < elementDom.firstChild.offsetHeight;
  182. };
  183. const createFunc = {
  184. EXPLAIN(element) {
  185. return getExplainElements(element);
  186. },
  187. FILL_QUESTION(element) {
  188. return getFillQuesitonElements(element, state.cardConfig);
  189. },
  190. FILL_LINE(element) {
  191. return getFillLineElements(element);
  192. },
  193. COMPOSITION(element) {
  194. return [getCompositionElements(element)];
  195. },
  196. FORBID_AREA(element) {
  197. return [element];
  198. },
  199. };
  200. const actions = {
  201. initTopicsFromPages({ state, commit }) {
  202. let topics = [];
  203. state.pages.forEach((page) => {
  204. page.columns.forEach((column) => {
  205. column.elements.forEach((element) => {
  206. if (
  207. element.type === "TOPIC_HEAD" ||
  208. (element.type === "CARD_HEAD" && element.isSimple)
  209. )
  210. return;
  211. topics.push(element);
  212. });
  213. });
  214. });
  215. commit("setTopics", topics);
  216. },
  217. actElementById({ state, commit }, id) {
  218. const curElement = findElementById(id, state.topics);
  219. if (!curElement) return;
  220. commit("setCurElement", curElement);
  221. },
  222. resetTopicSeries({ state, commit }) {
  223. let curTopicId = "",
  224. curTopicNo = 0,
  225. topicSeries = [],
  226. topicNoSeries = [];
  227. state.topics.forEach((topic) => {
  228. if (!topic.parent) return;
  229. let data = {
  230. id: topic.parent.id,
  231. topicNo: topic.parent.topicNo,
  232. type: topic.type,
  233. sign: topic.sign,
  234. };
  235. if (curTopicId !== topic.parent.id) {
  236. curTopicId = topic.parent.id;
  237. topicSeries.push(data);
  238. }
  239. if (curTopicNo !== topic.parent.topicNo) {
  240. curTopicNo = topic.parent.topicNo;
  241. topicNoSeries.push(data);
  242. }
  243. });
  244. commit("setTopicSeries", topicSeries);
  245. commit("setTopicNoSeries", topicNoSeries);
  246. },
  247. scrollToElementPage({ state, commit }, element) {
  248. let elementPapeNo = null;
  249. state.pages.forEach((page, pageNo) => {
  250. if (elementPapeNo !== null) return;
  251. page.columns.forEach((column) => {
  252. if (elementPapeNo !== null) return;
  253. column.elements.forEach((elem) => {
  254. if (elem.id === element.id || elem.parent?.id === element.id) {
  255. elementPapeNo = pageNo;
  256. }
  257. });
  258. });
  259. });
  260. // console.log(elementPapeNo);
  261. if (elementPapeNo === null || elementPapeNo === state.curPageNo) return;
  262. let pageTops = state.pages.map((page, pageNo) => {
  263. return document.getElementById(`edit-page-box-${pageNo}`).offsetTop;
  264. });
  265. pageTops = pageTops.map((item) => item - pageTops[0]);
  266. document.getElementById("design-main").scrollTop = pageTops[elementPapeNo];
  267. commit("setCurPageNo", elementPapeNo);
  268. },
  269. // 新增试题 --------------->
  270. addElement({ state, commit, dispatch }, element) {
  271. let pos = null;
  272. // 客观题和主观题分别对待
  273. if (state.insetTarget.id) {
  274. // 存在插入目标元素
  275. if (
  276. state.insetTarget.type === "FILL_QUESTION" &&
  277. element.type !== "FILL_QUESTION"
  278. ) {
  279. pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
  280. } else {
  281. let relateTopicPos = [];
  282. state.topics.forEach((item, index) => {
  283. if (item.parent && item.parent.id === state.insetTarget.id)
  284. relateTopicPos.push(index);
  285. });
  286. pos = relateTopicPos.slice(-1)[0] + 1;
  287. }
  288. } else {
  289. // 不存在插入目标元素
  290. if (element.sign === "objective") {
  291. pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
  292. }
  293. }
  294. let preElements = createFunc[element.type](element);
  295. preElements.forEach((preElement, index) => {
  296. if (pos && pos !== -1) {
  297. state.topics.splice(pos + index, 0, preElement);
  298. } else {
  299. state.topics.push(preElement);
  300. }
  301. });
  302. dispatch("resetTopicSeries");
  303. commit("setInsetTarget", {});
  304. commit("setCurElement", element);
  305. },
  306. addForbidArea({ state, commit }, { element, beforeElementId }) {
  307. const beforeElementPos = fetchElementPositionInfos(
  308. { id: beforeElementId },
  309. state.topics
  310. );
  311. state.topics.splice(beforeElementPos + 1, 0, element);
  312. commit("setCurElement", element);
  313. },
  314. // 修改试题 --------------->
  315. modifyTopic({ state }, element) {
  316. // 单独编辑某个细分题
  317. const pos = fetchElementPositionInfos(element, state.topics);
  318. state.topics.splice(pos, 1, element);
  319. },
  320. modifyComposition({ state }, element) {
  321. const positionInfos = fetchAllRelateParentElementPositionInfos(
  322. element,
  323. state.topics
  324. );
  325. positionInfos.forEach(({ _elementNo }) => {
  326. state.topics[_elementNo].topicNo = element.topicNo;
  327. state.topics[_elementNo].parent = objAssign(
  328. state.topics[_elementNo].parent,
  329. element
  330. );
  331. });
  332. },
  333. modifyExplain({ state }, element) {
  334. // 解答题既是拆分题,又是可复制题
  335. const positionInfos = fetchAllRelateParentElementPositionInfos(
  336. element,
  337. state.topics
  338. );
  339. const elementGroupPosInfos = groupByParams(positionInfos, "serialNumber");
  340. const orgElementCount = elementGroupPosInfos.length;
  341. if (orgElementCount > element.questionsCount) {
  342. // 原小题数多于新小题数,要删除原多于的小题;
  343. let needDeleteInfos = elementGroupPosInfos.splice(
  344. element.questionsCount,
  345. orgElementCount - element.questionsCount
  346. );
  347. needDeleteInfos.reverse().forEach((item) => {
  348. item.reverse().forEach((pos) => {
  349. state.topics.splice(pos._elementNo, 1);
  350. });
  351. });
  352. }
  353. const newElements = getExplainElements(element);
  354. const lastPos = elementGroupPosInfos.slice(-1)[0].slice(-1)[0];
  355. let lastNewElementPos = lastPos._elementNo;
  356. for (let i = 0; i < element.questionsCount; i++) {
  357. if (elementGroupPosInfos[i]) {
  358. elementGroupPosInfos[i].forEach((pos) => {
  359. let child = state.topics[pos._elementNo];
  360. child.serialNumber = i + element.startNumber;
  361. child.parent = { ...element };
  362. child.topicNo = element.topicNo;
  363. });
  364. } else {
  365. state.topics.splice(++lastNewElementPos, 0, newElements[i]);
  366. }
  367. }
  368. },
  369. modifySplitTopic({ state }, element) {
  370. // 非作文题都是拆分题,即同一个题拆分成多个小题展示
  371. const positionInfos = fetchAllRelateParentElementPositionInfos(
  372. element,
  373. state.topics
  374. );
  375. // 缓存已编辑的小题高度信息。
  376. // const elementHeights = positionInfos.map(
  377. // pos => state.topics[pos._elementNo].h
  378. // );
  379. // 删除所有小题
  380. positionInfos.reverse().forEach((pos) => {
  381. state.topics.splice(pos._elementNo, 1);
  382. });
  383. // 创建新的小题元素
  384. const newElements = createFunc[element.type](element);
  385. const pos = positionInfos.pop();
  386. newElements.forEach((newElement, index) => {
  387. // 不再复用缓存高度,修改于2022-03-10 09:23
  388. // newElement.h = Math.max(elementHeights[index] || 0, newElement.h);
  389. state.topics.splice(pos._elementNo + index, 0, newElement);
  390. });
  391. },
  392. modifyElement({ commit, dispatch }, element) {
  393. if (element.type === "COMPOSITION") {
  394. dispatch("modifyComposition", element);
  395. } else if (element.type === "EXPLAIN") {
  396. dispatch("modifyExplain", element);
  397. } else {
  398. dispatch("modifySplitTopic", element);
  399. }
  400. dispatch("resetTopicSeries");
  401. commit("setCurElement", element);
  402. },
  403. modifyCardHead({ state }, element) {
  404. state.topics.splice(0, 1, element);
  405. },
  406. // 修改试题包含元素
  407. modifyElementChild({ state, commit }, element) {
  408. // 修改解答题小题和作文题的子元素
  409. const pos = fetchElementPositionInfos(element.container, state.topics);
  410. const columnElements = state.topics[pos].elements;
  411. const childIndex = columnElements.findIndex(
  412. (item) => item.id === element.id
  413. );
  414. element.id = getElementId();
  415. // 作文题中的多线条和网格元素重新计算高度。
  416. if (element.container.type === "COMPOSITION") {
  417. if (element.type === "LINES") {
  418. element.h = element.lineCount * (element.lineSpacing + 1);
  419. }
  420. if (element.type === "GRIDS") {
  421. if (element.halving) {
  422. const columnWs = {
  423. 2: 703,
  424. 3: 461,
  425. 4: 349,
  426. };
  427. const columnSize =
  428. columnWs[state.cardConfig.columnNumber] / element.columnCount;
  429. element.h = element.rowCount * (columnSize + element.rowSpace) + 1;
  430. } else {
  431. element.h =
  432. element.rowCount * (element.columnSize + element.rowSpace) + 1;
  433. }
  434. }
  435. }
  436. if (childIndex === -1) {
  437. columnElements.push(element);
  438. } else {
  439. columnElements.splice(childIndex, 1, element);
  440. }
  441. commit("setCurElement", element);
  442. },
  443. // 粘贴试题内的元素
  444. pasteExplainElementChild({ state }, { curElement, pasteElement }) {
  445. let element = {
  446. id: curElement.container ? curElement.container.id : curElement.id,
  447. };
  448. const pos = fetchElementPositionInfos(element, state.topics);
  449. if (pos === -1) return;
  450. element = state.topics[pos];
  451. const newElement = Object.assign({}, pasteElement, {
  452. id: getElementId(),
  453. container: {
  454. id: element.id,
  455. type: element.type,
  456. },
  457. });
  458. element.elements.push(newElement);
  459. },
  460. // 删除试题 --------------->
  461. removeElement({ state, commit, dispatch }, element) {
  462. if (!element.parent) {
  463. state.topics = state.topics.filter((item) => item.id !== element.id);
  464. commit("setCurElement", {});
  465. return;
  466. }
  467. const positionInfos = fetchAllRelateParentElementPositionInfos(
  468. element.parent,
  469. state.topics
  470. );
  471. positionInfos.reverse().forEach((pos) => {
  472. state.topics.splice(pos._elementNo, 1);
  473. });
  474. dispatch("resetTopicSeries");
  475. commit("setCurElement", {});
  476. },
  477. // 删除试题包含元素 --------------->
  478. removeElementChild({ state, commit }, element) {
  479. // 删除解答题小题和作文题的子元素
  480. const pos = fetchElementPositionInfos(element.container, state.topics);
  481. const columnElements = state.topics[pos].elements;
  482. const childIndex = columnElements.findIndex(
  483. (item) => item.id === element.id
  484. );
  485. columnElements.splice(childIndex, 1);
  486. commit("setCurElement", {});
  487. },
  488. // 扩展答题区操作 --------------->
  489. copyExplainChildren({ state }, element) {
  490. let curElement = {
  491. id: element.container ? element.container.id : element.id,
  492. };
  493. const pos = fetchElementPositionInfos(curElement, state.topics);
  494. curElement = state.topics[pos];
  495. let newElement = Object.assign({}, curElement, {
  496. id: getElementId(),
  497. elements: [],
  498. h: 200,
  499. isExtend: true,
  500. showTitle: false,
  501. });
  502. state.topics.splice(pos + 1, 0, newElement);
  503. // 更新小题答题区isLast
  504. let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
  505. curElement,
  506. state.topics
  507. );
  508. positionInfos.forEach((pos, pindex) => {
  509. state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
  510. });
  511. },
  512. deleteExplainChildren({ state }, element) {
  513. let curElement = {
  514. id: element.container ? element.container.id : element.id,
  515. };
  516. const curPos = fetchElementPositionInfos(curElement, state.topics);
  517. curElement = state.topics[curPos];
  518. let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
  519. curElement,
  520. state.topics
  521. );
  522. if (positionInfos.length < 2) return;
  523. const pindex = positionInfos.findIndex(
  524. (item) => item._elementId === curElement.id
  525. );
  526. const pos = positionInfos[pindex]._elementNo;
  527. positionInfos.splice(pindex, 1);
  528. const nextPos = positionInfos[0]._elementNo;
  529. // 当删除的是非扩展区域时,则下一个答题区要被设置成非扩展区
  530. if (!curElement.isExtend) {
  531. state.topics[nextPos].isExtend = false;
  532. }
  533. // 当删除的是含有标题答题区时,则需要将下一个答题区开启显示标题。
  534. if (curElement.showTitle) {
  535. state.topics[nextPos].showTitle = true;
  536. }
  537. state.topics.splice(pos, 1);
  538. // 更新小题答题区isLast
  539. positionInfos = fetchSameSerialNumberChildrenPositionInfo(
  540. curElement,
  541. state.topics
  542. );
  543. positionInfos.forEach((pos, pindex) => {
  544. state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
  545. });
  546. },
  547. // 大题顺序操作 --------------->
  548. topicMoveUp({ state, dispatch }, topicId) {
  549. const curTopicPos = state.topicSeries.findIndex(
  550. (item) => item.id === topicId
  551. );
  552. const prevTopicId = state.topicSeries[curTopicPos - 1].id;
  553. let relateTopicPos = [];
  554. state.topics.forEach((item, index) => {
  555. if (item.parent && item.parent.id === topicId) relateTopicPos.push(index);
  556. });
  557. const prevTopicFirstIndex = state.topics.findIndex(
  558. (item) => item.parent && item.parent.id === prevTopicId
  559. );
  560. const relateTopics = state.topics.splice(
  561. relateTopicPos[0],
  562. relateTopicPos.length
  563. );
  564. relateTopics.reverse().forEach((topic) => {
  565. state.topics.splice(prevTopicFirstIndex, 0, topic);
  566. });
  567. dispatch("resetTopicSeries");
  568. },
  569. // 重构页面
  570. resetElementProp({ state }, isResetId = false) {
  571. state.topics.forEach((element) => {
  572. const elementDom = document.getElementById(element.id);
  573. if (elementDom) {
  574. element.h = elementDom.offsetHeight;
  575. element.w = elementDom.offsetWidth;
  576. }
  577. if (isResetId) {
  578. element.id = getElementId();
  579. }
  580. });
  581. },
  582. rebuildPages({ state, commit }) {
  583. const columnNumber = state.cardConfig.columnNumber;
  584. const pageSize = state.cardConfig.pageSize;
  585. // 更新元件最新的高度信息
  586. // 整理所有元件
  587. const cardHeadElement = state.topics[0];
  588. state.topics.forEach((element) => {
  589. const elementDom = document.getElementById(`preview-${element.id}`);
  590. if (elementDom) {
  591. element.h = elementDom.offsetHeight;
  592. element.w = elementDom.offsetWidth;
  593. // 解答题小题与其他题有些区别。
  594. // 其他题都是通过内部子元素自动撑高元件,而解答题则需要手动设置高度。
  595. const ESCAPE_ELEMENTS = ["CARD_HEAD"];
  596. element.isCovered =
  597. !ESCAPE_ELEMENTS.includes(element.type) &&
  598. checkElementisCovered(`preview-${element.id}`, element.type);
  599. }
  600. });
  601. // 动态计算每列可以分配的元件
  602. const columnHeight = document.getElementById("topic-column").offsetHeight;
  603. const simpleCardHeadHeight =
  604. document.getElementById("simple-card-head").offsetHeight;
  605. let pages = [];
  606. let page = {};
  607. let columns = [];
  608. let curColumnElements = [];
  609. let curColumnHeight = 0;
  610. const initCurColumnElements = () => {
  611. curColumnElements = [];
  612. curColumnHeight = 0;
  613. const groupColumnNumber = columnNumber * 2;
  614. // 奇数页第一栏;
  615. if (!(columns.length % groupColumnNumber)) {
  616. // 非第一页奇数页第一栏
  617. if (columns.length) {
  618. const cardHeadSimpleElement = Object.assign({}, cardHeadElement, {
  619. id: getElementId(),
  620. isSimple: true,
  621. h: simpleCardHeadHeight,
  622. });
  623. curColumnElements.push(cardHeadSimpleElement);
  624. curColumnHeight += simpleCardHeadHeight;
  625. } else {
  626. curColumnElements.push(cardHeadElement);
  627. curColumnHeight += cardHeadElement.h;
  628. }
  629. }
  630. };
  631. const checkElementIsCurColumnFirstType = (element) => {
  632. const topicHeadIndex = curColumnElements.findIndex(
  633. (elem) => elem.type === "TOPIC_HEAD" && elem.sign === element.sign
  634. );
  635. return topicHeadIndex === -1;
  636. };
  637. // 放入元素通用流程
  638. const pushElement = (element) => {
  639. // 当前栏中第一个题型之前新增题型头元素(topic-head)。
  640. // 题型头和当前题要组合加入栏中,不可拆分。
  641. let elementList = [element];
  642. if (checkElementIsCurColumnFirstType(element)) {
  643. elementList.unshift(
  644. getTopicHeadModel(
  645. state.cardConfig[`${element.sign}Attention`],
  646. element.sign,
  647. !curColumnElements.length
  648. )
  649. );
  650. }
  651. const elementHeight = calcSum(elementList.map((elem) => elem.h));
  652. if (curColumnHeight + elementHeight > columnHeight) {
  653. // 当前栏第一个元素高度超过一栏时,不拆分,直接放在当前栏。
  654. // 解决可能空栏的情况
  655. const curElementIsFirst = !curColumnElements.length;
  656. if (curElementIsFirst) {
  657. curColumnElements = [...curColumnElements, ...elementList];
  658. curColumnHeight += elementHeight;
  659. } else {
  660. columns.push([...curColumnElements]);
  661. initCurColumnElements();
  662. pushElement(element);
  663. }
  664. } else {
  665. curColumnElements = [...curColumnElements, ...elementList];
  666. curColumnHeight += elementHeight;
  667. }
  668. };
  669. // 批量添加所有元素。
  670. initCurColumnElements();
  671. state.topics.slice(1).forEach((element, eindex) => {
  672. element.elementSerialNo = eindex;
  673. pushElement(element);
  674. });
  675. // 最后一栏的处理。
  676. columns.push([...curColumnElements]);
  677. // 构建pages
  678. columns.forEach((column, cindex) => {
  679. const columnNo = cindex % columnNumber;
  680. if (!columnNo) {
  681. page = getNewPage(pages.length, { pageSize, columnNumber });
  682. }
  683. page.columns[columnNo].elements = column;
  684. if (columnNo + 1 === columnNumber || cindex === columns.length - 1) {
  685. pages.push(deepCopy(page));
  686. }
  687. });
  688. // 保证页面总是偶数页
  689. if (pages.length % 2) {
  690. pages.push(getNewPage(pages.length, { pageSize, columnNumber }));
  691. }
  692. commit("setPages", pages);
  693. commit("setCurPage", state.curPageNo);
  694. },
  695. };
  696. export { fetchSameSerialNumberChildrenPositionInfo, checkElementisCovered };
  697. export default {
  698. namespaced: true,
  699. state,
  700. mutations,
  701. actions,
  702. };