Browse Source

Merge branch 'dev_v3.0.1' into dev_v3.1.0

zhangjie 3 years ago
parent
commit
7ef7f9dca4
1 changed files with 698 additions and 687 deletions
  1. 698 687
      card/store/card.js

+ 698 - 687
card/store/card.js

@@ -1,687 +1,698 @@
-import {
-  getExplainElements,
-  getFillQuesitonElements,
-  getFillLineElements,
-  getCompositionElements,
-  getNewPage,
-  getTopicHeadModel
-} from "../elementModel";
-import { objAssign, deepCopy, calcSum, getElementId } from "../plugins/utils";
-
-const state = {
-  cardConfig: {},
-  paperParams: {},
-  curElement: {},
-  curDragElement: {},
-  curPage: {},
-  curPageNo: 0,
-  pages: [],
-  topics: [],
-  topicSeries: [], // 大题顺序号
-  insetTarget: {}, // 需要在其后面插入大题的大题
-  openElementEditDialog: false
-};
-
-const mutations = {
-  setCardConfig(state, cardConfig) {
-    state.cardConfig = Object.assign({}, state.cardConfig, cardConfig);
-  },
-  setPaperParams(state, paperParams) {
-    state.paperParams = paperParams;
-  },
-  setCurElement(state, curElement) {
-    state.curElement = curElement;
-  },
-  setCurDragElement(state, curDragElement) {
-    state.curDragElement = curDragElement;
-  },
-  setCurPage(state, curPageNo) {
-    const pageNo = state.pages[curPageNo] ? curPageNo : 0;
-    state.curPage = state.pages[pageNo];
-    state.curPageNo = pageNo;
-  },
-  setCurPageNo(state, curPageNo) {
-    state.curPageNo = curPageNo;
-  },
-  setPages(state, pages) {
-    state.pages = pages;
-  },
-  setTopics(state, topics) {
-    state.topics = topics;
-  },
-  setTopicSeries(state, topicSeries) {
-    state.topicSeries = topicSeries;
-  },
-  setInsetTarget(state, insetTarget) {
-    state.insetTarget = insetTarget;
-  },
-  addPage(state, page) {
-    state.pages.push(page);
-  },
-  modifyPage(state, page) {
-    state.pages.splice(page._pageNo, 1, page);
-  },
-  setOpenElementEditDialog(state, openElementEditDialog) {
-    state.openElementEditDialog = openElementEditDialog;
-  },
-  initState(state) {
-    state.curElement = {};
-    state.curDragElement = {};
-    state.curPage = {};
-    state.curPageNo = 0;
-    state.topics = [];
-    state.pages = [];
-    state.cardConfig = {};
-    state.paperParams = {};
-    state.openElementEditDialog = false;
-    state.topicSeries = [];
-  }
-};
-
-const fetchElementPositionInfos = (element, topics) => {
-  return topics.findIndex(item => item.id === element.id);
-};
-
-const fetchAllRelateParentElementPositionInfos = (parentElement, topics) => {
-  // 当为解答题时,parentElement传入的值是EXPLAIN
-  let postionInfos = [];
-  topics.forEach((item, eindex) => {
-    if (item["parent"] && item.parent.id === parentElement.id) {
-      let pos = { _elementNo: eindex };
-      if (parentElement.type === "EXPLAIN") {
-        pos.serialNumber = item.serialNumber;
-      }
-      postionInfos.push(pos);
-    }
-  });
-
-  return postionInfos;
-};
-
-const fetchFirstSubjectiveTopicPositionInfo = topics => {
-  return topics.findIndex(item => item.sign === "subjective");
-};
-
-const fetchSameSerialNumberChildrenPositionInfo = (
-  elementChildernElement,
-  topics
-) => {
-  let postionInfos = [];
-  const elementId = elementChildernElement.parent.id;
-  const serialNumber = elementChildernElement.serialNumber;
-
-  topics.forEach((item, eindex) => {
-    if (
-      item.parent &&
-      item.parent.id === elementId &&
-      item.serialNumber === serialNumber
-    ) {
-      postionInfos.push({
-        _elementNo: eindex,
-        _elementId: item.id
-      });
-    }
-  });
-  return postionInfos;
-};
-
-const groupByParams = (datas, paramKey) => {
-  let elementGroupInfos = [];
-  for (let i = 0, len = datas.length; i < len; i++) {
-    if (i === 0 || datas[i][paramKey] !== datas[i - 1][paramKey]) {
-      elementGroupInfos.push([datas[i]]);
-    } else {
-      elementGroupInfos[elementGroupInfos.length - 1].push(datas[i]);
-    }
-  }
-  return elementGroupInfos;
-};
-
-const findElementById = (id, topics) => {
-  let curElement = null;
-  topics.forEach(element => {
-    if (curElement) return;
-    if (element.id === id) {
-      curElement = element;
-      return;
-    }
-
-    if (element["elements"]) {
-      element["elements"].forEach(elem => {
-        if (elem.id === id) curElement = elem;
-      });
-    }
-  });
-  return curElement;
-};
-
-const checkElementisCovered = (id, type) => {
-  const elementDom = document.getElementById(id);
-
-  if (type === "EXPLAIN") {
-    const elemTitleDome = elementDom.querySelector(".elem-title");
-    const limitHeight = elemTitleDome
-      ? elementDom.offsetHeight - elemTitleDome.offsetHeight
-      : elementDom.offsetHeight;
-
-    let elementHeights = [];
-    elementDom
-      .querySelector(".elem-explain-elements")
-      .childNodes.forEach(node => {
-        if (!node.className.includes("elem-explain-element")) return;
-        elementHeights.push(
-          node.firstChild.offsetHeight + node.firstChild.offsetTop
-        );
-      });
-    return elementHeights.some(item => item > limitHeight);
-  }
-
-  if (type === "COMPOSITION") {
-    const elemTitleDome = elementDom.querySelector(".elem-title");
-    const limitHeight = elemTitleDome
-      ? elementDom.offsetHeight - elemTitleDome.offsetHeight
-      : elementDom.offsetHeight;
-
-    let elementHeights = [];
-    elementDom
-      .querySelector(".elem-composition-elements")
-      .childNodes.forEach(node => {
-        if (!node.className.includes("elem-composition-element")) return;
-        elementHeights.push(
-          node.firstChild.offsetHeight + node.firstChild.offsetTop
-        );
-      });
-    return elementHeights.some(item => item > limitHeight);
-  }
-
-  return elementDom.offsetHeight < elementDom.firstChild.offsetHeight;
-};
-
-const createFunc = {
-  EXPLAIN(element) {
-    return getExplainElements(element);
-  },
-  FILL_QUESTION(element) {
-    return getFillQuesitonElements(element, state.cardConfig);
-  },
-  FILL_LINE(element) {
-    return getFillLineElements(element);
-  },
-  COMPOSITION(element) {
-    return [getCompositionElements(element)];
-  }
-};
-
-const actions = {
-  initTopicsFromPages({ state, commit }) {
-    let topics = [];
-    state.pages.forEach(page => {
-      page.columns.forEach(column => {
-        column.elements.forEach(element => {
-          if (
-            element.type === "TOPIC_HEAD" ||
-            (element.type === "CARD_HEAD" && element.isSimple)
-          )
-            return;
-          topics.push(element);
-        });
-      });
-    });
-    commit("setTopics", topics);
-  },
-  actElementById({ state, commit }, id) {
-    const curElement = findElementById(id, state.topics);
-    if (!curElement) return;
-
-    commit("setCurElement", curElement);
-  },
-  resetTopicSeries({ state, commit }) {
-    let curTopicId = "",
-      curTopicNo = 0,
-      topicSeries = [];
-    state.topics.forEach(topic => {
-      if (!topic.parent) return;
-      if (curTopicId !== topic.parent.id) {
-        curTopicId = topic.parent.id;
-        curTopicNo++;
-        topicSeries.push({
-          id: curTopicId,
-          topicNo: curTopicNo,
-          type: topic.type,
-          sign: topic.sign
-        });
-      }
-      topic.topicNo = curTopicNo;
-    });
-    commit("setTopicSeries", topicSeries);
-  },
-  // 新增试题 --------------->
-  addElement({ state, commit, dispatch }, element) {
-    let pos = null;
-    // 客观题和主观题分别对待
-    if (state.insetTarget.id) {
-      // 存在插入目标元素
-      if (
-        state.insetTarget.type === "FILL_QUESTION" &&
-        element.type !== "FILL_QUESTION"
-      ) {
-        pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
-      } else {
-        let relateTopicPos = [];
-        state.topics.forEach((item, index) => {
-          if (item.parent && item.parent.id === state.insetTarget.id)
-            relateTopicPos.push(index);
-        });
-        pos = relateTopicPos.slice(-1)[0] + 1;
-      }
-    } else {
-      // 不存在插入目标元素
-      if (element.sign === "objective") {
-        pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
-      }
-    }
-
-    let preElements = createFunc[element.type](element);
-    preElements.forEach((preElement, index) => {
-      if (pos && pos !== -1) {
-        state.topics.splice(pos + index, 0, preElement);
-      } else {
-        state.topics.push(preElement);
-      }
-    });
-    dispatch("resetTopicSeries");
-    commit("setInsetTarget", {});
-
-    commit("setCurElement", element);
-  },
-  // 修改试题 --------------->
-  modifyTopic({ state }, element) {
-    // 单独编辑某个细分题
-    const pos = fetchElementPositionInfos(element, state.topics);
-    state.topics.splice(pos, 1, element);
-  },
-  modifyComposition({ state }, element) {
-    const positionInfos = fetchAllRelateParentElementPositionInfos(
-      element,
-      state.topics
-    );
-    positionInfos.forEach(({ _elementNo }) => {
-      state.topics[_elementNo].topicNo = element.topicNo;
-      state.topics[_elementNo].parent = objAssign(
-        state.topics[_elementNo].parent,
-        element
-      );
-    });
-  },
-  modifyExplain({ state }, element) {
-    // 解答题既是拆分题,又是可复制题
-    const positionInfos = fetchAllRelateParentElementPositionInfos(
-      element,
-      state.topics
-    );
-    const elementGroupPosInfos = groupByParams(positionInfos, "serialNumber");
-    const orgElementCount = elementGroupPosInfos.length;
-    if (orgElementCount > element.questionsCount) {
-      // 原小题数多于新小题数,要删除原多于的小题;
-      let needDeleteInfos = elementGroupPosInfos.splice(
-        element.questionsCount,
-        orgElementCount - element.questionsCount
-      );
-      needDeleteInfos.reverse().forEach(item => {
-        item.reverse().forEach(pos => {
-          state.topics.splice(pos._elementNo, 1);
-        });
-      });
-    }
-
-    const newElements = getExplainElements(element);
-    const lastPos = elementGroupPosInfos.slice(-1)[0].slice(-1)[0];
-    let lastNewElementPos = lastPos._elementNo;
-    for (let i = 0; i < element.questionsCount; i++) {
-      if (elementGroupPosInfos[i]) {
-        elementGroupPosInfos[i].forEach(pos => {
-          let child = state.topics[pos._elementNo];
-          child.serialNumber = i + element.startNumber;
-          child.parent = { ...element };
-          child.topicNo = element.topicNo;
-        });
-      } else {
-        state.topics.splice(++lastNewElementPos, 0, newElements[i]);
-      }
-    }
-  },
-  modifySplitTopic({ state }, element) {
-    // 非作文题都是拆分题,即同一个题拆分成多个小题展示
-    const positionInfos = fetchAllRelateParentElementPositionInfos(
-      element,
-      state.topics
-    );
-    // 缓存已编辑的小题高度信息。
-    // const elementHeights = positionInfos.map(
-    //   pos => state.topics[pos._elementNo].h
-    // );
-    // 删除所有小题
-    positionInfos.reverse().forEach(pos => {
-      state.topics.splice(pos._elementNo, 1);
-    });
-    // 创建新的小题元素
-    const newElements = createFunc[element.type](element);
-    const pos = positionInfos.pop();
-    newElements.forEach((newElement, index) => {
-      // 不再复用缓存高度,修改于2022-03-10 09:23
-      // newElement.h = Math.max(elementHeights[index] || 0, newElement.h);
-      state.topics.splice(pos._elementNo + index, 0, newElement);
-    });
-  },
-  modifyElement({ commit, dispatch }, element) {
-    if (element.type === "COMPOSITION") {
-      dispatch("modifyComposition", element);
-    } else if (element.type === "EXPLAIN") {
-      dispatch("modifyExplain", element);
-    } else {
-      dispatch("modifySplitTopic", element);
-    }
-    dispatch("resetTopicSeries");
-    commit("setCurElement", element);
-  },
-  modifyCardHead({ state }, element) {
-    state.topics.splice(0, 1, element);
-  },
-  // 修改试题包含元素
-  modifyElementChild({ state, commit }, element) {
-    // 修改解答题小题和作文题的子元素
-    const pos = fetchElementPositionInfos(element.container, state.topics);
-    const columnElements = state.topics[pos].elements;
-    const childIndex = columnElements.findIndex(item => item.id === element.id);
-    element.id = getElementId();
-    // 作文题中的多线条和网格元素重新计算高度。
-    if (element.container.type === "COMPOSITION") {
-      if (element.type === "LINES") {
-        element.h = element.lineCount * (element.lineSpacing + 1);
-      }
-      if (element.type === "GRIDS") {
-        element.h =
-          element.rowCount * (element.columnSize + 1 + element.rowSpace) + 1;
-      }
-    }
-    if (childIndex === -1) {
-      columnElements.push(element);
-    } else {
-      columnElements.splice(childIndex, 1, element);
-    }
-
-    commit("setCurElement", element);
-  },
-  // 粘贴试题内的元素
-  pasteExplainElementChild({ state }, { curElement, pasteElement }) {
-    let element = {
-      id: curElement.container ? curElement.container.id : curElement.id
-    };
-    const pos = fetchElementPositionInfos(element, state.topics);
-    if (pos === -1) return;
-
-    element = state.topics[pos];
-    const newElement = Object.assign({}, pasteElement, {
-      id: getElementId(),
-      container: {
-        id: element.id,
-        type: element.type
-      }
-    });
-    element.elements.push(newElement);
-  },
-  // 删除试题 --------------->
-  removeElement({ state, commit, dispatch }, element) {
-    const positionInfos = fetchAllRelateParentElementPositionInfos(
-      element.parent,
-      state.topics
-    );
-    positionInfos.reverse().forEach(pos => {
-      state.topics.splice(pos._elementNo, 1);
-    });
-
-    dispatch("resetTopicSeries");
-
-    commit("setCurElement", {});
-  },
-  // 删除试题包含元素 --------------->
-  removeElementChild({ state, commit }, element) {
-    // 删除解答题小题和作文题的子元素
-    const pos = fetchElementPositionInfos(element.container, state.topics);
-    const columnElements = state.topics[pos].elements;
-    const childIndex = columnElements.findIndex(item => item.id === element.id);
-    columnElements.splice(childIndex, 1);
-
-    commit("setCurElement", {});
-  },
-  // 扩展答题区操作 --------------->
-  copyExplainChildren({ state }, element) {
-    let curElement = {
-      id: element.container ? element.container.id : element.id
-    };
-    const pos = fetchElementPositionInfos(curElement, state.topics);
-    curElement = state.topics[pos];
-
-    let newElement = Object.assign({}, curElement, {
-      id: getElementId(),
-      elements: [],
-      h: 200,
-      isExtend: true,
-      showTitle: false
-    });
-
-    state.topics.splice(pos + 1, 0, newElement);
-    // 更新小题答题区isLast
-    let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
-      curElement,
-      state.topics
-    );
-    positionInfos.forEach((pos, pindex) => {
-      state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
-    });
-  },
-  deleteExplainChildren({ state }, element) {
-    let curElement = {
-      id: element.container ? element.container.id : element.id
-    };
-    const curPos = fetchElementPositionInfos(curElement, state.topics);
-    curElement = state.topics[curPos];
-
-    let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
-      curElement,
-      state.topics
-    );
-    if (positionInfos.length < 2) return;
-    const pindex = positionInfos.findIndex(
-      item => item._elementId === curElement.id
-    );
-    const pos = positionInfos[pindex]._elementNo;
-    positionInfos.splice(pindex, 1);
-    const nextPos = positionInfos[0]._elementNo;
-    // 当删除的是非扩展区域时,则下一个答题区要被设置成非扩展区
-    if (!curElement.isExtend) {
-      state.topics[nextPos].isExtend = false;
-    }
-    // 当删除的是含有标题答题区时,则需要将下一个答题区开启显示标题。
-    if (curElement.showTitle) {
-      state.topics[nextPos].showTitle = true;
-    }
-    state.topics.splice(pos, 1);
-    // 更新小题答题区isLast
-    positionInfos = fetchSameSerialNumberChildrenPositionInfo(
-      curElement,
-      state.topics
-    );
-    positionInfos.forEach((pos, pindex) => {
-      state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
-    });
-  },
-  // 大题顺序操作 --------------->
-  topicMoveUp({ state, dispatch }, topicId) {
-    const curTopicPos = state.topicSeries.findIndex(
-      item => item.id === topicId
-    );
-    const prevTopicId = state.topicSeries[curTopicPos - 1].id;
-    let relateTopicPos = [];
-    state.topics.forEach((item, index) => {
-      if (item.parent && item.parent.id === topicId) relateTopicPos.push(index);
-    });
-    const prevTopicFirstIndex = state.topics.findIndex(
-      item => item.parent && item.parent.id === prevTopicId
-    );
-    const relateTopics = state.topics.splice(
-      relateTopicPos[0],
-      relateTopicPos.length
-    );
-    relateTopics.reverse().forEach(topic => {
-      state.topics.splice(prevTopicFirstIndex, 0, topic);
-    });
-
-    dispatch("resetTopicSeries");
-  },
-  // 重构页面
-  resetElementProp({ state }, isResetId = false) {
-    state.topics.forEach(element => {
-      const elementDom = document.getElementById(element.id);
-      if (elementDom) {
-        element.h = elementDom.offsetHeight;
-        element.w = elementDom.offsetWidth;
-      }
-      if (isResetId) {
-        element.id = getElementId();
-      }
-    });
-  },
-  rebuildPages({ state, commit }) {
-    const columnNumber = state.cardConfig.columnNumber;
-    const pageSize = state.cardConfig.pageSize;
-    // 更新元件最新的高度信息
-    // 整理所有元件
-    const cardHeadElement = state.topics[0];
-    state.topics.forEach(element => {
-      const elementDom = document.getElementById(`preview-${element.id}`);
-
-      if (elementDom) {
-        element.h = elementDom.offsetHeight;
-        element.w = elementDom.offsetWidth;
-        // 解答题小题与其他题有些区别。
-        // 其他题都是通过内部子元素自动撑高元件,而解答题则需要手动设置高度。
-        const ESCAPE_ELEMENTS = ["CARD_HEAD"];
-        element.isCovered =
-          !ESCAPE_ELEMENTS.includes(element.type) &&
-          checkElementisCovered(`preview-${element.id}`, element.type);
-      }
-    });
-
-    // 动态计算每列可以分配的元件
-    const columnHeight = document.getElementById("topic-column").offsetHeight;
-    const simpleCardHeadHeight = document.getElementById("simple-card-head")
-      .offsetHeight;
-    let pages = [];
-    let page = {};
-    let columns = [];
-    let curColumnElements = [];
-    let curColumnHeight = 0;
-
-    const initCurColumnElements = () => {
-      curColumnElements = [];
-      curColumnHeight = 0;
-      const groupColumnNumber = columnNumber * 2;
-      // 奇数页第一栏;
-      if (!(columns.length % groupColumnNumber)) {
-        // 非第一页奇数页第一栏
-        if (columns.length) {
-          const cardHeadSimpleElement = Object.assign({}, cardHeadElement, {
-            id: getElementId(),
-            isSimple: true,
-            h: simpleCardHeadHeight
-          });
-          curColumnElements.push(cardHeadSimpleElement);
-          curColumnHeight += simpleCardHeadHeight;
-        } else {
-          curColumnElements.push(cardHeadElement);
-          curColumnHeight += cardHeadElement.h;
-        }
-      }
-    };
-
-    const checkElementIsCurColumnFirstType = element => {
-      const topicHeadIndex = curColumnElements.findIndex(
-        elem => elem.type === "TOPIC_HEAD" && elem.sign === element.sign
-      );
-      return topicHeadIndex === -1;
-    };
-
-    // 放入元素通用流程
-    const pushElement = element => {
-      // 当前栏中第一个题型之前新增题型头元素(topic-head)。
-      // 题型头和当前题要组合加入栏中,不可拆分。
-      let elementList = [element];
-      if (checkElementIsCurColumnFirstType(element)) {
-        elementList.unshift(
-          getTopicHeadModel(
-            state.cardConfig[`${element.sign}Attention`],
-            element.sign,
-            !curColumnElements.length
-          )
-        );
-      }
-
-      const elementHeight = calcSum(elementList.map(elem => elem.h));
-      if (curColumnHeight + elementHeight > columnHeight) {
-        // 当前栏第一个元素高度超过一栏时,不拆分,直接放在当前栏。
-        // 解决可能空栏的情况
-        const curElementIsFirst = !curColumnElements.length;
-        if (curElementIsFirst) {
-          curColumnElements = [...curColumnElements, ...elementList];
-          curColumnHeight += elementHeight;
-        } else {
-          columns.push([...curColumnElements]);
-          initCurColumnElements();
-          pushElement(element);
-        }
-      } else {
-        curColumnElements = [...curColumnElements, ...elementList];
-        curColumnHeight += elementHeight;
-      }
-    };
-
-    // 批量添加所有元素。
-    initCurColumnElements();
-    state.topics.slice(1).forEach((element, eindex) => {
-      element.elementSerialNo = eindex;
-      pushElement(element);
-    });
-
-    // 最后一栏的处理。
-    columns.push([...curColumnElements]);
-    // 构建pages
-    columns.forEach((column, cindex) => {
-      const columnNo = cindex % columnNumber;
-      if (!columnNo) {
-        page = getNewPage(pages.length, { pageSize, columnNumber });
-      }
-      page.columns[columnNo].elements = column;
-
-      if (columnNo + 1 === columnNumber || cindex === columns.length - 1) {
-        pages.push(deepCopy(page));
-      }
-    });
-    // 保证页面总是偶数页
-    if (pages.length % 2) {
-      pages.push(getNewPage(pages.length, { pageSize, columnNumber }));
-    }
-
-    commit("setPages", pages);
-    commit("setCurPage", state.curPageNo);
-  }
-};
-
-export { fetchSameSerialNumberChildrenPositionInfo, checkElementisCovered };
-
-export default {
-  namespaced: true,
-  state,
-  mutations,
-  actions
-};
+import {
+  getExplainElements,
+  getFillQuesitonElements,
+  getFillLineElements,
+  getCompositionElements,
+  getNewPage,
+  getTopicHeadModel
+} from "../elementModel";
+import { objAssign, deepCopy, calcSum, getElementId } from "../plugins/utils";
+
+const state = {
+  cardConfig: {},
+  paperParams: {},
+  curElement: {},
+  curDragElement: {},
+  curPage: {},
+  curPageNo: 0,
+  pages: [],
+  topics: [],
+  topicSeries: [], // 大题顺序号
+  insetTarget: {}, // 需要在其后面插入大题的大题
+  openElementEditDialog: false
+};
+
+const mutations = {
+  setCardConfig(state, cardConfig) {
+    state.cardConfig = Object.assign({}, state.cardConfig, cardConfig);
+  },
+  setPaperParams(state, paperParams) {
+    state.paperParams = paperParams;
+  },
+  setCurElement(state, curElement) {
+    state.curElement = curElement;
+  },
+  setCurDragElement(state, curDragElement) {
+    state.curDragElement = curDragElement;
+  },
+  setCurPage(state, curPageNo) {
+    const pageNo = state.pages[curPageNo] ? curPageNo : 0;
+    state.curPage = state.pages[pageNo];
+    state.curPageNo = pageNo;
+  },
+  setCurPageNo(state, curPageNo) {
+    state.curPageNo = curPageNo;
+  },
+  setPages(state, pages) {
+    state.pages = pages;
+  },
+  setTopics(state, topics) {
+    state.topics = topics;
+  },
+  setTopicSeries(state, topicSeries) {
+    state.topicSeries = topicSeries;
+  },
+  setInsetTarget(state, insetTarget) {
+    state.insetTarget = insetTarget;
+  },
+  addPage(state, page) {
+    state.pages.push(page);
+  },
+  modifyPage(state, page) {
+    state.pages.splice(page._pageNo, 1, page);
+  },
+  setOpenElementEditDialog(state, openElementEditDialog) {
+    state.openElementEditDialog = openElementEditDialog;
+  },
+  initState(state) {
+    state.curElement = {};
+    state.curDragElement = {};
+    state.curPage = {};
+    state.curPageNo = 0;
+    state.topics = [];
+    state.pages = [];
+    state.cardConfig = {};
+    state.paperParams = {};
+    state.openElementEditDialog = false;
+    state.topicSeries = [];
+  }
+};
+
+const fetchElementPositionInfos = (element, topics) => {
+  return topics.findIndex(item => item.id === element.id);
+};
+
+const fetchAllRelateParentElementPositionInfos = (parentElement, topics) => {
+  // 当为解答题时,parentElement传入的值是EXPLAIN
+  let postionInfos = [];
+  topics.forEach((item, eindex) => {
+    if (item["parent"] && item.parent.id === parentElement.id) {
+      let pos = { _elementNo: eindex };
+      if (parentElement.type === "EXPLAIN") {
+        pos.serialNumber = item.serialNumber;
+      }
+      postionInfos.push(pos);
+    }
+  });
+
+  return postionInfos;
+};
+
+const fetchFirstSubjectiveTopicPositionInfo = topics => {
+  return topics.findIndex(item => item.sign === "subjective");
+};
+
+const fetchSameSerialNumberChildrenPositionInfo = (
+  elementChildernElement,
+  topics
+) => {
+  let postionInfos = [];
+  const elementId = elementChildernElement.parent.id;
+  const serialNumber = elementChildernElement.serialNumber;
+
+  topics.forEach((item, eindex) => {
+    if (
+      item.parent &&
+      item.parent.id === elementId &&
+      item.serialNumber === serialNumber
+    ) {
+      postionInfos.push({
+        _elementNo: eindex,
+        _elementId: item.id
+      });
+    }
+  });
+  return postionInfos;
+};
+
+const groupByParams = (datas, paramKey) => {
+  let elementGroupInfos = [];
+  for (let i = 0, len = datas.length; i < len; i++) {
+    if (i === 0 || datas[i][paramKey] !== datas[i - 1][paramKey]) {
+      elementGroupInfos.push([datas[i]]);
+    } else {
+      elementGroupInfos[elementGroupInfos.length - 1].push(datas[i]);
+    }
+  }
+  return elementGroupInfos;
+};
+
+const findElementById = (id, topics) => {
+  let curElement = null;
+  topics.forEach(element => {
+    if (curElement) return;
+    if (element.id === id) {
+      curElement = element;
+      return;
+    }
+
+    if (element["elements"]) {
+      element["elements"].forEach(elem => {
+        if (elem.id === id) curElement = elem;
+      });
+    }
+  });
+  return curElement;
+};
+
+const checkElementisCovered = (id, type) => {
+  const elementDom = document.getElementById(id);
+
+  if (type === "EXPLAIN") {
+    const elemTitleDome = elementDom.querySelector(".elem-title");
+    const limitHeight = elemTitleDome
+      ? elementDom.offsetHeight - elemTitleDome.offsetHeight
+      : elementDom.offsetHeight;
+
+    let elementHeights = [];
+    elementDom
+      .querySelector(".elem-explain-elements")
+      .childNodes.forEach(node => {
+        if (!node.className.includes("elem-explain-element")) return;
+        elementHeights.push(
+          node.firstChild.offsetHeight + node.firstChild.offsetTop
+        );
+      });
+    return elementHeights.some(item => item > limitHeight);
+  }
+
+  if (type === "COMPOSITION") {
+    const elemTitleDome = elementDom.querySelector(".elem-title");
+    const limitHeight = elemTitleDome
+      ? elementDom.offsetHeight - elemTitleDome.offsetHeight
+      : elementDom.offsetHeight;
+
+    let elementHeights = [];
+    elementDom
+      .querySelector(".elem-composition-elements")
+      .childNodes.forEach(node => {
+        if (!node.className.includes("elem-composition-element")) return;
+        elementHeights.push(
+          node.firstChild.offsetHeight + node.firstChild.offsetTop
+        );
+      });
+    return elementHeights.some(item => item > limitHeight);
+  }
+
+  return elementDom.offsetHeight < elementDom.firstChild.offsetHeight;
+};
+
+const createFunc = {
+  EXPLAIN(element) {
+    return getExplainElements(element);
+  },
+  FILL_QUESTION(element) {
+    return getFillQuesitonElements(element, state.cardConfig);
+  },
+  FILL_LINE(element) {
+    return getFillLineElements(element);
+  },
+  COMPOSITION(element) {
+    return [getCompositionElements(element)];
+  }
+};
+
+const actions = {
+  initTopicsFromPages({ state, commit }) {
+    let topics = [];
+    state.pages.forEach(page => {
+      page.columns.forEach(column => {
+        column.elements.forEach(element => {
+          if (
+            element.type === "TOPIC_HEAD" ||
+            (element.type === "CARD_HEAD" && element.isSimple)
+          )
+            return;
+          topics.push(element);
+        });
+      });
+    });
+    commit("setTopics", topics);
+  },
+  actElementById({ state, commit }, id) {
+    const curElement = findElementById(id, state.topics);
+    if (!curElement) return;
+
+    commit("setCurElement", curElement);
+  },
+  resetTopicSeries({ state, commit }) {
+    let curTopicId = "",
+      curTopicNo = 0,
+      topicSeries = [];
+    state.topics.forEach(topic => {
+      if (!topic.parent) return;
+      if (curTopicId !== topic.parent.id) {
+        curTopicId = topic.parent.id;
+        curTopicNo++;
+        topicSeries.push({
+          id: curTopicId,
+          topicNo: curTopicNo,
+          type: topic.type,
+          sign: topic.sign
+        });
+      }
+      topic.topicNo = curTopicNo;
+    });
+    commit("setTopicSeries", topicSeries);
+  },
+  // 新增试题 --------------->
+  addElement({ state, commit, dispatch }, element) {
+    let pos = null;
+    // 客观题和主观题分别对待
+    if (state.insetTarget.id) {
+      // 存在插入目标元素
+      if (
+        state.insetTarget.type === "FILL_QUESTION" &&
+        element.type !== "FILL_QUESTION"
+      ) {
+        pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
+      } else {
+        let relateTopicPos = [];
+        state.topics.forEach((item, index) => {
+          if (item.parent && item.parent.id === state.insetTarget.id)
+            relateTopicPos.push(index);
+        });
+        pos = relateTopicPos.slice(-1)[0] + 1;
+      }
+    } else {
+      // 不存在插入目标元素
+      if (element.sign === "objective") {
+        pos = fetchFirstSubjectiveTopicPositionInfo(state.topics);
+      }
+    }
+
+    let preElements = createFunc[element.type](element);
+    preElements.forEach((preElement, index) => {
+      if (pos && pos !== -1) {
+        state.topics.splice(pos + index, 0, preElement);
+      } else {
+        state.topics.push(preElement);
+      }
+    });
+    dispatch("resetTopicSeries");
+    commit("setInsetTarget", {});
+
+    commit("setCurElement", element);
+  },
+  // 修改试题 --------------->
+  modifyTopic({ state }, element) {
+    // 单独编辑某个细分题
+    const pos = fetchElementPositionInfos(element, state.topics);
+    state.topics.splice(pos, 1, element);
+  },
+  modifyComposition({ state }, element) {
+    const positionInfos = fetchAllRelateParentElementPositionInfos(
+      element,
+      state.topics
+    );
+    positionInfos.forEach(({ _elementNo }) => {
+      state.topics[_elementNo].topicNo = element.topicNo;
+      state.topics[_elementNo].parent = objAssign(
+        state.topics[_elementNo].parent,
+        element
+      );
+    });
+  },
+  modifyExplain({ state }, element) {
+    // 解答题既是拆分题,又是可复制题
+    const positionInfos = fetchAllRelateParentElementPositionInfos(
+      element,
+      state.topics
+    );
+    const elementGroupPosInfos = groupByParams(positionInfos, "serialNumber");
+    const orgElementCount = elementGroupPosInfos.length;
+    if (orgElementCount > element.questionsCount) {
+      // 原小题数多于新小题数,要删除原多于的小题;
+      let needDeleteInfos = elementGroupPosInfos.splice(
+        element.questionsCount,
+        orgElementCount - element.questionsCount
+      );
+      needDeleteInfos.reverse().forEach(item => {
+        item.reverse().forEach(pos => {
+          state.topics.splice(pos._elementNo, 1);
+        });
+      });
+    }
+
+    const newElements = getExplainElements(element);
+    const lastPos = elementGroupPosInfos.slice(-1)[0].slice(-1)[0];
+    let lastNewElementPos = lastPos._elementNo;
+    for (let i = 0; i < element.questionsCount; i++) {
+      if (elementGroupPosInfos[i]) {
+        elementGroupPosInfos[i].forEach(pos => {
+          let child = state.topics[pos._elementNo];
+          child.serialNumber = i + element.startNumber;
+          child.parent = { ...element };
+          child.topicNo = element.topicNo;
+        });
+      } else {
+        state.topics.splice(++lastNewElementPos, 0, newElements[i]);
+      }
+    }
+  },
+  modifySplitTopic({ state }, element) {
+    // 非作文题都是拆分题,即同一个题拆分成多个小题展示
+    const positionInfos = fetchAllRelateParentElementPositionInfos(
+      element,
+      state.topics
+    );
+    // 缓存已编辑的小题高度信息。
+    // const elementHeights = positionInfos.map(
+    //   pos => state.topics[pos._elementNo].h
+    // );
+    // 删除所有小题
+    positionInfos.reverse().forEach(pos => {
+      state.topics.splice(pos._elementNo, 1);
+    });
+    // 创建新的小题元素
+    const newElements = createFunc[element.type](element);
+    const pos = positionInfos.pop();
+    newElements.forEach((newElement, index) => {
+      // 不再复用缓存高度,修改于2022-03-10 09:23
+      // newElement.h = Math.max(elementHeights[index] || 0, newElement.h);
+      state.topics.splice(pos._elementNo + index, 0, newElement);
+    });
+  },
+  modifyElement({ commit, dispatch }, element) {
+    if (element.type === "COMPOSITION") {
+      dispatch("modifyComposition", element);
+    } else if (element.type === "EXPLAIN") {
+      dispatch("modifyExplain", element);
+    } else {
+      dispatch("modifySplitTopic", element);
+    }
+    dispatch("resetTopicSeries");
+    commit("setCurElement", element);
+  },
+  modifyCardHead({ state }, element) {
+    state.topics.splice(0, 1, element);
+  },
+  // 修改试题包含元素
+  modifyElementChild({ state, commit }, element) {
+    // 修改解答题小题和作文题的子元素
+    const pos = fetchElementPositionInfos(element.container, state.topics);
+    const columnElements = state.topics[pos].elements;
+    const childIndex = columnElements.findIndex(item => item.id === element.id);
+    element.id = getElementId();
+    // 作文题中的多线条和网格元素重新计算高度。
+    if (element.container.type === "COMPOSITION") {
+      if (element.type === "LINES") {
+        element.h = element.lineCount * (element.lineSpacing + 1);
+      }
+      if (element.type === "GRIDS") {
+        if (element.halving) {
+          const columnWs = {
+            2: 703,
+            3: 461,
+            4: 349
+          };
+          const columnSize =
+            columnWs[state.cardConfig.columnNumber] / element.columnCount;
+          element.h = element.rowCount * (columnSize + element.rowSpace) + 1;
+        } else {
+          element.h =
+            element.rowCount * (element.columnSize + element.rowSpace) + 1;
+        }
+      }
+    }
+    if (childIndex === -1) {
+      columnElements.push(element);
+    } else {
+      columnElements.splice(childIndex, 1, element);
+    }
+
+    commit("setCurElement", element);
+  },
+  // 粘贴试题内的元素
+  pasteExplainElementChild({ state }, { curElement, pasteElement }) {
+    let element = {
+      id: curElement.container ? curElement.container.id : curElement.id
+    };
+    const pos = fetchElementPositionInfos(element, state.topics);
+    if (pos === -1) return;
+
+    element = state.topics[pos];
+    const newElement = Object.assign({}, pasteElement, {
+      id: getElementId(),
+      container: {
+        id: element.id,
+        type: element.type
+      }
+    });
+    element.elements.push(newElement);
+  },
+  // 删除试题 --------------->
+  removeElement({ state, commit, dispatch }, element) {
+    const positionInfos = fetchAllRelateParentElementPositionInfos(
+      element.parent,
+      state.topics
+    );
+    positionInfos.reverse().forEach(pos => {
+      state.topics.splice(pos._elementNo, 1);
+    });
+
+    dispatch("resetTopicSeries");
+
+    commit("setCurElement", {});
+  },
+  // 删除试题包含元素 --------------->
+  removeElementChild({ state, commit }, element) {
+    // 删除解答题小题和作文题的子元素
+    const pos = fetchElementPositionInfos(element.container, state.topics);
+    const columnElements = state.topics[pos].elements;
+    const childIndex = columnElements.findIndex(item => item.id === element.id);
+    columnElements.splice(childIndex, 1);
+
+    commit("setCurElement", {});
+  },
+  // 扩展答题区操作 --------------->
+  copyExplainChildren({ state }, element) {
+    let curElement = {
+      id: element.container ? element.container.id : element.id
+    };
+    const pos = fetchElementPositionInfos(curElement, state.topics);
+    curElement = state.topics[pos];
+
+    let newElement = Object.assign({}, curElement, {
+      id: getElementId(),
+      elements: [],
+      h: 200,
+      isExtend: true,
+      showTitle: false
+    });
+
+    state.topics.splice(pos + 1, 0, newElement);
+    // 更新小题答题区isLast
+    let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
+      curElement,
+      state.topics
+    );
+    positionInfos.forEach((pos, pindex) => {
+      state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
+    });
+  },
+  deleteExplainChildren({ state }, element) {
+    let curElement = {
+      id: element.container ? element.container.id : element.id
+    };
+    const curPos = fetchElementPositionInfos(curElement, state.topics);
+    curElement = state.topics[curPos];
+
+    let positionInfos = fetchSameSerialNumberChildrenPositionInfo(
+      curElement,
+      state.topics
+    );
+    if (positionInfos.length < 2) return;
+    const pindex = positionInfos.findIndex(
+      item => item._elementId === curElement.id
+    );
+    const pos = positionInfos[pindex]._elementNo;
+    positionInfos.splice(pindex, 1);
+    const nextPos = positionInfos[0]._elementNo;
+    // 当删除的是非扩展区域时,则下一个答题区要被设置成非扩展区
+    if (!curElement.isExtend) {
+      state.topics[nextPos].isExtend = false;
+    }
+    // 当删除的是含有标题答题区时,则需要将下一个答题区开启显示标题。
+    if (curElement.showTitle) {
+      state.topics[nextPos].showTitle = true;
+    }
+    state.topics.splice(pos, 1);
+    // 更新小题答题区isLast
+    positionInfos = fetchSameSerialNumberChildrenPositionInfo(
+      curElement,
+      state.topics
+    );
+    positionInfos.forEach((pos, pindex) => {
+      state.topics[pos._elementNo].isLast = pindex + 1 === positionInfos.length;
+    });
+  },
+  // 大题顺序操作 --------------->
+  topicMoveUp({ state, dispatch }, topicId) {
+    const curTopicPos = state.topicSeries.findIndex(
+      item => item.id === topicId
+    );
+    const prevTopicId = state.topicSeries[curTopicPos - 1].id;
+    let relateTopicPos = [];
+    state.topics.forEach((item, index) => {
+      if (item.parent && item.parent.id === topicId) relateTopicPos.push(index);
+    });
+    const prevTopicFirstIndex = state.topics.findIndex(
+      item => item.parent && item.parent.id === prevTopicId
+    );
+    const relateTopics = state.topics.splice(
+      relateTopicPos[0],
+      relateTopicPos.length
+    );
+    relateTopics.reverse().forEach(topic => {
+      state.topics.splice(prevTopicFirstIndex, 0, topic);
+    });
+
+    dispatch("resetTopicSeries");
+  },
+  // 重构页面
+  resetElementProp({ state }, isResetId = false) {
+    state.topics.forEach(element => {
+      const elementDom = document.getElementById(element.id);
+      if (elementDom) {
+        element.h = elementDom.offsetHeight;
+        element.w = elementDom.offsetWidth;
+      }
+      if (isResetId) {
+        element.id = getElementId();
+      }
+    });
+  },
+  rebuildPages({ state, commit }) {
+    const columnNumber = state.cardConfig.columnNumber;
+    const pageSize = state.cardConfig.pageSize;
+    // 更新元件最新的高度信息
+    // 整理所有元件
+    const cardHeadElement = state.topics[0];
+    state.topics.forEach(element => {
+      const elementDom = document.getElementById(`preview-${element.id}`);
+
+      if (elementDom) {
+        element.h = elementDom.offsetHeight;
+        element.w = elementDom.offsetWidth;
+        // 解答题小题与其他题有些区别。
+        // 其他题都是通过内部子元素自动撑高元件,而解答题则需要手动设置高度。
+        const ESCAPE_ELEMENTS = ["CARD_HEAD"];
+        element.isCovered =
+          !ESCAPE_ELEMENTS.includes(element.type) &&
+          checkElementisCovered(`preview-${element.id}`, element.type);
+      }
+    });
+
+    // 动态计算每列可以分配的元件
+    const columnHeight = document.getElementById("topic-column").offsetHeight;
+    const simpleCardHeadHeight = document.getElementById("simple-card-head")
+      .offsetHeight;
+    let pages = [];
+    let page = {};
+    let columns = [];
+    let curColumnElements = [];
+    let curColumnHeight = 0;
+
+    const initCurColumnElements = () => {
+      curColumnElements = [];
+      curColumnHeight = 0;
+      const groupColumnNumber = columnNumber * 2;
+      // 奇数页第一栏;
+      if (!(columns.length % groupColumnNumber)) {
+        // 非第一页奇数页第一栏
+        if (columns.length) {
+          const cardHeadSimpleElement = Object.assign({}, cardHeadElement, {
+            id: getElementId(),
+            isSimple: true,
+            h: simpleCardHeadHeight
+          });
+          curColumnElements.push(cardHeadSimpleElement);
+          curColumnHeight += simpleCardHeadHeight;
+        } else {
+          curColumnElements.push(cardHeadElement);
+          curColumnHeight += cardHeadElement.h;
+        }
+      }
+    };
+
+    const checkElementIsCurColumnFirstType = element => {
+      const topicHeadIndex = curColumnElements.findIndex(
+        elem => elem.type === "TOPIC_HEAD" && elem.sign === element.sign
+      );
+      return topicHeadIndex === -1;
+    };
+
+    // 放入元素通用流程
+    const pushElement = element => {
+      // 当前栏中第一个题型之前新增题型头元素(topic-head)。
+      // 题型头和当前题要组合加入栏中,不可拆分。
+      let elementList = [element];
+      if (checkElementIsCurColumnFirstType(element)) {
+        elementList.unshift(
+          getTopicHeadModel(
+            state.cardConfig[`${element.sign}Attention`],
+            element.sign,
+            !curColumnElements.length
+          )
+        );
+      }
+
+      const elementHeight = calcSum(elementList.map(elem => elem.h));
+      if (curColumnHeight + elementHeight > columnHeight) {
+        // 当前栏第一个元素高度超过一栏时,不拆分,直接放在当前栏。
+        // 解决可能空栏的情况
+        const curElementIsFirst = !curColumnElements.length;
+        if (curElementIsFirst) {
+          curColumnElements = [...curColumnElements, ...elementList];
+          curColumnHeight += elementHeight;
+        } else {
+          columns.push([...curColumnElements]);
+          initCurColumnElements();
+          pushElement(element);
+        }
+      } else {
+        curColumnElements = [...curColumnElements, ...elementList];
+        curColumnHeight += elementHeight;
+      }
+    };
+
+    // 批量添加所有元素。
+    initCurColumnElements();
+    state.topics.slice(1).forEach((element, eindex) => {
+      element.elementSerialNo = eindex;
+      pushElement(element);
+    });
+
+    // 最后一栏的处理。
+    columns.push([...curColumnElements]);
+    // 构建pages
+    columns.forEach((column, cindex) => {
+      const columnNo = cindex % columnNumber;
+      if (!columnNo) {
+        page = getNewPage(pages.length, { pageSize, columnNumber });
+      }
+      page.columns[columnNo].elements = column;
+
+      if (columnNo + 1 === columnNumber || cindex === columns.length - 1) {
+        pages.push(deepCopy(page));
+      }
+    });
+    // 保证页面总是偶数页
+    if (pages.length % 2) {
+      pages.push(getNewPage(pages.length, { pageSize, columnNumber }));
+    }
+
+    commit("setPages", pages);
+    commit("setCurPage", state.curPageNo);
+  }
+};
+
+export { fetchSameSerialNumberChildrenPositionInfo, checkElementisCovered };
+
+export default {
+  namespaced: true,
+  state,
+  mutations,
+  actions
+};