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: [], // 大题顺序号,不排重 topicNoSeries: [], // 大题顺序号,按顺序排重 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; }, setTopicNoSeries(state, topicNoSeries) { state.topicNoSeries = topicNoSeries; }, 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.cardConfig = {}; state.paperParams = {}; state.curElement = {}; state.curDragElement = {}; state.curPage = {}; state.curPageNo = 0; state.pages = []; state.topics = []; state.topicSeries = []; state.topicNoSeries = []; state.insetTarget = {}; state.openElementEditDialog = false; }, }; 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)]; }, FORBID_AREA(element) { return [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 = [], topicNoSeries = []; state.topics.forEach((topic) => { if (!topic.parent) return; let data = { id: topic.parent.id, topicNo: topic.parent.topicNo, type: topic.type, sign: topic.sign, }; if (curTopicId !== topic.parent.id) { curTopicId = topic.parent.id; topicSeries.push(data); } if (curTopicNo !== topic.parent.topicNo) { curTopicNo = topic.parent.topicNo; topicNoSeries.push(data); } }); commit("setTopicSeries", topicSeries); commit("setTopicNoSeries", topicNoSeries); }, scrollToElementPage({ state, commit }, element) { let elementPapeNo = null; state.pages.forEach((page, pageNo) => { if (elementPapeNo !== null) return; page.columns.forEach((column) => { if (elementPapeNo !== null) return; column.elements.forEach((elem) => { if (elem.id === element.id || elem.parent?.id === element.id) { elementPapeNo = pageNo; } }); }); }); // console.log(elementPapeNo); if (elementPapeNo === null || elementPapeNo === state.curPageNo) return; let pageTops = state.pages.map((page, pageNo) => { return document.getElementById(`edit-page-box-${pageNo}`).offsetTop; }); pageTops = pageTops.map((item) => item - pageTops[0]); document.getElementById("design-main").scrollTop = pageTops[elementPapeNo]; commit("setCurPageNo", elementPapeNo); }, // 新增试题 ---------------> 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); }, addForbidArea({ state, commit }, { element, beforeElementId }) { const beforeElementPos = fetchElementPositionInfos( { id: beforeElementId }, state.topics ); state.topics.splice(beforeElementPos + 1, 0, element); 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) { if (!element.parent) { state.topics = state.topics.filter((item) => item.id !== element.id); commit("setCurElement", {}); return; } 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, };