1、计算字段
1.1 获取及处理计算字段表达式
field // 字段对象
expression // 计算字段表达式
childrenCode // 字段 code
functions //API 函数要用到的 url 及参数
遍历所有字段时找出 type 为 calculation
的就是计算字段,调用 resolveCalculation
统一处理所有计算字段,并将结果存储在名叫 sub_calculation
的集合内;
sub_calculation 数据格式如下:
[{
"code":"js1", // 计算字段 code
"childrenCode":"zb_cod", // 子表 code
"dependenceFields":["num2","zb_cod.num"], // 计算表达式中涉及到的字段,这些字段值变化时将触发计算字段计算
"execFn":execFn(data) // 计算函数,传入当前行数据键值集合,返回计算结果
}]
1.2 计算字段表达式解析
表达式解析基本都在 web\mobile\src\utils\parser.js 这个文件中完成
第一步:调用 parse
方法开始解析表达式,使用 getFields
方法获取到表达式中参与计算的所有字段,然后用 replaceFields
方法将表达式中字段 code 替换成 value 值。如 SUN([main.num1],[main.num2])转换后为 SUM(1,2);
第二步:拆分表达式,使用正则按照运算优先级依次解析函数, 直到匹配不到函数,如下:
'1 - SUM(ROUND(1,2)) + CNMoney(ROUND(2,2))'.match(/(CNMoney\(|MIN\(|MAX\(|APIEVAL\(|ROUND\(|SUM\()[^\(\)]+\)/g)
["ROUND(1,2)", "ROUND(2,2)"]
然后算出函数值并进行值替换,替换后结果为: '1 - SUM(1.00)) + CNMoney(2.00)'
最终结果为:'1 - 1.00 + 贰元整'
第三步:四则运算
此时表达式就只剩四则运算了,将表达式转化成逆波兰表达式所需要的格式:["1", "-", "1", ".", "0", "0", "+", "贰", "元", "整"]
根据四则运算优先级转换后的表达式为:[1, 1, "-", "贰元整", "+"]
根据逆波兰表达式此算式运算过程如下:
从左往右依次取值,如果不是运算符则 push 到 stack
中存起来,遇到运算符取出 stack
中的值进行运算,运算结果插入表达式中
1 stack-->[1] [1, "-", "贰元整", "+"]
1 stack-->[1,1] ["-", "贰元整", "+"]
- stack-->[] ["0", "贰元整", "+"]
0 stack-->[0] ["贰元整", "+"]
贰元整 stack-->[0,"贰元整"] ["+"]
+ stack-->[] ["0 贰元整"]
当已经找不到运算符时,表达式如果是正确的, 最终, 栈⾥里里还有⼀一个元素, 且正是表达式的计算结果
最后结果就是:"0 贰元整"
1.3 触发计算字段计算
每个表单控件都设置了一个叫 dataChange
的监听事件,字段值一旦发生改变便会触发 dataChange
事件,该函数会返回触发事件的字段对象及修改后的 value;
然后在 dataChange
函数内调用 setCalculationValue
方法获取计算字段值。
在 setCalculationValue
方法内遍历刚刚处理好计算字段数据的 sub_calculation 集合,找出当前修改值的字段是否有被其他计算字段引用,如果有那么就执行 execFn 方法获取计算字段值。
this.sub_calculation.forEach(calculation => {if (calculation.dependenceFields.includes(fieldCode) && calculation.childrenCode == this.code) {let value = calculation.execFn(fieldsObj);
})
2、规则解析
2.1 获取规则表达式
遍历所有字段时找出设置有规则(rules)的字段,调用 resolveFieldRule
方法分别处理显示隐藏(hidden)、必填(required)、样式(style);resolveFieldRule
方法跟计算字段的 resolveCalculation
方法类似,它也会统一处理字段规则,最后将所有字段规则存储在一个叫 fRules
的集合中;fRules
最终的数据格式如下:
[{
"code":"rule1", // 设置了规则字段 code
"group":"ruel_cod", // 设置规则的字段所在的表 code
"field",// 当前字段数据对象
"target": {rules: [], // 规则集合
symbol: "AND" // 筛选逻辑
},
"type": 'is_show',// 规则类型(is_show:显示隐藏、required:必填、style:样式)"styleRule" :{color:red},// 样式规则样式内容
"dependenceFields":["num2","zb_cod.num"], // 规则表达式中涉及到的字段,这些字段值变化时将规则解析
"execFn":execFn(data) // 规则解析函数,传入主表、子表数据键值集合,返回解析结果
}]
2.2 规则表达式解析
execFn
函数首先会拆分出业务对象规则和表达式规则,表达式规则解析同计算字段表达式解析,所有表达式解析计算后将结果替换至原规则表达式中,最后解析业务对象规则;
具体过程如下:
配置:
调用 resolveFieldRule
方法后表达式解析如下:
‘SUM(ROUND(1,2)) > [main.num2] AND f.equal([main.num],11)’
在 execFn
函数中它将被分成两部分
1、SUM(ROUND(1,2)) > [main.num2](同计算字段表达式解析)
2、f.equal([main.num],11)
首先解析表达式 1,如 main.num2 = 0
;返回结果为true
, 然后替换原表达式为:'true AND f.equal([main.num],11)'
在 web\mobile\src\utils\converter.js 这个文件夹中我们配置了一个解析大于等于小于包含不包含等等一些列的规则匹配对象;
然后将 AND、OR 转换为 && 和 ||:'true && f.equal([main.num],11)'
最后通过 new Function 的方法解析结果:new Function('main', 'children' ,'f',
return ${expression_fn.replace(/[/g, ”).replace(/]/g, ”)});
main: {num:11}
children: {zb:[{num:0}]}
expression_fn 'true && f.equal([main.num],11)'
f {equal:(lv,rl)=>{return lv===rv}}
函数最终解析结果为:
true && f.equal(11,11)
true && true
true
规则解析逻辑
比较逻辑
字段被删除时表达式返回 false
文本:
等于
内容都为空或相等时返回 true,其余返回 false
不等于
内容不相等时返回 true,其余返回 false
包含
文本 1 包含文本 2 时返回 true,其余返回 false
不包含
文本 1 不包含文本 2 时返回 true,其余返回 false
起始字符
文本 2 为文本 1 起始字符返回 true,其余返回 false
为空
文本 1 为空返回 true,其余返回 false
不为空
文本 1 不为空返回 true,其余返回 false
多行文本、下拉、单选、复选、组织选择、人员选择、城市选择:规则同文本
组织选择
比较时取 `org_id`
人员选择
比较时取 `auditor_id`
城市选择
比较时取 `CityEN`
数值、计算:
等于
同文本
不等于 / 大于等于 / 小于等于 / 大于 / 小于
满足不等于 / 大于等于 / 小于等于 / 大于 / 小于时返回 true,其余返回 false
日期:
大于 / 大于等于
日期 1 或日期 2 值为空时返回 false,都有值且满足大于 / 大于等于返回 true,其余返回 false
小于 / 小于等于
同大于 / 大于等于
其他规则同文本
函数计算逻辑
APIEVAL
接口返回值数据类型
符合配置类型:直接返回接口返回值
不符合配置类型(或接口异常):配置类型为字符串则返回空字符串
配置类型为数字则返回 0
配置类型为日期则返回空字符串
CNMoney
值异常或为空时返回零元整
值四舍五入保留两位小数
SUM
值异常或为空时返回 0
ROUND
值异常或为空时按 0 处理
MIN/MAX
值异常或为空时返回 0
+-*\
按四则运算优先级运算,遇到非数值类型时转字符串拼接