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) //计算函数,传入当前行数据键值集合,返回计算结果 }]
表达式解析基本都在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贰元整"
每个表单控件都设置了一个叫 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); })
遍历所有字段时找出设置有规则(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) //规则解析函数,传入主表、子表数据键值集合,返回解析结果 }]
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这个文件夹中我们配置了一个解析大于等于小于包含不包含等等一些列的规则匹配对象;
'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
+-*/
按四则运算优先级运算,遇到非数值类型时转字符串拼接