store.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. import {
  2. getExplainChildren,
  3. getFillQuesitons,
  4. getFillLine,
  5. getNewPage,
  6. getTopicHead,
  7. getElementId
  8. } from "./elementModel";
  9. import { deepCopy, calcSum } from "@/plugins/utils";
  10. const state = {
  11. curElement: {},
  12. curDragElement: {},
  13. curPageNo: 0,
  14. pages: [],
  15. topicNos: [],
  16. cardConfig: {},
  17. paperParams: {},
  18. openElementEditDialog: false
  19. };
  20. const mutations = {
  21. setCurElement(state, curElement) {
  22. state.curElement = curElement;
  23. },
  24. setCurDragElement(state, curDragElement) {
  25. state.curDragElement = curDragElement;
  26. },
  27. setPages(state, pages) {
  28. state.pages = pages;
  29. },
  30. setTopicNos(state, topicNos) {
  31. topicNos.sort((a, b) => a - b);
  32. state.topicNos = topicNos;
  33. },
  34. setCardConfig(state, cardConfig) {
  35. state.cardConfig = Object.assign({}, state.cardConfig, cardConfig);
  36. },
  37. setPaperParams(state, paperParams) {
  38. state.paperParams = paperParams;
  39. },
  40. setCurPageNo(state, curPageNo) {
  41. state.curPageNo = curPageNo;
  42. },
  43. addPage(state, page) {
  44. state.pages.push(page);
  45. },
  46. modifyPage(state, page) {
  47. state.pages.splice(page._pageNo, 1, page);
  48. },
  49. setOpenElementEditDialog(state, openElementEditDialog) {
  50. state.openElementEditDialog = openElementEditDialog;
  51. },
  52. initTopicNos(state) {
  53. state.topicNos = getPageTopicNos(state.pages);
  54. },
  55. initState(state) {
  56. state.curElement = {};
  57. state.curDragElement = {};
  58. state.curPageNo = 0;
  59. state.pages = [];
  60. state.cardConfig = {};
  61. state.paperParams = {};
  62. state.openElementEditDialog = false;
  63. state.topicNos = [];
  64. }
  65. };
  66. /**
  67. * 获取所有相同元素的位置信息
  68. * @param {Object} element 元素
  69. * @param {Object} pages 页面结构
  70. */
  71. const fetchElementPositionInfos = (element, pages) => {
  72. let postionInfos = [];
  73. for (let i = 0, ilen = pages.length; i < ilen; i++) {
  74. for (let j = 0, jlen = pages[i].columns.length; j < jlen; j++) {
  75. pages[i].columns[j].elements.forEach((item, eindex) => {
  76. if (item.id === element.id) {
  77. postionInfos.push({ _pageNo: i, _columnNo: j, _elementNo: eindex });
  78. }
  79. });
  80. }
  81. }
  82. return postionInfos;
  83. };
  84. /**
  85. * 获取所有含有相同parent属性的元素位置信息
  86. * @param {Object} parentElement 父元素
  87. * @param {Object} pages 页面结构
  88. */
  89. const fetchAllRelateParentElementPositionInfos = (parentElement, pages) => {
  90. // 当为解答题时,parentElement传入的值是EXPLAIN
  91. let postionInfos = [];
  92. for (let i = 0, ilen = pages.length; i < ilen; i++) {
  93. for (let j = 0, jlen = pages[i].columns.length; j < jlen; j++) {
  94. pages[i].columns[j].elements.forEach((item, eindex) => {
  95. if (item["parent"] && item.parent.id === parentElement.id) {
  96. let pos = { _pageNo: i, _columnNo: j, _elementNo: eindex };
  97. if (parentElement.type === "EXPLAIN") {
  98. pos.explainNumber = item.explainNumber;
  99. }
  100. postionInfos.push(pos);
  101. }
  102. });
  103. }
  104. }
  105. return postionInfos;
  106. };
  107. const fetchFirstSubjectiveTopicPositionInfo = pages => {
  108. for (let i = 0, ilen = pages.length; i < ilen; i++) {
  109. for (let j = 0, jlen = pages[i].columns.length; j < jlen; j++) {
  110. const index = pages[i].columns[j].elements.findIndex(item => {
  111. return item.sign === "subjective";
  112. });
  113. if (index !== -1) return { _pageNo: i, _columnNo: j, _elementNo: index };
  114. }
  115. }
  116. };
  117. const fetchSameExplainNumberExplainChildernPositionInfo = (
  118. explainChildernElement,
  119. pages
  120. ) => {
  121. let postionInfos = [];
  122. const explainId = explainChildernElement.parent.id;
  123. const explainNumber = explainChildernElement.explainNumber;
  124. for (let i = 0, ilen = pages.length; i < ilen; i++) {
  125. for (let j = 0, jlen = pages[i].columns.length; j < jlen; j++) {
  126. pages[i].columns[j].elements.forEach((item, eindex) => {
  127. if (
  128. item.parent &&
  129. item.parent.id === explainId &&
  130. item.explainNumber === explainNumber
  131. ) {
  132. postionInfos.push({
  133. _pageNo: i,
  134. _columnNo: j,
  135. _elementNo: eindex,
  136. _elementId: item.id
  137. });
  138. }
  139. });
  140. }
  141. }
  142. return postionInfos;
  143. };
  144. function groupByParams(datas, paramKey) {
  145. let elementGroupInfos = [];
  146. for (let i = 0, len = datas.length; i < len; i++) {
  147. if (i === 0 || datas[i][paramKey] !== datas[i - 1][paramKey]) {
  148. elementGroupInfos.push([datas[i]]);
  149. } else {
  150. elementGroupInfos[elementGroupInfos.length - 1].push(datas[i]);
  151. }
  152. }
  153. return elementGroupInfos;
  154. }
  155. const getPageTopicNos = pages => {
  156. let topicNos = [];
  157. pages.forEach(page => {
  158. page.columns.forEach(column => {
  159. column.elements.forEach(element => {
  160. if (element["topicNo"] && !topicNos.includes(element["topicNo"]))
  161. topicNos.push(element["topicNo"]);
  162. });
  163. });
  164. });
  165. return topicNos;
  166. };
  167. const fetchPageLastColumnPositionInfo = pages => {
  168. return {
  169. _pageNo: pages.length - 1,
  170. _columnNo: pages[pages.length - 1].columns.length - 1
  171. };
  172. };
  173. const findElementById = (id, pages) => {
  174. let curElement = null;
  175. pages.forEach(page => {
  176. page.columns.forEach(column => {
  177. column.elements.forEach(element => {
  178. if (curElement) return;
  179. if (element.id === id) {
  180. curElement = element;
  181. return;
  182. }
  183. if (element["elements"]) {
  184. element["elements"].forEach(elem => {
  185. if (element.id === id) curElement = elem;
  186. });
  187. }
  188. });
  189. });
  190. });
  191. return curElement;
  192. };
  193. const createFunc = {
  194. EXPLAIN(element) {
  195. return getExplainChildren(element);
  196. },
  197. FILL_QUESTION(element) {
  198. return getFillQuesitons(element, state.cardConfig.columnNumber);
  199. },
  200. FILL_LINE(element) {
  201. return getFillLine(element);
  202. },
  203. COMPOSITION(element) {
  204. return [element];
  205. }
  206. };
  207. const actions = {
  208. modifyElement({ state, commit, dispatch }, element) {
  209. if (element.type === "COMPOSITION") {
  210. const positionInfos = fetchElementPositionInfos(element, state.pages);
  211. if (positionInfos.length) {
  212. const pos = positionInfos[0];
  213. const elements =
  214. state.pages[pos._pageNo].columns[pos._columnNo].elements;
  215. elements.splice(pos._elementNo, 1, element);
  216. } else {
  217. dispatch("addElement", element);
  218. }
  219. } else if (element.type === "EXPLAIN") {
  220. const positionInfos = fetchAllRelateParentElementPositionInfos(
  221. element,
  222. state.pages
  223. );
  224. if (positionInfos.length) {
  225. const elementGroupPosInfos = groupByParams(
  226. positionInfos,
  227. "explainNumber"
  228. );
  229. const orgElementCount = elementGroupPosInfos.length;
  230. if (orgElementCount > element.questionsCount) {
  231. // 原小题数多于新小题数,要删除原多于的小题;
  232. let needDeleteInfos = elementGroupPosInfos.splice(
  233. element.questionsCount,
  234. orgElementCount - element.questionsCount
  235. );
  236. needDeleteInfos.reverse().forEach(item => {
  237. item.reverse().forEach(pos => {
  238. const elems =
  239. state.pages[pos._pageNo].columns[pos._columnNo].elements;
  240. elems.splice(pos._elementNo, 1);
  241. });
  242. });
  243. }
  244. const newElements = getExplainChildren(element);
  245. const lastPos = elementGroupPosInfos.slice(-1)[0].slice(-1)[0];
  246. for (var i = 0; i < element.questionsCount; i++) {
  247. if (elementGroupPosInfos[i]) {
  248. elementGroupPosInfos[i].forEach(pos => {
  249. let child =
  250. state.pages[pos._pageNo].columns[pos._columnNo].elements[
  251. pos._elementNo
  252. ];
  253. child.explainNumber = i + element.startNumber;
  254. child.parent = { ...element };
  255. });
  256. } else {
  257. state.pages[lastPos._pageNo].columns[
  258. lastPos._columnNo
  259. ].elements.splice(lastPos._elementNo + 1, 0, newElements[i]);
  260. }
  261. }
  262. } else {
  263. dispatch("addElement", element);
  264. }
  265. } else {
  266. // 非作文题都是拆分题,即同一个题拆分成多个小题展示
  267. const positionInfos = fetchAllRelateParentElementPositionInfos(
  268. element,
  269. state.pages
  270. );
  271. if (positionInfos.length) {
  272. // 缓存已编辑的小题高度信息。
  273. const elementHeights = positionInfos.map(pos => {
  274. return state.pages[pos._pageNo].columns[pos._columnNo].elements[
  275. pos._elementNo
  276. ].h;
  277. });
  278. // 删除所有小题
  279. positionInfos.reverse().forEach(pos => {
  280. const elems =
  281. state.pages[pos._pageNo].columns[pos._columnNo].elements;
  282. elems.splice(pos._elementNo, 1);
  283. });
  284. // 创建新的小题元素
  285. const newElements = createFunc[element.type](element);
  286. const pos = positionInfos.pop();
  287. newElements.forEach((newElement, index) => {
  288. newElement.h = elementHeights[index] || newElement.h;
  289. state.pages[pos._pageNo].columns[pos._columnNo].elements.splice(
  290. pos._elementNo + index,
  291. 0,
  292. newElement
  293. );
  294. });
  295. } else {
  296. dispatch("addElement", element);
  297. }
  298. }
  299. commit("setCurElement", element);
  300. },
  301. addElement({ state, commit }, element) {
  302. let pos = null;
  303. // 客观题和主观题分别对待
  304. if (element.type === "FILL_QUESTION") {
  305. pos =
  306. fetchFirstSubjectiveTopicPositionInfo(state.pages) ||
  307. fetchPageLastColumnPositionInfo(state.pages);
  308. } else {
  309. pos = fetchPageLastColumnPositionInfo(state.pages);
  310. }
  311. const elements = state.pages[pos._pageNo].columns[pos._columnNo].elements;
  312. let preElements = createFunc[element.type](element);
  313. preElements.forEach(preElement => {
  314. elements.push(preElement);
  315. });
  316. commit("setCurElement", element);
  317. },
  318. modifyCardHead({ state }, element) {
  319. state.pages[0].columns[0].elements.splice(0, 1, element);
  320. },
  321. removeElement({ state, commit }, element) {
  322. if (element.type === "COMPOSITION") {
  323. const positionInfos = fetchElementPositionInfos(element, state.pages);
  324. if (!positionInfos.length) return;
  325. positionInfos.forEach(pos => {
  326. const elements =
  327. state.pages[pos._pageNo].columns[pos._columnNo].elements;
  328. elements.splice(pos._elementNo, 1);
  329. });
  330. } else {
  331. // 非作文题,删除所有小题。
  332. const positionInfos = fetchAllRelateParentElementPositionInfos(
  333. element.parent,
  334. state.pages
  335. );
  336. if (positionInfos.length) {
  337. positionInfos.reverse().forEach(pos => {
  338. const elems =
  339. state.pages[pos._pageNo].columns[pos._columnNo].elements;
  340. elems.splice(pos._elementNo, 1);
  341. });
  342. }
  343. }
  344. // 删除大题题号
  345. state.topicNos.splice(state.topicNos.indexOf(element.topicNo), 1);
  346. commit("setCurElement", {});
  347. },
  348. removeElementChild({ state, commit }, element) {
  349. // 删除解答题小题和作文题的子元素
  350. const positionInfos = fetchElementPositionInfos(
  351. element.container,
  352. state.pages
  353. );
  354. if (!positionInfos.length) return;
  355. positionInfos.forEach(pos => {
  356. const columnElement =
  357. state.pages[pos._pageNo].columns[pos._columnNo].elements[
  358. pos._elementNo
  359. ];
  360. const childIndex = columnElement.elements.findIndex(
  361. item => item.id === element.id
  362. );
  363. columnElement.elements.splice(childIndex, 1);
  364. });
  365. commit("setCurElement", {});
  366. },
  367. modifyElementChild({ state, commit }, element) {
  368. // 修改解答题小题和作文题的子元素
  369. const positionInfos = fetchElementPositionInfos(
  370. element.container,
  371. state.pages
  372. );
  373. if (!positionInfos.length) return;
  374. positionInfos.forEach(pos => {
  375. const columnElement =
  376. state.pages[pos._pageNo].columns[pos._columnNo].elements[
  377. pos._elementNo
  378. ];
  379. const childIndex = columnElement.elements.findIndex(
  380. item => item.id === element.id
  381. );
  382. columnElement.elements.splice(childIndex, 1, element);
  383. });
  384. commit("setCurElement", element);
  385. },
  386. resetElementProp({ state }, isResetId = false) {
  387. state.pages.forEach(page => {
  388. page.columns.forEach(column => {
  389. column.elements.forEach(element => {
  390. const elementDom = document.getElementById(element.id);
  391. if (elementDom) {
  392. element.h = elementDom.offsetHeight;
  393. element.w = elementDom.offsetWidth;
  394. }
  395. if (isResetId) {
  396. element.id = getElementId();
  397. }
  398. });
  399. });
  400. });
  401. },
  402. rebuildPages({ state, commit }) {
  403. const columnNumber = state.cardConfig.columnNumber;
  404. // 更新元件最新的高度信息
  405. // 整理所有元件
  406. let objectiveElements = [];
  407. let subjectiveElements = [];
  408. const cardHeadElement = state.pages[0].columns[0].elements[0];
  409. state.pages.forEach(page => {
  410. page.columns.forEach(column => {
  411. column.elements.forEach(element => {
  412. const elementDom = document.getElementById(element.id);
  413. if (elementDom) {
  414. element.h = elementDom.offsetHeight;
  415. element.w = elementDom.offsetWidth;
  416. // 解答题小题与其他题有些区别。
  417. // 其他题都是通过内部子元素自动撑高元件,而解答题则需要手动设置高度。
  418. element.isCovered =
  419. element.type !== "EXPLAIN_CHILDREN" &&
  420. elementDom.offsetHeight < elementDom.firstChild.offsetHeight;
  421. }
  422. // 过滤掉所有topic-head元素,这个元素是动态加的,页面重排时可能会添加重复元件。
  423. if (element.sign && element.type !== "TOPIC_HEAD") {
  424. if (element.sign === "objective") objectiveElements.push(element);
  425. if (element.sign === "subjective") subjectiveElements.push(element);
  426. }
  427. });
  428. });
  429. });
  430. // 动态计算每列可以分配的元件
  431. const columnHeight = document.getElementById("column-0-0").offsetHeight;
  432. const simpleCardHeadHeight = document.getElementById("simple-card-head")
  433. .offsetHeight;
  434. let pages = [];
  435. let page = {};
  436. let columns = [];
  437. let curColumnElements = [];
  438. let curColumnHeight = 0;
  439. const initCurColumnElements = () => {
  440. curColumnElements = [];
  441. curColumnHeight = 0;
  442. const groupColumnNumber = columnNumber * 2;
  443. // 奇数页第一栏;
  444. if (!(columns.length % groupColumnNumber)) {
  445. // 非第一页奇数页第一栏
  446. if (columns.length) {
  447. const cardHeadSimpleElement = {
  448. ...cardHeadElement
  449. };
  450. cardHeadSimpleElement.id = getElementId();
  451. cardHeadSimpleElement.isSimple = true;
  452. cardHeadSimpleElement.h = simpleCardHeadHeight;
  453. curColumnElements.push(cardHeadSimpleElement);
  454. curColumnHeight += simpleCardHeadHeight;
  455. } else {
  456. curColumnElements.push(cardHeadElement);
  457. curColumnHeight += cardHeadElement.h;
  458. }
  459. }
  460. };
  461. const checkElementIsCurColumnFirstType = element => {
  462. const topicHeadIndex = curColumnElements.findIndex(
  463. elem => elem.type === "TOPIC_HEAD" && elem.sign === element.sign
  464. );
  465. return topicHeadIndex === -1;
  466. };
  467. // 放入元素通用流程
  468. const pushElement = element => {
  469. // 当前栏中第一个题型之前新增题型头元素(topic-head)。
  470. // 题型头和当前题要组合加入栏中,不可拆分。
  471. let elementList = [element];
  472. if (checkElementIsCurColumnFirstType(element)) {
  473. elementList.unshift(
  474. getTopicHead(
  475. state.cardConfig[`${element.sign}Notice`],
  476. element.sign,
  477. !curColumnElements.length
  478. )
  479. );
  480. }
  481. const elementHeight = calcSum(elementList.map(elem => elem.h));
  482. if (curColumnHeight + elementHeight > columnHeight) {
  483. // 当前栏第一个元素高度超过一栏时,不拆分,直接放在当前栏。
  484. // 解决可能空栏的情况
  485. const curElementIsFirst = !curColumnElements.length;
  486. if (curElementIsFirst) {
  487. curColumnElements = [...curColumnElements, ...elementList];
  488. curColumnHeight += elementHeight;
  489. } else {
  490. columns.push([...curColumnElements]);
  491. initCurColumnElements();
  492. pushElement(element);
  493. }
  494. } else {
  495. curColumnElements = [...curColumnElements, ...elementList];
  496. curColumnHeight += elementHeight;
  497. }
  498. };
  499. // 批量添加所有元素。
  500. initCurColumnElements();
  501. [...objectiveElements, ...subjectiveElements].map((element, eindex) => {
  502. element.serialNo = eindex;
  503. pushElement(element);
  504. });
  505. // 最后一栏的处理。
  506. columns.push([...curColumnElements]);
  507. // 构建pages
  508. columns.forEach((column, cindex) => {
  509. const columnNo = cindex % columnNumber;
  510. if (!columnNo) {
  511. page = getNewPage(pages.length, columnNumber);
  512. }
  513. page.columns[columnNo].elements = column;
  514. if (columnNo + 1 === columnNumber || cindex === columns.length - 1) {
  515. pages.push(deepCopy(page));
  516. }
  517. });
  518. // 保证页面总是偶数页
  519. if (pages.length % 2) {
  520. pages.push(getNewPage(pages.length, columnNumber));
  521. }
  522. commit("setPages", pages);
  523. },
  524. actElementById({ state, commit }, id) {
  525. const curElement = findElementById(id, state.pages);
  526. if (!curElement) return;
  527. commit("setCurElement", curElement);
  528. },
  529. copyExplainChildren({ state }, element) {
  530. let newElement = deepCopy(element);
  531. newElement.id = getElementId();
  532. newElement.elements = [];
  533. newElement.h = 200;
  534. newElement.showTitle = false;
  535. const positionInfos = fetchElementPositionInfos(element, state.pages);
  536. if (!positionInfos.length) return;
  537. const pos = positionInfos[0];
  538. state.pages[pos._pageNo].columns[pos._columnNo].elements.splice(
  539. pos._elementNo + 1,
  540. 0,
  541. newElement
  542. );
  543. },
  544. deleteExplainChildren({ state }, element) {
  545. const positionInfos = fetchSameExplainNumberExplainChildernPositionInfo(
  546. element,
  547. state.pages
  548. );
  549. if (positionInfos.length < 2) return;
  550. const pindex = positionInfos.findIndex(
  551. item => item._elementId === element.id
  552. );
  553. const pos = positionInfos[pindex];
  554. state.pages[pos._pageNo].columns[pos._columnNo].elements.splice(
  555. pos._elementNo,
  556. 1
  557. );
  558. // 当删除的是含有标题解答题小题时,则需要将下一个答题区开启显示标题。
  559. // element.explainNumber === element.parent.startNumber && pindex === 0
  560. if (element.showTitle) {
  561. const nextPos = positionInfos[pindex + 1];
  562. state.pages[nextPos._pageNo].columns[nextPos._columnNo].elements[
  563. nextPos._elementNo
  564. ].showTitle = true;
  565. }
  566. }
  567. };
  568. export { fetchSameExplainNumberExplainChildernPositionInfo };
  569. export default {
  570. namespaced: true,
  571. state,
  572. mutations,
  573. actions
  574. };