|  | @@ -6,6 +6,8 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    // 原始数据
 | 
	
		
			
				|  |  |    let rawData = [];
 | 
	
		
			
				|  |  | +  // ID与原始数据映射表(主要是恢复用)
 | 
	
		
			
				|  |  | +  const orgMap = {};
 | 
	
		
			
				|  |  |    // 建设其他费表格对象
 | 
	
		
			
				|  |  |    let spread = null;
 | 
	
		
			
				|  |  |    // 建设其他费树
 | 
	
	
		
			
				|  | @@ -56,21 +58,63 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |      return validator[item.data.type || 'text'];
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +  // 单元格文本转换处理
 | 
	
		
			
				|  |  | +  const textFactory = {
 | 
	
		
			
				|  |  | +    calcBase(node) {
 | 
	
		
			
				|  |  | +      if (node.data.calcBase && node.data.calcBase !== "") {
 | 
	
		
			
				|  |  | +        return cbParser.toFExpr(node.data.calcBase);
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.common.unitFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.common.unitFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.common.totalFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.common.totalFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.building.totalFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.building.totalFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.installation.totalFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.installation.totalFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.equipment.totalFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.equipment.totalFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    'feesIndex.other.totalFee': (node) => {
 | 
	
		
			
				|  |  | +      return _.get(node, 'data.feesIndex.other.totalFee', '') || '';
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +  };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |    /* 表格事件相关 */
 | 
	
		
			
				|  |  | -  // 恢复数据
 | 
	
		
			
				|  |  | -  const recover = (sheet, changedCells) => {
 | 
	
		
			
				|  |  | +  // 根据节点数据刷新表格数据
 | 
	
		
			
				|  |  | +  const refreshData = (sheet, changedCells) => {
 | 
	
		
			
				|  |  |      if (!tree) {
 | 
	
		
			
				|  |  |        return;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | -    changedCells.forEach(({ row, col }) => {
 | 
	
		
			
				|  |  | -      const node = tree.items[row];
 | 
	
		
			
				|  |  | -      const field = getFieldByCol(col);
 | 
	
		
			
				|  |  | -      if (!field || !node) {
 | 
	
		
			
				|  |  | -        return;
 | 
	
		
			
				|  |  | +    TREE_SHEET_HELPER.massOperationSheet(sheet, () => {
 | 
	
		
			
				|  |  | +      changedCells.forEach(({ row, col }) => {
 | 
	
		
			
				|  |  | +        const node = tree.items[row];
 | 
	
		
			
				|  |  | +        const field = getFieldByCol(col);
 | 
	
		
			
				|  |  | +        if (!field || !node) {
 | 
	
		
			
				|  |  | +          return;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        const textFunc = textFactory[field];
 | 
	
		
			
				|  |  | +        const val = textFunc ? textFunc(node) : node.data[field];
 | 
	
		
			
				|  |  | +        sheet.setValue(row, col, val);
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +    });
 | 
	
		
			
				|  |  | +  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  // 刷新整个表格
 | 
	
		
			
				|  |  | +  const refreshAll = (sheet) => {
 | 
	
		
			
				|  |  | +    const changedCells = [];
 | 
	
		
			
				|  |  | +    const colCount = budgetSummaryTreeSetting.cols.length;
 | 
	
		
			
				|  |  | +    for (let row = 0; row < tree.items.length; row++) {
 | 
	
		
			
				|  |  | +      for (let col = 0; col < colCount; col++) {
 | 
	
		
			
				|  |  | +        changedCells.push({ row, col })
 | 
	
		
			
				|  |  |        }
 | 
	
		
			
				|  |  | -      const orgVal = node.data[field];
 | 
	
		
			
				|  |  | -      sheet.setValue(row, col, orgVal);
 | 
	
		
			
				|  |  | -    })
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    refreshData(sheet, changedCells);
 | 
	
		
			
				|  |  |    }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    // 更新数据
 | 
	
	
		
			
				|  | @@ -78,27 +122,6 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |      await ajaxPost('/bills/bulkOperation', { bulkData });
 | 
	
		
			
				|  |  |    };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  // 计算节点
 | 
	
		
			
				|  |  | -  const calcNodes = async (sheet, nodes) => {
 | 
	
		
			
				|  |  | -    try {
 | 
	
		
			
				|  |  | -      $.bootstrapLoading.start();
 | 
	
		
			
				|  |  | -      debugger;
 | 
	
		
			
				|  |  | -      const changedNodes = projectObj.project.calcProgram.calcNodes(nodes, false);
 | 
	
		
			
				|  |  | -      const dataArr = [];
 | 
	
		
			
				|  |  | -      for (const node of changedNodes) {
 | 
	
		
			
				|  |  | -        if (node.changed) {
 | 
	
		
			
				|  |  | -          const data = calcTools.cutNodeForSave(node);
 | 
	
		
			
				|  |  | -          dataArr.push(data);
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -      }
 | 
	
		
			
				|  |  | -      console.log(dataArr);
 | 
	
		
			
				|  |  | -    } catch (err) {
 | 
	
		
			
				|  |  | -      alert(err);
 | 
	
		
			
				|  |  | -    } finally {
 | 
	
		
			
				|  |  | -      $.bootstrapLoading.end();
 | 
	
		
			
				|  |  | -    }
 | 
	
		
			
				|  |  | -  }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |    // 编辑相关
 | 
	
		
			
				|  |  |    const edit = async (sheet, changedCells) => {
 | 
	
		
			
				|  |  |      if (!changedCells.length) {
 | 
	
	
		
			
				|  | @@ -111,7 +134,7 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |      });
 | 
	
		
			
				|  |  |      // 验证不通过,恢复
 | 
	
		
			
				|  |  |      if (!isValid) {
 | 
	
		
			
				|  |  | -      recover(sheet, changedCells);
 | 
	
		
			
				|  |  | +      refreshData(sheet, changedCells);
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |      let needCalc = false;
 | 
	
		
			
				|  |  |      const nodes = [];
 | 
	
	
		
			
				|  | @@ -129,36 +152,87 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |          const field = getFieldByCol(col);
 | 
	
		
			
				|  |  |          const value = sheet.getValue(row, col) || '';
 | 
	
		
			
				|  |  | +        const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {}));
 | 
	
		
			
				|  |  | +        if (['feesIndex.common.unitFee'].includes(field)) {
 | 
	
		
			
				|  |  | +          const fees = node.data.fees || [];
 | 
	
		
			
				|  |  | +          const feeItem = fees.find(item => item.fieldName === 'common');
 | 
	
		
			
				|  |  | +          if (feeItem) {
 | 
	
		
			
				|  |  | +            feeItem.unitFee = value;
 | 
	
		
			
				|  |  | +          } else {
 | 
	
		
			
				|  |  | +            fees.push({ fieldName: 'common', totalFee: 0, unitFee: +value });
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +          data[field] = fees;
 | 
	
		
			
				|  |  | +          node.data[field] = fees;
 | 
	
		
			
				|  |  | +          node.data.feesIndex = getFeeIndex(node.data.fees);
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          data[field] = value;
 | 
	
		
			
				|  |  | +          node.data[field] = value;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          if (field === 'calcBase') {
 | 
	
		
			
				|  |  | -          debugger;
 | 
	
		
			
				|  |  |            node.data.userCalcBase = value;
 | 
	
		
			
				|  |  |            projectObj.project.calcBase.calculate(node, null, false);
 | 
	
		
			
				|  |  |            if (!projectObj.project.calcBase.success) {
 | 
	
		
			
				|  |  |              throw projectObj.project.calcBase.errMsg;
 | 
	
		
			
				|  |  | +          } else if (isEmptyVal(value)) {
 | 
	
		
			
				|  |  | +            // 删除清单基数,单价要清空
 | 
	
		
			
				|  |  | +            calcTools.setFieldValue(node, 'feesIndex.common.unitFee', 0);
 | 
	
		
			
				|  |  |            }
 | 
	
		
			
				|  |  | +          data.calcBase = node.data.calcBase;
 | 
	
		
			
				|  |  | +          data.calcBaseValue = node.data.calcBaseValue;
 | 
	
		
			
				|  |  | +          data.tenderCalcBaseValue = node.data.tenderCalcBaseValue;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        const data = (IDMap[node.data.ID] || (IDMap[node.data.ID] = {}));
 | 
	
		
			
				|  |  | -        data[field] = value;
 | 
	
		
			
				|  |  |          if (['quantity', 'feesIndex.common.unitFee', 'calcBase', 'feeRate'].includes(field)) {
 | 
	
		
			
				|  |  |            needCalc = true;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  | +      // 重算节点
 | 
	
		
			
				|  |  | +      const dataArr = [];
 | 
	
		
			
				|  |  | +      if (needCalc && nodes.length) {
 | 
	
		
			
				|  |  | +        const changedNodes = projectObj.project.calcProgram.calcNodes(nodes, false, tree);
 | 
	
		
			
				|  |  | +        for (const node of changedNodes) {
 | 
	
		
			
				|  |  | +          nodes.push(node);
 | 
	
		
			
				|  |  | +          if (node.changed) {
 | 
	
		
			
				|  |  | +            const data = calcTools.cutNodeForSave(node);
 | 
	
		
			
				|  |  | +            dataArr.push(data);
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      }
 | 
	
		
			
				|  |  | +      dataArr.forEach(item => {
 | 
	
		
			
				|  |  | +        delete item.projectID;
 | 
	
		
			
				|  |  | +        const data = IDMap[item.ID];
 | 
	
		
			
				|  |  | +        if (data) {
 | 
	
		
			
				|  |  | +          Object.assign(data, item);
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +          IDMap[item.ID] = item;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +      // 保存节点
 | 
	
		
			
				|  |  |        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);
 | 
	
		
			
				|  |  | -      });
 | 
	
		
			
				|  |  | -      // 重算节点
 | 
	
		
			
				|  |  | -      /* if (needCalc && nodes.length) {
 | 
	
		
			
				|  |  | -        await calcNodes(sheet, nodes);
 | 
	
		
			
				|  |  | -      } */
 | 
	
		
			
				|  |  | +      Object
 | 
	
		
			
				|  |  | +        .entries(IDMap)
 | 
	
		
			
				|  |  | +        .forEach(([ID, data]) => {
 | 
	
		
			
				|  |  | +          const node = tree.findNode(ID);
 | 
	
		
			
				|  |  | +          if (node) {
 | 
	
		
			
				|  |  | +            Object.assign(node.data, data);
 | 
	
		
			
				|  |  | +            node.data.feesIndex = getFeeIndex(node.data.fees);
 | 
	
		
			
				|  |  | +            orgMap[ID] = _.cloneDeep(node.data);
 | 
	
		
			
				|  |  | +          }
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +      refreshAll(sheet);
 | 
	
		
			
				|  |  |      } catch (err) {
 | 
	
		
			
				|  |  | -      recover(sheet, changedCells);
 | 
	
		
			
				|  |  | +      console.log(err);
 | 
	
		
			
				|  |  | +      nodes.forEach(node => {
 | 
	
		
			
				|  |  | +        const orgItem = orgMap[node.data.ID];
 | 
	
		
			
				|  |  | +        if (orgItem) {
 | 
	
		
			
				|  |  | +          Object.assign(node.data, orgItem);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  | +      refreshData(sheet, changedCells);
 | 
	
		
			
				|  |  |        alert(err);
 | 
	
		
			
				|  |  |      } finally {
 | 
	
		
			
				|  |  |        $.bootstrapLoading.end();
 | 
	
	
		
			
				|  | @@ -312,6 +386,11 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |            const cellType = cellTypeFunc(node);
 | 
	
		
			
				|  |  |            sheet.getCell(row, col).cellType(cellType);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | +        // 单元格文本转换
 | 
	
		
			
				|  |  | +        const textFunc = textFactory[field];
 | 
	
		
			
				|  |  | +        if (textFunc) {
 | 
	
		
			
				|  |  | +          sheet.setValue(row, col, textFunc(node));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |    }
 | 
	
	
		
			
				|  | @@ -363,13 +442,18 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |      // 更新数据
 | 
	
		
			
				|  |  |      updateData.forEach(item => {
 | 
	
		
			
				|  |  |        if (item.type === 'new') {
 | 
	
		
			
				|  |  | +        orgMap[item.data.ID] = _.cloneDeep(item.data);
 | 
	
		
			
				|  |  |          rawData.push(item.data)
 | 
	
		
			
				|  |  |        } else if (item.type === 'update') {
 | 
	
		
			
				|  |  | +        if (orgMap[item.data.ID]) {
 | 
	
		
			
				|  |  | +          Object.assign(orgMap[item.data.ID], item.data);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |          const node = tree.findNode(item.data.ID);
 | 
	
		
			
				|  |  |          if (node) {
 | 
	
		
			
				|  |  |            Object.assign(node.data, item.data);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |        } else {
 | 
	
		
			
				|  |  | +        delete orgMap[item.data.ID];
 | 
	
		
			
				|  |  |          const removeIndex = rawData.findIndex(d => d.ID === item.data.ID);
 | 
	
		
			
				|  |  |          if (removeIndex > -1) {
 | 
	
		
			
				|  |  |            rawData.splice(removeIndex, 1);
 | 
	
	
		
			
				|  | @@ -640,6 +724,9 @@ const budgetSummaryObj = (() => {
 | 
	
		
			
				|  |  |        });
 | 
	
		
			
				|  |  |        const spread = initSpread();
 | 
	
		
			
				|  |  |        const sheet = spread.getSheet(0);
 | 
	
		
			
				|  |  | +      rawData.forEach(item => {
 | 
	
		
			
				|  |  | +        orgMap[item.ID] = _.cloneDeep(item);
 | 
	
		
			
				|  |  | +      });
 | 
	
		
			
				|  |  |        initTree(rawData, sheet, budgetSummaryTreeSetting);
 | 
	
		
			
				|  |  |        calcBase.initBudget();
 | 
	
		
			
				|  |  |      } catch (err) {
 |