/* 建设其他费表格相关 */ const budgetSummaryObj = (() => { const { isEmptyVal, isDef, isNumber } = window.commonUtil; const { fixedFlag, BudgetArea } = window.commonConstants; // 原始数据 let rawData = []; // 建设其他费表格对象 let spread = null; // 建设其他费树 let tree = null; // 单位设置下拉框 const setUnitCombo = (sheet, data) => { const unitCol = budgetSummaryTreeSetting.cols.findIndex(item => item.data.field === 'unit'); if (unitCol >= 0) { TREE_SHEET_HELPER.massOperationSheet(sheet, () => { const comboBox = sheetCommonObj.getDynamicCombo(); comboBox .itemHeight(10) .items(['m', 'm2', 'm3', 'km', 't', 'kg', '台班', '工日', '昼夜', '元', '项', '处', '个', '件', '根', '组', '系统', '台', '套', '株', '丛', '缸', '支', '只', '块', '座', '对', '份', '樘', '攒', '榀']) .editable(true); data.forEach((item, index) => { sheet.getCell(index, unitCol).cellType(comboBox); }) }); } } const getFieldByCol = (col) => { const item = budgetSummaryTreeSetting.cols[col]; return item && item.data && item.data.field || null; } // 单元格值验证器 const validator = { text() { return true }, number(val) { return !isDef(val) || isNumber(val); }, }; const getValidator = (col) => { const item = budgetSummaryTreeSetting.cols[col]; if (!item) { return 'text'; } return validator[item.data.type || 'text']; } /* 表格事件相关 */ // 恢复数据 const recover = (sheet, changedCells) => { if (!tree) { return; } changedCells.forEach(({ row, col }) => { const node = tree.items[row]; const field = getFieldByCol(col); if (!field || !node) { return; } const orgVal = node.data[field]; sheet.setValue(row, col, orgVal); }) } // 更新数据 const bulkOperation = async (bulkData) => { await ajaxPost('/bills/bulkOperation', { bulkData }); }; // 编辑相关 const edit = async (sheet, changedCells) => { if (!changedCells.length) { return; } // 单元格值验证 const isValid = changedCells.every(({ row, col }) => { const val = sheet.getValue(row, col); return getValidator(col)(val); }); // 验证不通过,恢复 if (!isValid) { recover(sheet, changedCells); } console.log(changedCells); try { $.bootstrapLoading.start(); const IDMap = {}; const bulkData = []; changedCells.forEach(({ row, col }) => { const node = tree.items[row]; if (!node) { return; } const field = getFieldByCol(col); const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {})); data[field] = sheet.getValue(row, col); }); Object .entries(IDMap) .forEach(([ID, data]) => { bulkData.push({ type: 'update', data: { ID, ...data } }); }); await bulkOperation(bulkData); bulkData.forEach(item => { const node = tree.findNode(item.data.ID); Object.assign(node.data, item.data); }); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); } } // 是否是属于工程费用区域的节点 const isConstructionFeeArea = (node) => { return node && node.data && node.data.area === BudgetArea.CONSTRUCTION_FEE; } // 工具栏可操作性 let upLevelDisabled = false; let downLevelDisabled = false; let upMoveDisabled = false; let downMoveDisabled = false; const refreshToolsBar = (node) => { upLevelDisabled = !node || !node.canUpLevel() || isConstructionFeeArea(node) || (node.nextSibling && node.data.calcBase); downLevelDisabled = !node || !node.canDownLevel() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling) || (node.preSibling && node.preSibling.data.calcBase); upMoveDisabled = !node || !node.canUpMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.preSibling); downMoveDisabled = !node || !node.canDownMove() || isConstructionFeeArea(node) || isConstructionFeeArea(node.nextSibling); if (upLevelDisabled) { $('#budget-upLevel').addClass('disabled'); } else { $('#budget-upLevel').removeClass('disabled'); } if (downLevelDisabled) { $('#budget-downLevel').addClass('disabled'); } else { $('#budget-downLevel').removeClass('disabled'); } if (upMoveDisabled) { $('#budget-upMove').addClass('disabled'); } else { $('#budget-upMove').removeClass('disabled'); } if (downMoveDisabled) { $('#budget-downMove').addClass('disabled'); } else { $('#budget-downMove').removeClass('disabled'); } }; // 表格选中相关 const selectCell = (row, col, setCell = false) => { const node = tree.items[row]; refreshToolsBar(node); tree.selected = node; console.log(node); const sheet = spread.getSheet(0); if (setCell) { sheet.setActiveCell(row, col); } //设置选中行底色和恢复前选中行底色 const refreshNodes = [node]; if (!tree.preSelected) { refreshNodes.push(tree.items[0]); } else { refreshNodes.push(tree.preSelected); } tree.preSelected = node; projectObj.setNodesStyle(sheet, refreshNodes, tree); }; // 事件列表 const events = { EnterCell(sender, args) { args.sheet.repaint(); }, SelectionChanged(sender, args) { const newSel = args.newSelections[0] ? { row: args.newSelections[0].row, col: args.newSelections[0].col } : { row: 0, col: 0 }; selectCell(newSel.row, newSel.col); }, ValueChanged(sender, args) { if (isEmptyVal(args.oldValue) && isEmptyVal(args.newValue)) { return; } edit(args.sheet, [{ row: args.row, col: args.col }]); }, RangeChanged(sender, args) { edit(args.sheet, args.changedCells); } } const bindEvents = (sheet) => { Object.entries(events).forEach(([ev, evFunc]) => { sheet.bind(GC.Spread.Sheets.Events[ev], evFunc); }); } /* 只读相关 */ // 单元格锁定判断 const lockFactory = { code(node) { return !!(node && node.getFlag()); }, name(node) { return !!(node && node.getFlag()); }, calcBase(node) { return !!(node && node.children.length); }, feeRate(node) { return !!(node && node.children.length); } }; const lockData = (sheet, nodes, isMass = true) => { const lock = () => { if (projectReadOnly) { sheet.getRange(0, 0, nodes.length, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true); return; } // 工程费用区域,只读 const equipmentNode = nodes.find(node => node.getFlag() === fixedFlag.CONSTRUCTION_EQUIPMENT_FEE); if (!equipmentNode) { return; } sheet.getRange(0, 0, equipmentNode.serialNo() + 1, budgetSummaryTreeSetting.cols.length, GC.Spread.Sheets.SheetArea.viewport).locked(true); // 剩下的区域,根据单元格锁定方法,按字段进行锁定判断 const startIndex = equipmentNode.serialNo() + 1; for (let row = startIndex; row < nodes.length; row ++) { const node = nodes[row]; budgetSummaryTreeSetting.cols.forEach((item, col) => { const field = item.data.field; const lockFunc = lockFactory[field]; if (!lockFunc) { return; } const isLocked = lockFunc(node); sheet.getCell(row, col).locked(isLocked); }); } } if (isMass) { TREE_SHEET_HELPER.massOperationSheet(sheet, () => { lock(); }); } else { lock(); } } /* 初始化表格 */ const initSpread = () => { if (!spread) { // spread = sheetCommonObj.createSpread($('#budget-summary-sheet')[0], 1); spread = SheetDataHelper.createNewSpread($('#budget-summary-sheet')[0]); sheetCommonObj.spreadDefaultStyle(spread); // 设置表头 const sheet = spread.getSheet(0); bindEvents(sheet); const headers = sheetCommonObj.getHeadersFromTreeSetting(budgetSummaryTreeSetting); sheetCommonObj.setHeader(sheet, headers); // 右键菜单 initContextMenu(); } else { spread.refresh(); } return spread; } // 初始化树 const initTree = (data, sheet, setting) => { tree = idTree.createNew({ id: 'ID', pid: 'ParentID', nid: 'NextSiblingID', rootId: -1, autoUpdate: true }); const controller = TREE_SHEET_CONTROLLER.createNew(tree, sheet, setting, false, true); tree.loadDatas(data); tree.items.forEach(node => { node.source = node; node.sourceType = ModuleNames.bills; }); controller.showTreeData(); sheet.setRowCount(data.length); setUnitCombo(sheet, data); TREE_SHEET_HELPER.massOperationSheet(sheet, () => { lockData(sheet, tree.items, false); // 表格格式化 budgetSummaryTreeSetting.cols.forEach((item, index) => { sheet.setFormatter(-1, index, item.formatter || '@', GC.Spread.Sheets.SheetArea.viewport); }); }); selectCell(sheet.getActiveRowIndex(), sheet.getActiveColumnIndex()); } /* 右键菜单 */ // 更新树结构数据 const updateTree = (sheet, updateData) => { // 更新数据 updateData.forEach(item => { if (item.type === 'new') { rawData.push(item.data) } else if (item.type === 'update') { const node = tree.findNode(item.data.ID); if (node) { Object.assign(node.data, item.data); } } else { const removeIndex = rawData.findIndex(d => d.ID === item.data.ID); if (removeIndex > -1) { rawData.splice(removeIndex, 1); } } }); // 重新初始化树 initTree(rawData, sheet, budgetSummaryTreeSetting); } let loading = false; // 插入 const insert = async (sheet, selected) => { try { if (loading) { return; } loading = true; $.bootstrapLoading.start(); const updateData = tree.getInsertData(selected.data.ParentID, selected.data.NextSiblingID, uuid.v1()); const newData = updateData.filter(item => item.type === 'new'); newData.forEach(item => { item.data.fees = []; item.data.flags = []; item.feesIndex = {}; item.flagsIndex = {}; item.data.type = selected.data.type; item.data.projectID = projectObj.project.property.rootProjectID; }); await bulkOperation(updateData); updateTree(sheet, updateData); selectCell(sheet.getActiveRowIndex() + selected.posterityCount() + 1, sheet.getActiveColumnIndex(), true); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 删除 const remove = async (sheet, selected) => { try { if (loading) { return; } loading = true; $.bootstrapLoading.start(); const updateData = tree.getDeleteData(selected); await bulkOperation(updateData); updateTree(sheet, updateData); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 升级 const upLevel = async (selected) => { if (!spread || !tree) { return; } const sheet = spread.getSheet(0); selected = selected ? selected : tree.selected; try { if (loading || upLevelDisabled) { return; } loading = true; $.bootstrapLoading.start(); const updateData = selected.getUpLevelData(); await bulkOperation(updateData); updateTree(sheet, updateData); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 降级 const downLevel = async (selected) => { if (!spread || !tree) { return; } const sheet = spread.getSheet(0); selected = selected ? selected : tree.selected; try { if (loading || downLevelDisabled) { return; } loading = true; $.bootstrapLoading.start(); const updateData = selected.getDownLevelData(); await bulkOperation(updateData); updateTree(sheet, updateData); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 上移 const upMove = async (selected) => { if (!spread || !tree) { return; } const sheet = spread.getSheet(0); selected = selected ? selected : tree.selected; try { if (loading || upMoveDisabled) { return; } loading = true; $.bootstrapLoading.start(); const updateData = selected.getUpMoveData(); await bulkOperation(updateData); updateTree(sheet, updateData); const prev = selected.preSibling; const row = sheet.getActiveRowIndex() - prev.posterityCount() - 1; selectCell(row, sheet.getActiveColumnIndex(), true); sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 下移 const downMove = async (selected) => { if (!spread || !tree) { return; } const sheet = spread.getSheet(0); selected = selected ? selected : tree.selected; try { if (loading || downMoveDisabled) { return; } loading = true; $.bootstrapLoading.start(); const updateData = selected.getDownMoveData(); await bulkOperation(updateData); updateTree(sheet, updateData); const next = selected.nextSibling; const row = sheet.getActiveRowIndex() + next.posterityCount() + 1; selectCell(row, sheet.getActiveColumnIndex(), true); sheet.showRow(row, GC.Spread.Sheets.VerticalPosition.center); } catch (err) { alert(err); } finally { $.bootstrapLoading.end(); loading = false; } } // 初始化右键菜单 const initContextMenu = () => { if (!spread) { return; } let curRow; let curNode; const sheet = spread.getSheet(0); $.contextMenu({ selector: '#budget-summary-sheet', build: function ($trigger, e) { const target = SheetDataHelper.safeRightClickSelection($trigger, e, spread); curRow = target.row; curNode = tree && tree.items[curRow] || null; selectCell(target.row, target.col, true); return target.hitTestType === GC.Spread.Sheets.SheetArea.viewport || target.hitTestType === GC.Spread.Sheets.SheetArea.rowHeader; }, items: { insert: { name: '插入行', icon: 'fa-sign-in', disabled() { return !curNode || (curNode.data.area === BudgetArea.CONSTRUCTION_FEE && curNode.getFlag() !== fixedFlag.CONSTRUCTION_FEE); }, callback() { insert(sheet, curNode); } }, remove: { name: '删除行', icon: 'fa-remove', disabled() { return !curNode || isConstructionFeeArea(curNode) || curNode.getFlag(); }, callback() { remove(sheet, curNode); } }, /* upLevel: { name: '升级', icon: 'fa-arrow-left', disabled() { return !curNode || !curNode.canUpLevel() || isConstructionFeeArea(curNode); }, callback() { upLevel(sheet, curNode); } }, downLevel: { name: '降级', icon: 'fa-arrow-right', disabled() { return !curNode || !curNode.canDownLevel() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling); }, callback() { downLevel(sheet, curNode); } }, upMove: { name: '上移', icon: 'fa-arrow-up', disabled() { return !curNode || !curNode.canUpMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.preSibling); }, callback() { upMove(sheet, curNode); } }, downMove: { name: '下移', icon: 'fa-arrow-down', disabled() { return !curNode || !curNode.canDownMove() || isConstructionFeeArea(curNode) || isConstructionFeeArea(curNode.nextSibling); }, callback() { downMove(sheet, curNode); } }, */ refresh: { name: '刷新数据', icon: 'fa-refresh', callback() { init(projectObj.project.property.rootProjectID); } }, } }); } // 初始化 const init = async (constructionID) => { try { $.bootstrapLoading.start(); rawData = await ajaxPost('/bills/getBudgetSummary', { constructionID }); rawData.forEach((item) => { if (item.quantity) { item.quantity = parseFloat(item.quantity); } item.feesIndex = getFeeIndex(item.fees); item.flagsIndex = {}; if (item.flags) { item.flags.forEach((flag) => { item.flagsIndex[flag.fieldName] = flag; }); } }); const spread = initSpread(); const sheet = spread.getSheet(0); initTree(rawData, sheet, budgetSummaryTreeSetting); } catch (err) { console.log(err); alert(err); } finally { $.bootstrapLoading.end(); } } // 点击tab,重新初始化 $('#tab-budget-summary').click(function () { if (!$(this).hasClass('active')) { init(projectObj.project.property.rootProjectID); } }); $('#budget-upLevel').click(() => { upLevel(); }); $('#budget-downLevel').click(() => { downLevel(); }); $('#budget-upMove').click(() => { upMove(); }); $('#budget-downMove').click(() => { downMove(); }); // 对外暴露 return { getTree: () => tree, }; })();