'use strict'; /** * * * @author Zhong * @date 2018/6/11 * @version */ //清单指引/精灵获取完清单数据后的回调函数 let doAfterLoadGuidance = null; const billsGuidance = (function () { //更新类型 const updateType = {update: 'update', create: 'create'}; //库类型 const libType = {'guidance': 1, 'elf': 2}; //清单指引、清单精灵 const libSel = $('#stdBillsGuidanceLibSelect'); //工作内容 let stdBillsJobData = []; //项目特征 let stdBillsFeatureData = []; //正在插入 let isInserting = false; const bills = { dom: $('#billsGuidance_bills'), workBook: null, cache: [], tree: null, controller: null, treeSetting: { emptyRowHeader: true, rowHeaderWidth: 15, treeCol: 1, emptyRows: 0, headRows: 1, headRowHeight: [40], defaultRowHeight: 21, cols: [ { width: 35, readOnly: false, showHint: false, head: { titleNames: ["选用"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "select", vAlign: 1, hAlign: 1, font: "Arial" } }, { width: 105, readOnly: true, showHint: true, head: { titleNames: ["项目编码"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "code", vAlign: 1, hAlign: 0, font: "Arial" } }, { width: 190, readOnly: true, head: { titleNames: ["项目名称"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "name", vAlign: 1, hAlign: 0, font: "Arial" } }, { width: 60, readOnly: true, head: { titleNames: ["计量单位"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "unit", vAlign: 1, hAlign: 1, font: "Arial" } } ] }, headers: [ {name: '选用', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'}, {name: '项目编码', dataCode: 'code', width: 105, vAlign: 'center', hAlign: 'left', formatter: '@'}, {name: '项目名称', dataCode: 'name', width: 190, vAlign: 'center', hAlign: 'left', formatter: '@'}, {name: '单位', dataCode: 'unit', width: 60, vAlign: 'center', hAlign: 'center', formatter: '@'}, ], rowHeaderWidth:1, events: { ButtonClicked: function (sender, args) { if(args.sheet.isEditing()){ args.sheet.endEdit(true); } if (args.col !== 0) { return; } const selectedNode = bills.tree.items[args.row]; const curValue = args.sheet.getValue(args.row, args.col); let count = selectedNode.posterityCount(); if (count) { renderSheetFunc(args.sheet, () => { let row = args.row; while (count--) { row++; args.sheet.setValue(row, args.col, curValue); } }); } }, CellDoubleClick: function (sender, args) { if (args.col === 0) { return; } if(!bills.tree){ return; } let node = bills.tree.items[args.row]; if(!node){ return; } //展开收起(非最底层节点且双击的是第一列) if (args.col === 1 && node.children.length > 0) { node.setExpanded(!node.expanded); //设置展开收起状态 sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items)); renderSheetFunc(args.sheet, function () { let iCount = node.posterityCount(), i, child; for (i = 0; i < iCount; i++) { child = bills.tree.items[args.row + i + 1]; args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea); } args.sheet.invalidateLayout(); }); args.sheet.repaint(); } else if (!projectReadOnly && !isInserting && !projectObj.project.isBillsLocked() && (![1].includes(args.col) || node.children.length === 0)) { //选中部分的最底层(只是选中部分的最底) let lowestNodes = [bills.tree.items[args.row]]; insertBills(lowestNodes); } } } }; if (compilationName === '内蒙古高速公路日常养护估算(2021)' || compilationName === '广东农村养护(2021)') { bills.treeSetting.cols.push( { width: 100, readOnly: true, showHint: false, head: { titleNames: ["默认指标基价"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "baseEstUnitPrice", vAlign: 1, hAlign: 2, font: "Arial" } } ); bills.headers.push({name: '默认指标基价', dataCode: 'baseEstUnitPrice', width: 100, vAlign: 'center', hAlign: 'center', formatter: '@'}); } // 获取对比树片段数据的方法,此方法可能会被覆盖,方法存在一个对象中,使得外部可以覆盖相关方法 // 在农村公路2020中,主树对比片段与这里的逻辑是不相同的 const overwrite = { getFragment() { return { parent: null, mainTreeFragment: projectObj.project.Bills.tree.roots } } }; // 反转插入顺序 // 正常顺序插入时,next数据可能还没插入,会造成错误,需要反过来插入 // insertData为正向排好序的数据 function reverseInsertData(insertData) { const reverseData = []; const parentMap = {}; const IDMap = {}; insertData.forEach(item => { IDMap[item.data.ID] = item; (parentMap[item.data.ParentID] || (parentMap[item.data.ParentID] = [])).push(item); }); // 找到顶层数据: 数据中没有找不到对应parent的数据 const topLevelData = insertData .filter(item => !IDMap[item.data.ParentID]) .reverse(); function getReverseData(items) { items.forEach(item => { reverseData.push(item); const children = parentMap[item.data.ID]; if (children && children.length) { getReverseData(children.reverse()); } }) } getReverseData(topLevelData); return reverseData; } //插入清单 async function insertBills(lowestNodes) { try { let selTree = getSelTree(lowestNodes); const { errMsg, parent, mainTreeFragment } = overwrite.getFragment(); if (errMsg) { alert(errMsg); return; } let compareData = compareTree(parent, mainTreeFragment, selTree.roots); chkAndSetBillsUnitPrice(compareData.postData); //设置指标基价(默认单价) let sheet = projectObj.mainSpread.getActiveSheet(), row = sheet.getActiveColumnIndex(), col = sheet.getActiveColumnIndex(); if (compareData.postData.length > 0) { //如果插入的是固定清单,则需要判断该固定清单在造价书中是否已存在,造价书中不可存在相同的固定清单 let fixedDatas = compareData.postData.filter((data) => data.updateType === updateType.create && Array.isArray(data.updateData.flags)); if (fixedDatas.length > 0) { //提示已存在此固定清单并且定位 let firstFixed = fixedDatas[0].updateData; let existNode = projectObj.project.mainTree.items.find((node) => node.data && node.data.flagsIndex && node.data.flagsIndex.fixed && node.data.flagsIndex.fixed.flag === firstFixed.flags[0].flag); if (existNode) { alert(`固定清单“${firstFixed.name}”已被第${existNode.serialNo() + 1}行清单占用。`); locateAtSpread(sheet, existNode.serialNo(), col); return; } } isInserting = true; await ajaxPost('/bills/insertBills', { postData: compareData.postData }); // 更新前端树 const toReverseData = []; const cacheData = []; compareData.postData.forEach(item => { const cacheItem = { type: ModuleNames.bills, action: item.updateType === updateType.create ? 'add' : 'update', data: item.updateData, } if (item.updateType === updateType.create) { toReverseData.push(cacheItem); } else { cacheData.push(cacheItem); } }); cacheData.push(...reverseInsertData(toReverseData)); console.log(cacheData); const nodes = projectObj.project.updateNodesCache(cacheData, false); projectObj.mainController.refreshTreeNode(nodes); row = nodes[nodes.length - 1].serialNo(); //有新的节点插入,也有可能定位至旧节点(批量选用的情况下) if (compareData.locateNode) { //该清单节点在主树的位置 row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo(); } locateAtSpread(sheet, row, col); } else if (compareData.locateNode) { //该清单节点在主树的位置 row = projectObj.project.mainTree.nodes[projectObj.project.mainTree.prefix + compareData.locateNode.data.ID].serialNo(); locateAtSpread(sheet, row, col); } } catch (err) { console.log(err); if (!$('hintBox_form').is(':visible')) { alert(err); } } finally { isInserting = false; $.bootstrapLoading.end(); } } function locateAtSpread(sheet, row, col) { sheet.setSelection(row, col, 1, 1); projectObj.mainController.setTreeSelected(projectObj.mainController.tree.items[row]);//触发树节点选中事件 sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center); } /* * * 1.选中的树结构(清单规则选中的节点及其所有父项)与主树对比(主树中节点“编码-名称-单位”与选中树~组合相同视为同一节点), * 将主树中不含有的选中节点全部插入 * 2.插入位置由1对比得出,主树有与选中树结构相同层次结构的片段,这个相同片段为插入位置 * 3.选中树结构除去相同片段,为需要插入的节点,插入时,遇到同层节点,根据编码的字符编码值确定顺序 * a.code <= b.code,则a节点在b节点前 * */ //获取选中的树(将选中的节点及其所有父项,组合成一棵树,没有重复节点) //@param {Array}lowestNodes(选中的最底层节点) @return {Array} function getSelTree(lowestNodes) { let allNodes = []; //获取树上所有的节点 for (let node of lowestNodes) { while (node && !allNodes.includes(node)) { allNodes.push(node); node = node.parent; } } // 根据原节点serialNo排序,排序后,后一项节点只可能前节点的后兄弟项,或子项 allNodes.sort(function (a, b) { let aV = a.serialNo(), bV = b.serialNo(); if (aV > bV) { return 1; } else if (aV < bV) { return -1; } return 0; }); //生成树数据 let treeData = []; //旧ID与新ID映射 let IDMapping = {}; const ParentIDMap = {}; for (let i = 0; i < allNodes.length; i++) { //原节点 let thisN = allNodes[i]; let newNodeData = { code: thisN.data.code, name: thisN.data.name, ID: uuid.v1(), NextSiblingID: -1, ParentID: -1, orgID: thisN.data.ID }; IDMapping[newNodeData.orgID] = newNodeData.ID; (ParentIDMap[thisN.data.ParentID] || (ParentIDMap[thisN.data.ParentID] = [])).push(newNodeData); // 兄弟节点已经拍好序(serialNo排序) treeData.push(newNodeData); } // 重新设置ParentID、NextSiblingID for (const orgParentID in ParentIDMap) { const newNodeDatas = ParentIDMap[orgParentID]; const newParentID = orgParentID == -1 ? -1 : IDMapping[orgParentID]; newNodeDatas.forEach((newNodeData, index) => { newNodeData.ParentID = newParentID; const preNewNodeData = newNodeDatas[index - 1]; if (preNewNodeData) { preNewNodeData.NextSiblingID = newNodeData.ID; } }); } let selTree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true}); selTree.loadDatas(treeData); return selTree; } /* * 选中树与清单主树进行对比,(自上而下,从roots开始)获取插入、更新数据 * 找到的清单树中清单子项含有非清单项或输入了计算基数,则中止该清单的对比,不可插入数据 * */ const firstBillsSymbol = Symbol('第一行固定清单'); //获取节点的匹配数据:编码-名称-单位 //@param {Object}node @return {String} function getMatchContent(node) { // 标准清单第一个根节点与项目清单第一个根节点写死能对应匹配上 const firstStdNode = bills.tree.roots[0]; const firstProjectNode = projectObj.project.Bills.tree.roots[0]; if (node === firstStdNode || node === firstProjectNode) { return firstBillsSymbol; } return `${node.data.code ? node.data.code : '*'}-${node.data.name ? node.data.name : '*'}-${node.data.unit ? node.data.unit : '*'}`; } //**对比清单主树节点片段与选中树节点片段,获取需要更新和插入的数据** //@param {Object}parent - 主树片段的父节点 {Array}mainTreeFragment {Array}stdTreeFragment function compareTree(parent, mainTreeFragment, stdTreeFragment) { //需要插入、更新的数据 let postData = [], //跟树结构自动定位至的清单节点(最近匹配到的节点) locateNode = null; if (!mainTreeFragment || !stdTreeFragment) { return postData; } comparePeer(parent, mainTreeFragment, stdTreeFragment); return {postData, locateNode}; /* * 该清单节点是否可以继续往下递归匹配,即该节点是否还可插入子项(在该层匹配到的时候判断) * 1.该底层节点不能含有非清单子项 * 2.该节点不能含有计算基数 * todo 3.数量单价等相关概念,等有了补上 * @param {Object}billsNode(清单树中的某节点) @return {Boolean} * */ function canRecursive(billsNode) { //主树节点含有子节点,清单节点不含子节点,说明该节点含有非清单子项 //这里需要去mainTree查,不能在清单树查,因为清单树的节点children里只会有清单,就算实际上有定额,这个children里也不含该定额 let mainTreeNode = projectObj.project.mainTree.getNodeByID(billsNode.data.ID); if (!mainTreeNode) { return false; } if (mainTreeNode && mainTreeNode.children.length > 0 && billsNode.children.length === 0) { return false; } else if (billsNode.data.calcBase) { return false; } return true; } //获取某选中节点要往清单主树同层插入的位置 function insertPos(peerNodes, node) { //node选中树中没有记录原清单的数据,只记录了原清单ID,需要node的数据,则要找回原清单 let orgNode = bills.tree.nodes[`${bills.tree.prefix}${node.data.orgID}`]; if (!orgNode) { return -1; } let insertCode = orgNode.data.code ? orgNode.data.code : ''; //插入最顶层节点或者无编码,对比选中节点后兄弟节点和同层主树节点的名称,插在的匹配的节点前面 function mathNext(selNode, compareNodes) { let selNext = selNode.nextSibling; while (selNext) { for (let i = 0; i < compareNodes.length; i++) { if (compareNodes[i].data.name === selNext.data.name) { return i; } } selNext = selNext.nextSibling; } return compareNodes.length; } if (orgNode.depth() === 0 || insertCode === '') { return mathNext(orgNode, peerNodes); //return peerNodes.length; } // 比较编码大小,可能需要对比xx-xx xx-xx-xx .... function compareCode(codeA, codeB) { const codeASplits = codeA.split('-'); const codeBSplits = codeB.split('-'); const count = Math.max(codeASplits.length, codeBSplits.length); for (let i = 0; i < count; i++) { const perA = codeASplits[i]; const perB = codeBSplits[i]; if (perA && !perB) { return 1; } else if (!perA && perB) { return -1; } else if (perA && perB) { const numberCompare = !isNaN(perA) && !isNaN(perB); const compareRst = numberCompare ? +perA - +perB : perA.localeCompare(perB); if (compareRst === 0) { continue; } else { return compareRst; } } } } for (let i = 0; i < peerNodes.length; i++) { let thisNode = peerNodes[i]; let thisCode = thisNode.data.code ? thisNode.data.code : ''; //确定同层节点的顺序,编码小于等于在前,大于在后 const compareRst = compareCode(insertCode, thisCode); if (compareRst <= 0) { return i; } else { continue; } } /* for (let i = 0; i < peerNodes.length; i++) { let thisNode = peerNodes[i]; let thisCode = thisNode.data.code ? thisNode.data.code : ''; //确定同层节点的顺序,编码小于等于在前,大于在后 if (insertCode <= thisCode) { return i; } else if (insertCode > thisCode) { continue; } } */ return peerNodes.length; } //获取插入清单数据 function getInsertData(insertObj) { let stdNode = bills.tree.nodes[`${bills.tree.prefix}${insertObj.orgID}`]; if (!stdNode) { return null; } let stdData = {}; stdData.projectID = projectObj.project.projectInfo.ID; stdData.isAdd = 1; stdData.ID = insertObj.ID; stdData.ParentID = insertObj.ParentID; stdData.NextSiblingID = insertObj.NextSiblingID; //顶层节点是大项费用 stdData.type = stdNode.parent ? billType.BILL : billType.DXFY; stdData.code = stdNode.data.code; stdData.name = stdNode.data.name; stdData.unit = stdNode.data.unit; stdData.ruleText = stdNode.data.ruleText; stdData.comments = stdNode.data.comments; stdData.programID = stdNode.data.engineering; stdData.billsLibId = stdNode.data.billsLibId; if (stdNode.data.fixedFlag) { stdData.flags = [{flag : stdNode.data.fixedFlag, fieldName : 'fixed'}]; stdData.flagsIndex = {fixed: {fieldName: 'fixed', flag: stdNode.data.fixedFlag}}; //前端用 } if (projectObj.project.property.valuationType === commonConstants.ValuationType.BOQ) { stdData.unitPriceAnalysis = 1; } return stdData; } //从同层节点中获取更新数据 //@param {Object}parentNode(同层节点挂载的父节点,清单主树中) {Array}peerNodes(同层节点,含有清单树和选中树节点) // {Array}billsNodes(该层清单树节点) {Array}selNodes(该层选中树节点) @return {Array} function getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes) { let rst = []; //向下获取插入数据直到最底层 function getDataTillDeepest(node) { if (node) { let insertData = getInsertData(node.data); if (insertData) { rst.push({updateType: updateType.create, updateData: insertData}); } for (let child of node.children) { getDataTillDeepest(child); } } } for (let i = 0; i < peerNodes.length; i++) { let thisNode = peerNodes[i], nextNode = peerNodes[i + 1]; //更新原清单节点NextSiblingID if (billsNodes.includes(thisNode) && selNodes.includes(nextNode)) { rst.push({updateType: updateType.update, updateData: {ID: thisNode.data.ID, NextSiblingID: nextNode.data.ID}}); } else if (selNodes.includes(thisNode) && billsNodes.includes(nextNode)) { //变更选中节点的NextSiblingID thisNode.data.NextSiblingID = nextNode.data.ID; } //所有选中的同层节点设为清单树父节点的子项,获取插入数据(插入该同层节点及其所有子节点) if (selNodes.includes(thisNode)) { thisNode.data.ParentID = parentNode ? parentNode.data.ID : -1; getDataTillDeepest(thisNode); } } return rst; } //同层节点之间比较,匹配到的则继续往下匹配,匹配不到的节点则按照规定的顺序进行排序,插入更新(更新清单树节点中NextSiblingID改变的节点) function comparePeer(parentNode, billsNodes, selNodes) { let peerNodes = [].concat(billsNodes); //同层节点 let matchNode = null; //匹配到的清单主树节点 for (let selNode of selNodes) { let stdNode = bills.tree.nodes[`${bills.tree.prefix}${selNode.data.orgID}`], selMatch = getMatchContent(stdNode), isMatched = false; for( let billsNode of billsNodes) { let billsMatch = getMatchContent(billsNode); if (selMatch === billsMatch) {//只进行一次成功匹配 // 特殊处理:如果匹配成功,但是清单名称不同,将造价书清单的名称设成清单库中的清单名称 const stdName = selNode.data.name || ''; const billsName = billsNode.data.name || ''; if (billsName !== stdName) { postData.push({updateType: updateType.update, updateData: {ID: billsNode.data.ID, name: stdName}}); } matchNode = billsNode; isMatched = true; if (selNode.children.length === 0) {//成功匹配且为选中的最底节点,则为自动定位节点 locateNode = matchNode; } break; } } if (isMatched && canRecursive(matchNode)) {//匹配成功,且该匹配到的节点可插入子项,递归匹配子项 comparePeer(matchNode, matchNode.children, selNode.children); } else if (!isMatched) { //匹配不成功,其节点与该层清单节点同层,根据编码插入同层数组中 let pos = insertPos(peerNodes, selNode); if (pos >= 0) { peerNodes.splice(pos, 0, selNode); } } } //同层节点比清单树节点多了,说明在该层有选中节点插入清单树中 if (peerNodes.length > billsNodes.length) { let updateData = getUpdateDataFromPeer(parentNode, peerNodes, billsNodes, selNodes); postData = postData.concat(updateData); } } } //项目指引类型 const itemType = { job: 0, ration: 1 }; const guideItem = { dom: $('#billsGuidance_items'), workBook: null, tree: null, controller: null, treeSetting: { treeCol: 1, emptyRows: 0, headRows: 1, headRowHeight: [40], defaultRowHeight: 21, cols: [ { width: 35, readOnly: false, head: { titleNames: ["选择"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "select", vAlign: 1, hAlign: 1, font: "Arial" } }, { width: 420, readOnly: false, head: { titleNames: ["项目指引"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "name", vAlign: 1, hAlign: 0, font: "Arial" } } ] }, headers: [ {name: '选择', dataCode: 'select', width: 35, vAlign: 'center', hAlign: 'center', formatter: '@'}, {name: '项目指引', dataCode: 'name', width: 300, vAlign: 'center', hAlign: 'left', formatter: '@'}, ], rowHeaderWidth:25, events: { EditStarting: function (sender, args) { if(!bills.tree || guideItem.headers[args.col]['dataCode'] === 'name'){ args.cancel = true; } }, ButtonClicked: function (sender, args) { if(args.sheet.isEditing()){ args.sheet.endEdit(true); } refreshInsertRation(); }, CellDoubleClick: function (sender, args) { if(!bills.tree || !bills.tree.selected){ return; } let node = bills.tree.selected.guidance.tree.selected; if(!node){ return; } if(node.children.length === 0){ if(guideItem.headers[args.col]['dataCode'] === 'name'){ insertRations(getInsertRationData([args.row])); } } else { node.setExpanded(!node.expanded); renderSheetFunc(args.sheet, function () { let iCount = node.posterityCount(), i, child; for (i = 0; i < iCount; i++) { child = bills.tree.selected.guidance.tree.items[args.row + i + 1]; args.sheet.setRowVisible(args.row + i + 1, child.visible, args.sheetArea); } args.sheet.invalidateLayout(); }); args.sheet.repaint(); } } } }; const elfItem = { dom: $('#billsGuidance_items'), workBook: null, tree: null, controller: null, treeSetting: { treeCol: 0, emptyRows: 0, headRows: 1, headRowHeight: [40], defaultRowHeight: 21, cols: [ { width: 250, readOnly: true, head: { titleNames: ["施工工序"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "name", vAlign: 1, hAlign: 0, font: "Arial" } }, { width: 250, readOnly: false, head: { titleNames: ["选项"], spanCols: [1], spanRows: [1], vAlign: [1], hAlign: [1], font: ["Arial"] }, data: { field: "options", vAlign: 1, hAlign: 0, font: "Arial" } } ] }, headers: [ {name: '施工工序', dataCode: 'name', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'center', formatter: '@'}, {name: '选项', dataCode: 'options', width: 250, rateWidth: 0.5, vAlign: 'center', hAlign: 'left', formatter: '@'}, ], rowHeaderWidth:25, events: { CellClick: function (sender, args) { if(elfItem.headers[args.col]['dataCode'] === 'options' && args.sheetArea === 3){ if(!args.sheet.getCell(args.row, args.col).locked() && !args.sheet.isEditing()){ args.sheet.startEdit(); } } }, ClipboardPasting: function (sender, info) { info.cancel = true; } } }; const options = { workBook: { tabStripVisible: false, allowContextMenu: false, allowCopyPasteExcelStyle : false, allowExtendPasteRange: false, allowUserDragDrop : false, allowUserDragFill: false, scrollbarMaxAlign : true }, sheet: { protectionOptions: {allowResizeRows: true, allowResizeColumns: true}, clipBoardOptions: GC.Spread.Sheets.ClipboardPasteOptions.values } }; //渲染时方法,停止渲染 //@param {Object}sheet {Function}func @return {void} function renderSheetFunc(sheet, func){ sheet.suspendEvent(); sheet.suspendPaint(); if(func){ func(); } sheet.resumeEvent(); sheet.resumePaint(); } //设置表选项 //@param {Object}workBook {Object}opts @return {void} function setOptions (workBook, opts) { for(let opt in opts.workBook){ workBook.options[opt] = opts.workBook[opt]; } for(let opt in opts.sheet){ workBook.getActiveSheet().options[opt] = opts.sheet[opt]; } } //建表头 //@param {Object}sheet {Array}headers @return {void} function buildHeader(sheet, headers) { let fuc = function () { sheet.setColumnCount(headers.length); sheet.setRowHeight(0, 30, GC.Spread.Sheets.SheetArea.colHeader); //sheet.setColumnWidth(0, sheet.getParent() === bills.workBook ? 15 : 25, GC.Spread.Sheets.SheetArea.rowHeader); if(sheet.getParent() === elfItem.workBook || sheet.getParent() === guideItem.workBook){ sheet.setRowHeight(0, 20, GC.Spread.Sheets.SheetArea.colHeader); } for(let i = 0, len = headers.length; i < len; i++){ sheet.setValue(0, i, headers[i].name, GC.Spread.Sheets.SheetArea.colHeader); sheet.setColumnWidth(i, headers[i].width, GC.Spread.Sheets.SheetArea.colHeader); if(headers[i].formatter){ sheet.setFormatter(-1, i, headers[i].formatter); } sheet.getRange(-1, i, -1, 1).hAlign(GC.Spread.Sheets.HorizontalAlign[headers[i]['hAlign']]); sheet.getRange(-1, i, -1, 1).vAlign(GC.Spread.Sheets.VerticalAlign[headers[i]['vAlign']]); } }; renderSheetFunc(sheet, fuc); } //表监听事件 //@param {Object}workBook @return {void} function bindEvent(workBook, events) { if(Object.keys(events).length === 0){ return; } const Events = GC.Spread.Sheets.Events; for(let event in events){ workBook.bind(Events[event], events[event]); } } //根据宽度比例设置列宽 //@param {Object}workBook {Number}workBookWidth {Array}headers @return {void} function setColumnWidthByRate(workBook, workBookWidth, headers) { if(workBook){ workBookWidth -= 48; const sheet = workBook.getActiveSheet(); sheet.suspendEvent(); sheet.suspendPaint(); for(let col = 0; col < headers.length; col++){ if(headers[col]['rateWidth'] !== undefined && headers[col]['rateWidth'] !== null && headers[col]['rateWidth'] !== ''){ let width = workBookWidth * headers[col]['rateWidth']; if(headers[col]['dataCode'] === 'options'){ width = width; } sheet.setColumnWidth(col, width, GC.Spread.Sheets.SheetArea.colHeader) } else { if(headers[col]['headerWidth'] !== undefined && headers[col]['headerWidth'] !== null && headers[col]['headerWidth'] !== ''){ sheet.setColumnWidth(col, headers[col]['headerWidth'], GC.Spread.Sheets.SheetArea.colHeader) } } } sheet.resumeEvent(); sheet.resumePaint(); } } //建表 //@param {Object}module @return {void} function buildSheet(module) { if(!module.workBook){ module.workBook = new GC.Spread.Sheets.Workbook(module.dom[0], {sheetCount: 1}); sheetCommonObj.spreadDefaultStyle(module.workBook); let sheet = module.workBook.getActiveSheet(); if(module === bills){ //默认初始可控制焦点在清单表中 sheet.options.rowHeaderVisible = false; module.workBook.focus(); sheet.options.isProtected = true; sheet.name('stdBillsGuidance_bills'); //设置悬浮提示 TREE_SHEET_HELPER.initSetting(bills.dom[0], bills.treeSetting); } if(module === guideItem){ sheet.options.isProtected = true; sheet.getRange(-1, 0, -1, 1).locked(false); sheet.getRange(-1, 1, -1, 1).locked(true); } if(module === elfItem){ sheet.options.isProtected = true; sheet.getRange(-1, 0, -1, 1).locked(true); sheet.getRange(-1, 1, -1, 1).locked(false); } if(module.rowHeaderWidth) { sheet.setColumnWidth(0, module.rowHeaderWidth, GC.Spread.Sheets.SheetArea.rowHeader); } setOptions(module.workBook, options); buildHeader(module.workBook.getActiveSheet(), module.headers); if(module === elfItem){ setColumnWidthByRate(elfItem.workBook, $('#zy').width(), elfItem.headers) } bindEvent(module.workBook, module.events); } } //清空表数据 //@param {Object}sheet {Array}headers {Number}rowCount @return {void} function cleanData(sheet, headers, rowCount){ renderSheetFunc(sheet, function () { sheet.clear(-1, 0, -1, headers.length, GC.Spread.Sheets.SheetArea.viewport, GC.Spread.Sheets.StorageType.data); if (rowCount > 0) { sheet.setRowCount(rowCount); } }); } //初始化各工作表 //@param {Array}modules @return {void} function initWorkBooks(modules){ for(let module of modules){ buildSheet(module); } } //点击清单名称后面的问号,弹出补注窗口并设置当前节点(或xxx父节点)的补注 //@param {Number}row(当前焦点行) @return {void} function initRechargeModal(row) { let node = bills.tree.items[row]; while (node && !node.data.recharge){ node = node.parent; } let recharge = node && node.data.recharge ? node.data.recharge : '无内容'; node = bills.tree.items[row]; while (node && !node.data.ruleText){ node = node.parent; } let ruleText = node && node.data.ruleText ? node.data.ruleText : '无内容'; $('#questionTab1').text('补注'); $('#questionContent1').html(recharge); $('#questionContent2').html(ruleText); $('#questionModal').modal('show'); } //节点链上含有补注或工程量计算规则数据 //@param {Number}row(行当前行) @return {Boolean} function hasRechargeRuleText(row) { let node = bills.tree.items[row]; if (!node) { return false; } while (node) { if (node.data.recharge || node.data.ruleText) { return true; } node = node.parent; } return false; } //初始化并输出树 //@param {Object}module {Object}sheet {Object}treeSetting {Array}datas function initTree(module, sheet, treeSetting, datas){ module.tree = idTree.createNew({id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true}); module.controller = TREE_SHEET_CONTROLLER.createNew(module.tree, sheet, treeSetting, false); module.tree.loadDatas(datas); if(module === bills){ initExpandStat(); } module.controller.showTreeData(); if (module === bills) { module.workBook.getSheet(0).options.rowHeaderVisible = true; setBillsHint(bills.tree.items, stdBillsJobData, stdBillsFeatureData); renderSheetFunc(sheet, function () { const checkBoxType = new GC.Spread.Sheets.CellTypes.CheckBox(); for (let i = 0; i < bills.tree.items.length; i++) { sheet.setCellType(i, 0, checkBoxType); sheet.setCellType(i, 2, TREE_SHEET_HELPER.getQuestionCellType(initRechargeModal, hasRechargeRuleText)); } }); } } //项目指引表焦点控制 //@param {Number}row @return {void} function guideItemInitSel(row){ let billsNode = bills.tree.selected; let node = null; if(billsNode && billsNode.guidance.tree){ node = billsNode.guidance.tree.items[row]; if(node){ billsNode.guidance.tree.selected = node; } } } //清单精灵表焦点控制 //@param {Number}row @return {void} function elfItemInitSel(row){ let billsNode = bills.tree.selected; let node = null; if(billsNode && billsNode.elf.tree){ node = billsNode.elf.tree.items[row]; if(node){ billsNode.elf.tree.selected = node; } } } //根据项目指引的类型设置单元格类型,定额类型的项目指引为复选框 //@param {Array}nodes @return {void} function setItemCellType(nodes){ //设置单元格类型 const base = new GC.Spread.Sheets.CellTypes.Base(); const checkBox = new GC.Spread.Sheets.CellTypes.CheckBox(); const sheet = guideItem.workBook.getActiveSheet(); renderSheetFunc(sheet, function(){ for(let node of nodes){ sheet.setCellType(node.serialNo(), 0, node.data.type === itemType.ration ? checkBox : base); } }); } //初始化清单的工作内容和项目特征 //@param {Number}billsLibId {Function}callback @return {void} function initJobAndCharacter(billsLibId, callback){ CommonAjax.post('/stdBillsEditor/getJobContent', {userId: userID, billsLibId: billsLibId}, function (datas) { stdBillsJobData = datas; CommonAjax.post('/stdBillsEditor/getItemCharacter', {userId: userID, billsLibId: billsLibId}, function (datas) { stdBillsFeatureData = datas; if(callback){ callback(); } }); }); } //初始化清单展开收起状态 //@return {void} function initExpandStat(){ //读取展开收起状态 let currentExpState = sessionStorage.getItem('stdBillsGuidanceExpState'); if(currentExpState){ bills.tree.setExpandedByState(bills.tree.items, currentExpState); } //非叶子节点默认收起 else{ bills.tree.setRootExpanded(bills.tree.roots, false); // 默认展开第一个节点到第二层 bills.tree.roots[0].setExpanded(true); } } //设置tag以悬浮提示 function setTagForHint(nodes){ let sheet = bills.workBook.getActiveSheet(); renderSheetFunc(sheet, function () { for(let node of nodes){ sheet.setTag(node.serialNo(), 2, node.data.ruleText ? node.data.ruleText : ''); } }); } //根据编码定位至清单精灵库中 //@param {String}code @return {void} function locateAtBills(code) { let nineCode = code.substring(0, 9); let items = bills.tree.items; let locateBills = _.find(items, function(item){ return item.data.code === nineCode; }); if(locateBills){ expandSearchNodes([locateBills]); sessionStorage.setItem('stdBillsGuidanceExpState', bills.tree.getExpState(bills.tree.items)); } let sheet = bills.workBook.getActiveSheet(); let locateRow = locateBills ? locateBills.serialNo() : 0; sheet.setActiveCell(locateRow, 0); sheet.showRow(locateRow, GC.Spread.Sheets.VerticalPosition.center); } //清单设置悬浮提示信息 //@param {Array}billsNodes(清单节点) {Array}jobs(总的工作内容数据) {Array}items(总的项目特征数据) function setBillsHint(billsNodes, jobs, items) { let jobsMapping = {}, itemsMapping = {}; for(let job of jobs){ jobsMapping[job.id] = job; } for(let item of items){ itemsMapping[item.id] = item; } let tagInfo = []; for(let billsNode of billsNodes){ let hintArr = []; let billsItems = billsNode.data.items; if(billsItems.length > 0){ //项目特征 hintArr.push('项目特征:'); } let itemCount = 1, jobCount = 1; for(let billsItem of billsItems){ let itemData = itemsMapping[billsItem.id]; if(itemData){ //特征值 let eigens = []; for(let eigen of itemData.itemValue){ eigens.push(eigen.value); } eigens = eigens.join(';'); hintArr.push(`${itemCount}.${itemData.content}${eigens === '' ? '' : ': ' + eigens}`); itemCount ++; } } //工作内容 let billsJobs = billsNode.data.jobs; if(billsJobs.length > 0){ hintArr.push('工作内容:'); } for(let billsJob of billsJobs){ let jobData = jobsMapping[billsJob.id]; if(jobData){ hintArr.push(`${jobCount}.${jobData.content}`); jobCount ++; } } /*if(billsNode.data.ruleText && billsNode.data.ruleText !== ''){ hintArr.push('工程量计算规则:'); hintArr.push(billsNode.data.ruleText); } if(billsNode.data.recharge && billsNode.data.recharge !== ''){ hintArr.push('补注:'); hintArr.push(billsNode.data.recharge); }*/ if(hintArr.length > 0){ tagInfo.push({row: billsNode.serialNo(), value: hintArr.join('\n')}); } } let sheet = bills.workBook.getActiveSheet(); renderSheetFunc(sheet, function () { for(let tagI of tagInfo){ sheet.setTag(tagI.row, 0, tagI.value); } }); } //初始选择标准清单 //@param {Number}libID @return {void} function libInitSel(libID){ //获取清单 $.bootstrapLoading.start(); CommonAjax.post('/billsGuidance/api/getLibWithBills', {libID: libID, isGuidanceLib: false}, function(rstData){ if(guideItem.workBook){ guideItem.workBook.destroy(); guideItem.workBook = null; } if(elfItem.workBook){ elfItem.workBook.destroy(); elfItem.workBook = null; } initViews(); let callback = function () { if (compilationName === '内蒙古高速公路日常养护估算(2021)' || compilationName === '广东农村养护(2021)') { //给清单加点料 baseEstUnitPrice chkAndAddEstUnitPrice(rstData.bills); } initTree(bills, bills.workBook.getActiveSheet(), bills.treeSetting, rstData.bills); if(doAfterLoadGuidance){ doAfterLoadGuidance(); } $.bootstrapLoading.end(); }; //获取清单库中的工作内容和项目特征 initJobAndCharacter(libID, callback); }, function () { $.bootstrapLoading.end(); }); } //初始化清单指引库 //@param {Array}libDats @return {void} function initLibs(libDatas){ libSel.empty(); if(!libDatas){ return; } let selectedLib = sessionStorage.getItem('stdBillsGuidance'); for(let libData of libDatas){ let opt = $('