|  | @@ -13,7 +13,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "1",
 | 
	
		
			
				|  |  |              name: "定额直接费",
 | 
	
		
			
				|  |  |              dispExpr: "F2+F3+F4",
 | 
	
		
			
				|  |  | -            expression: "@('2')+@('3')+@('4')",
 | 
	
		
			
				|  |  |              statement: "人工费+材料费+机械费",
 | 
	
		
			
				|  |  |              feeRate: null,
 | 
	
		
			
				|  |  |              memo: ''
 | 
	
	
		
			
				|  | @@ -23,7 +22,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "1.1",
 | 
	
		
			
				|  |  |              name: "人工费",
 | 
	
		
			
				|  |  |              dispExpr: "HJ",
 | 
	
		
			
				|  |  | -            expression: "HJ",
 | 
	
		
			
				|  |  |              statement: "合计",
 | 
	
		
			
				|  |  |              feeRate: 50,
 | 
	
		
			
				|  |  |              fieldName: 'labour',
 | 
	
	
		
			
				|  | @@ -34,7 +32,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "1.2",
 | 
	
		
			
				|  |  |              name: "材料费",
 | 
	
		
			
				|  |  |              dispExpr: "HJ",
 | 
	
		
			
				|  |  | -            expression: "HJ",
 | 
	
		
			
				|  |  |              statement: "合计",
 | 
	
		
			
				|  |  |              feeRate: 30,
 | 
	
		
			
				|  |  |              fieldName: 'material',
 | 
	
	
		
			
				|  | @@ -45,7 +42,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "1.3",
 | 
	
		
			
				|  |  |              name: "机械费",
 | 
	
		
			
				|  |  |              dispExpr: "HJ",
 | 
	
		
			
				|  |  | -            expression: "HJ",
 | 
	
		
			
				|  |  |              statement: "合计",
 | 
	
		
			
				|  |  |              feeRate: 20,
 | 
	
		
			
				|  |  |              fieldName: 'machine',
 | 
	
	
		
			
				|  | @@ -56,7 +52,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "2",
 | 
	
		
			
				|  |  |              name: "企业管理费",
 | 
	
		
			
				|  |  |              dispExpr: "F1",
 | 
	
		
			
				|  |  | -            expression: "@('1')",
 | 
	
		
			
				|  |  |              statement: "定额直接费",
 | 
	
		
			
				|  |  |              feeRate: null,
 | 
	
		
			
				|  |  |              fieldName: 'manage',
 | 
	
	
		
			
				|  | @@ -67,7 +62,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "3",
 | 
	
		
			
				|  |  |              name: "利润",
 | 
	
		
			
				|  |  |              dispExpr: "F1",
 | 
	
		
			
				|  |  | -            expression: "@('1')",
 | 
	
		
			
				|  |  |              statement: "定额直接费",
 | 
	
		
			
				|  |  |              feeRate: null,
 | 
	
		
			
				|  |  |              fieldName: 'profit',
 | 
	
	
		
			
				|  | @@ -78,7 +72,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "4",
 | 
	
		
			
				|  |  |              name: "风险费用",
 | 
	
		
			
				|  |  |              dispExpr: "F1",
 | 
	
		
			
				|  |  | -            expression: "@('1')",
 | 
	
		
			
				|  |  |              statement: "定额直接费",
 | 
	
		
			
				|  |  |              feeRate: null,
 | 
	
		
			
				|  |  |              fieldName: 'risk',
 | 
	
	
		
			
				|  | @@ -89,7 +82,6 @@ let defaultBillTemplate = {
 | 
	
		
			
				|  |  |              code: "5",
 | 
	
		
			
				|  |  |              name: "综合单价",
 | 
	
		
			
				|  |  |              dispExpr: "F1+F5+F6+F7",
 | 
	
		
			
				|  |  | -            expression: "@('1')+@('5')+@('6')+@('7')",
 | 
	
		
			
				|  |  |              statement: "定额直接费+企业管理费+利润+风险费用",
 | 
	
		
			
				|  |  |              feeRate: null,
 | 
	
		
			
				|  |  |              fieldName: 'common',
 | 
	
	
		
			
				|  | @@ -702,14 +694,13 @@ const rationCalcBases = {
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  let analyzer = {
 | 
	
		
			
				|  |  | -    calcTemplate: null,
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      standard: function(expr){
 | 
	
		
			
				|  |  |          let str = expr;
 | 
	
		
			
				|  |  |          str = str.replace(/\s/g, "");               // 去空格、去中文空格
 | 
	
		
			
				|  |  |          str = str.replace(/(/g, "(");              // 中文括号"("换成英文括号"("
 | 
	
		
			
				|  |  |          str = str.replace(/)/g, ")");              // 中文括号")"换成英文括号")"
 | 
	
		
			
				|  |  |          str = str.replace(/f/g, "F");               // f换成F
 | 
	
		
			
				|  |  | +        str = str.replace(/l/g, "L");               // l换成L
 | 
	
		
			
				|  |  |          return str;
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  |      getFArr: function (expr) {
 | 
	
	
		
			
				|  | @@ -722,40 +713,44 @@ let analyzer = {
 | 
	
		
			
				|  |  |          let arr = expr.match(patt);
 | 
	
		
			
				|  |  |          return arr ? arr : [];
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  | -    getFID: function (FName) {          // F3、F22
 | 
	
		
			
				|  |  | -        let me = this;
 | 
	
		
			
				|  |  | +    getBaseArr: function (expr) {
 | 
	
		
			
				|  |  | +        let pattBase = new RegExp(/\[[\u4E00-\u9FA5]+\]/gi);
 | 
	
		
			
				|  |  | +        let arrBase = expr.match(pattBase);
 | 
	
		
			
				|  |  | +        return arrBase ? arrBase : [];
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    getFID: function (FName, calcTemplate) {          // F3、F22 → 4、99
 | 
	
		
			
				|  |  |          let idx = FName.slice(1) - 1;
 | 
	
		
			
				|  |  | -        let id = me.calcTemplate.calcItems[idx] ? me.calcTemplate.calcItems[idx].ID : null;
 | 
	
		
			
				|  |  | +        let id = calcTemplate.calcItems[idx] ? calcTemplate.calcItems[idx].ID : null;
 | 
	
		
			
				|  |  |          return id;
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  | -    getFItem: function (FName){
 | 
	
		
			
				|  |  | -        let me = this;
 | 
	
		
			
				|  |  | +    getFItem: function (FName, calcTemplate){      // F3 → calcItems[2] → {Object}
 | 
	
		
			
				|  |  |          let idx = FName.slice(1) - 1;
 | 
	
		
			
				|  |  | -        return me.calcTemplate.calcItems[idx];
 | 
	
		
			
				|  |  | +        return calcTemplate.calcItems[idx];
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  | -    isCycleCalc: function (expr) {     // @5+@6  这里已经是ID引用
 | 
	
		
			
				|  |  | -        let me = this;
 | 
	
		
			
				|  |  | +    isCycleCalc: function (expression, calcTemplate) {     // 这里判断expression,是ID引用: @5+@6
 | 
	
		
			
				|  |  | +        let me = analyzer;
 | 
	
		
			
				|  |  |          
 | 
	
		
			
				|  |  | -        function isCycle(nodeExpr) {
 | 
	
		
			
				|  |  | +        function isCycle(nodeExpr, template) {
 | 
	
		
			
				|  |  |              let atIDArr = me.getAtIDArr(nodeExpr);
 | 
	
		
			
				|  |  |              for (let atID of atIDArr){
 | 
	
		
			
				|  |  |                  let ID = atID.slice(1);
 | 
	
		
			
				|  |  | -                let item = me.calcTemplate.compiledCalcItems[ID];
 | 
	
		
			
				|  |  | +                let item = template.compiledCalcItems[ID];
 | 
	
		
			
				|  |  |                  if (item.expression.includes(atID)) {
 | 
	
		
			
				|  |  |                      return true;
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |                  else {
 | 
	
		
			
				|  |  | -                    isCycle(item.expression);
 | 
	
		
			
				|  |  | +                    isCycle(item.expression, template);
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              };
 | 
	
		
			
				|  |  |              return false;
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        return isCycle(expr);
 | 
	
		
			
				|  |  | +        return isCycle(expression, calcTemplate);
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  | -    isLegal: function (expr) {   // 调用前必须先标准化
 | 
	
		
			
				|  |  | -        let me = this;
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | +    isLegal: function (dispExpr, calcTemplate) {  // 检测包括:无效字符、基数是否中括号、基数是否定义、行引用、循环计算
 | 
	
		
			
				|  |  | +        let me = analyzer;
 | 
	
		
			
				|  |  | +        let expr = me.standard(dispExpr);
 | 
	
		
			
				|  |  |          let invalidChars = /[^0-9\u4e00-\u9fa5\+\-\*\/\(\)\.\[\]F%]/g;
 | 
	
		
			
				|  |  |          if (invalidChars.test(expr)){
 | 
	
		
			
				|  |  |              alert('表达式中含有无效的字符!');
 | 
	
	
		
			
				|  | @@ -787,38 +782,72 @@ let analyzer = {
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // 行引用检测、行引用转ID引用
 | 
	
		
			
				|  |  |          let arrF = me.getFArr(expr);
 | 
	
		
			
				|  |  |          for (let F of arrF){
 | 
	
		
			
				|  |  |              let num = F.slice(1);
 | 
	
		
			
				|  |  | -            if (num > me.calcTemplate.calcItems.length){
 | 
	
		
			
				|  |  | +            if (num > calcTemplate.calcItems.length){
 | 
	
		
			
				|  |  |                  alert('表达式中 “F'+ num +'” 行号引用错误!');
 | 
	
		
			
				|  |  |                  return false;
 | 
	
		
			
				|  |  |              };
 | 
	
		
			
				|  |  | -            let id = me.getFID(F);
 | 
	
		
			
				|  |  | -            let fn = new RegExp(F, "g");
 | 
	
		
			
				|  |  | -            expr = expr.replace(fn, `@('${id}')`);
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        // 循环计算
 | 
	
		
			
				|  |  | -        if (me.isCycleCalc(expr)){
 | 
	
		
			
				|  |  | +        let expression = me.getExpression(expr, calcTemplate);
 | 
	
		
			
				|  |  | +        if (me.isCycleCalc(expression, calcTemplate)){
 | 
	
		
			
				|  |  |              alert('表达式中有循环计算!');
 | 
	
		
			
				|  |  |              return false;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |          return true;
 | 
	
		
			
				|  |  |      },
 | 
	
		
			
				|  |  | -    analyzeUserExpr: function(calcTemplate, calcItem){
 | 
	
		
			
				|  |  | -        let me = this;
 | 
	
		
			
				|  |  | -        me.calcTemplate = calcTemplate;
 | 
	
		
			
				|  |  | +    analyzeUserExpr: function(calcItem, calcTemplate){
 | 
	
		
			
				|  |  | +        let me = analyzer;
 | 
	
		
			
				|  |  |          let expr = calcItem.dispExpr;
 | 
	
		
			
				|  |  |          expr = me.standard(expr);
 | 
	
		
			
				|  |  |          calcItem.dispExpr = expr;
 | 
	
		
			
				|  |  | -        if (me.isLegal(expr)){
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (me.isLegal(expr, calcTemplate)){
 | 
	
		
			
				|  |  |              calcItem.expression = expr;
 | 
	
		
			
				|  |  |              return true;
 | 
	
		
			
				|  |  |          }else
 | 
	
		
			
				|  |  |              return false;
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    getExpression: function (dispExpr, calcTemplate) {
 | 
	
		
			
				|  |  | +        function refLineToID(expr, template) {
 | 
	
		
			
				|  |  | +            let rst = expr;
 | 
	
		
			
				|  |  | +            let fArr = me.getFArr(rst);
 | 
	
		
			
				|  |  | +            let IDArr = [];
 | 
	
		
			
				|  |  | +            for (let F of fArr){
 | 
	
		
			
				|  |  | +                let ID = me.getFID(F, template);
 | 
	
		
			
				|  |  | +                IDArr.push(ID);
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +            for (let i = 0; i < fArr.length; i++) {
 | 
	
		
			
				|  |  | +                let patt = new RegExp(fArr[i]);
 | 
	
		
			
				|  |  | +                let val = `@${IDArr[i]}`;
 | 
	
		
			
				|  |  | +                rst = rst.replace(patt, val);
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +            return rst;
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        let me = analyzer;
 | 
	
		
			
				|  |  | +        let expr = me.standard(dispExpr);
 | 
	
		
			
				|  |  | +        return refLineToID(expr, calcTemplate);
 | 
	
		
			
				|  |  | +    },
 | 
	
		
			
				|  |  | +    getCompiledExpr: function (expression) {
 | 
	
		
			
				|  |  | +        let me = analyzer;
 | 
	
		
			
				|  |  | +        let rst = expression;
 | 
	
		
			
				|  |  | +        let atIDArr = me.getAtIDArr(rst);
 | 
	
		
			
				|  |  | +        let IDArr = atIDArr.map(function (atID) {
 | 
	
		
			
				|  |  | +            return atID.slice(1);
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        for (var i = 0; i < atIDArr.length; i++) {
 | 
	
		
			
				|  |  | +            let patt = new RegExp(atIDArr[i]);
 | 
	
		
			
				|  |  | +            let val = `$CE.at(${IDArr[i]})`;
 | 
	
		
			
				|  |  | +            rst = rst.replace(patt, val);
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        rst = rst.replace(/\[/g, "$CE.base('");
 | 
	
		
			
				|  |  | +        rst = rst.replace(/\]/g, "')");
 | 
	
		
			
				|  |  | +        return rst;
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -950,7 +979,7 @@ class CalcProgram {
 | 
	
		
			
				|  |  |          template.hasCompiled = false;
 | 
	
		
			
				|  |  |          template.errs = [];
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        let private_extract_ID = function(str, idx){
 | 
	
		
			
				|  |  | +        /*let private_extract_ID = function(str, idx){
 | 
	
		
			
				|  |  |              let rst = '', lBracket = 0, rBracket = 0, firstIdx = idx, lastIdx = 0;
 | 
	
		
			
				|  |  |              for (let i = idx; i < str.length; i++) {
 | 
	
		
			
				|  |  |                  if (str[i] === '(') {
 | 
	
	
		
			
				|  | @@ -973,8 +1002,8 @@ class CalcProgram {
 | 
	
		
			
				|  |  |                  }
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  |              return rst;
 | 
	
		
			
				|  |  | -        };
 | 
	
		
			
				|  |  | -        let private_parse_ref = function(item, itemIdx){
 | 
	
		
			
				|  |  | +        };*/
 | 
	
		
			
				|  |  | +        /*let private_parse_ref = function(item, itemIdx){
 | 
	
		
			
				|  |  |              let idx = item.expression.indexOf('@(', 0);
 | 
	
		
			
				|  |  |              while (idx >= 0) {
 | 
	
		
			
				|  |  |                  let ID = private_extract_ID(item.expression, idx);
 | 
	
	
		
			
				|  | @@ -996,6 +1025,37 @@ class CalcProgram {
 | 
	
		
			
				|  |  |              if (template.compiledSeq.indexOf(itemIdx) < 0) {
 | 
	
		
			
				|  |  |                  template.compiledSeq.push(itemIdx);
 | 
	
		
			
				|  |  |              }
 | 
	
		
			
				|  |  | +        };*/
 | 
	
		
			
				|  |  | +        let private_extract_ID = function(str, idx){
 | 
	
		
			
				|  |  | +            let subStr = str.slice(idx);
 | 
	
		
			
				|  |  | +            let patt = new RegExp(/@\d+/);
 | 
	
		
			
				|  |  | +            let atID = subStr.match(patt);
 | 
	
		
			
				|  |  | +            let ID = atID ? atID[0].slice(1) : null;
 | 
	
		
			
				|  |  | +            return ID;
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +        let private_parse_ref = function(item, itemIdx){
 | 
	
		
			
				|  |  | +            let idx = item.expression.indexOf('@', 0);
 | 
	
		
			
				|  |  | +            while (idx >= 0) {
 | 
	
		
			
				|  |  | +                let ID = private_extract_ID(item.expression, idx);
 | 
	
		
			
				|  |  | +                let len = ID ? ID.toString().length : 0;
 | 
	
		
			
				|  |  | +                if (len) {
 | 
	
		
			
				|  |  | +                    let subItem = template.compiledCalcItems[ID];
 | 
	
		
			
				|  |  | +                    if (subItem) {
 | 
	
		
			
				|  |  | +                        if (subItem.ID !== item.ID) {
 | 
	
		
			
				|  |  | +                            private_parse_ref(subItem, template.compiledCalcItems[ID + "_idx"]);
 | 
	
		
			
				|  |  | +                        } else {
 | 
	
		
			
				|  |  | +                            template.errs.push("循环引用ID: " + ID);
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    } else {
 | 
	
		
			
				|  |  | +                        template.errs.push("找不到ID: " + ID);
 | 
	
		
			
				|  |  | +                        console.log('找不到ID: ' + ID);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                idx = item.expression.indexOf('@', idx + len + 1);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (template.compiledSeq.indexOf(itemIdx) < 0) {
 | 
	
		
			
				|  |  | +                template.compiledSeq.push(itemIdx);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          };
 | 
	
		
			
				|  |  |          let private_compile_items = function() {
 | 
	
		
			
				|  |  |              for (let idx of template.compiledSeq) {
 | 
	
	
		
			
				|  | @@ -1003,10 +1063,8 @@ class CalcProgram {
 | 
	
		
			
				|  |  |                  item.dispExprUser = item.dispExpr;    // 用于界面显示。disExpr是公式模板,不允许修改:人工系数占位符被修改后变成数值,第二次无法正确替换。
 | 
	
		
			
				|  |  |                  if (item.expression == 'HJ')
 | 
	
		
			
				|  |  |                      item.compiledExpr = '$CE.HJ()'
 | 
	
		
			
				|  |  | -                else{
 | 
	
		
			
				|  |  | -                    item.compiledExpr = item.expression.split('@(').join('$CE.at(');
 | 
	
		
			
				|  |  | -                    item.compiledExpr = item.compiledExpr.split('base(').join('$CE.base(');
 | 
	
		
			
				|  |  | -                };
 | 
	
		
			
				|  |  | +                else
 | 
	
		
			
				|  |  | +                    item.compiledExpr = analyzer.getCompiledExpr(item.expression);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |                  if (item.labourCoeID){
 | 
	
		
			
				|  |  |                      let lc = me.compiledLabourCoes[item.labourCoeID].coe;
 | 
	
	
		
			
				|  | @@ -1035,6 +1093,7 @@ class CalcProgram {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |              for (let i = 0; i < template.calcItems.length; i++) {
 | 
	
		
			
				|  |  |                  let item = template.calcItems[i];
 | 
	
		
			
				|  |  | +                item.expression = analyzer.getExpression(item.dispExpr, template);
 | 
	
		
			
				|  |  |                  template.compiledCalcItems[item.ID] = item;
 | 
	
		
			
				|  |  |                  template.compiledCalcItems[item.ID + "_idx"] = i;
 | 
	
		
			
				|  |  |              }
 | 
	
	
		
			
				|  | @@ -1379,7 +1438,8 @@ class CalcProgram {
 | 
	
		
			
				|  |  |      /* 计算所有树结点(分3种情况),并返回发生变动的零散的多个树结点。参数取值如下:
 | 
	
		
			
				|  |  |          calcAllType.catAll       计算所有树结点 (不指定参数时的默认值)
 | 
	
		
			
				|  |  |          calcAllType.catBills     计算所有清单 (改变项目属性中清单取费算法时会用到)
 | 
	
		
			
				|  |  | -        calcAllType.catRations   计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到)
 | 
	
		
			
				|  |  | +        calcAllType.catRations   计算所有定额、工料机形式的定额、量价,因为它们都走自己的计算程序 (改变人工系数、费率值、工料机单价时会用到)  不要用
 | 
	
		
			
				|  |  | +        缺陷:calcAllType.catRations 参数情况不会计算父结点。(calcAllType.catBills 可以,因为清单的父结点也是清单会计算)
 | 
	
		
			
				|  |  |      */
 | 
	
		
			
				|  |  |      calcAllNodes(calcType = calcAllType.catAll){
 | 
	
		
			
				|  |  |          let me = this;
 | 
	
	
		
			
				|  | @@ -1514,4 +1574,6 @@ class CalcProgram {
 | 
	
		
			
				|  |  |          baseNodes.push(calcTools.getNodeByFlag(fixedFlag.CHARGE));
 | 
	
		
			
				|  |  |          return me.getTotalFee(baseNodes, excludeNodes);
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | -};
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +// export default analyzer;
 |