目录
前言
最近遇到一个非常复杂的需求:单子在流转过程中,根据单子内的数据匹配展示不同的操作按钮和表单内容。
听着很简单,写几个if/else直接判断完事。但是坑在后面啊,这类单子的实际流程居然在不同地区存在差异,进而导致单子内的数据也存在一定的差异。
这...,写if/else那是万万不行了,总不能每个地区写一套判断代码,部署时再做注释显示,而且在实测中万一有一点差别,就得改代码重新部署,很不现实。
于是我就在想,有没有一个可配置的方法,匹配全部操作按钮的显隐达到控制流程、匹配表单内部输入框的显隐达到数据流转时信息的不同。这样在每个地区,我只要改动配置就可以达到想要的按钮权限以及表单。(无尽叹息,说好的写几个if/else就能搞定的呢?)
规则
javascript
{
rule: [
{
rule: [
{
rule: [{
key: '', // key的值如果是数组或者对象,key.key2(用点连接)取对象下key2的值或者数组下每个对象的key2的值。
valueKey: '', // 从customValue中获取,所以不能乱写。
value: ['', '', ...], // number || string || array。 优先级低于valueKey
judge: 1, // 1代表等于,2代表不等于,3代表大于,4代表小于,5代表属于,6代表不属于。 key或value等于empty时,judge只支持1、2
judgeType: 1, // 1代表key的值属不属于、大于、小于value/valueKey的值;2代表value/valueKey的值属不属于、大于、小于key。 key等于empty时,judgeType必须选2;value等于empty时,judgeType必选选1。
judgeMode: 1, // 被比较的值为数组时生效,非数组选1。 1代表有一个成立就成立,2代表全部成立才成立
}, ...],
type: 1, // 1代表且,2代表或,即rule规则同且还是或判断
},
...
],
type: 1, // 1代表且,2代表或,即rule规则同且还是或判断
},
...
],
type: 1, // 1代表且,2代表或,即rule规则同且还是或判断
}
配置界面

方法
javascript
const isEmpty = (val) => {
if (val == null) return true; // covers null and undefined
if (val === '') return true;
if (Array.isArray(val) && val.length === 0) return true;
if (typeof val === 'object' && !Array.isArray(val) && Object.keys(val).length === 0) return true;
return false;
};
const isNumeric = (val) => {
if (typeof val !== 'number' && typeof val !== 'string') {
return false;
}
if (typeof val === 'number') {
return Number.isFinite(val); // ✅ 使用 Number.isFinite
}
if (typeof val === 'string') {
const trimmed = val.trim();
if (trimmed === '') return false;
// 注意:Number('') → 0,但我们已经排除了空串
const num = Number(trimmed);
// 必须是有限数,且不能是 NaN
return !Number.isNaN(num) && Number.isFinite(num); // ✅
}
return false;
};
const getValueByKey = (data, keyPath) => {
const keys = keyPath.split('.');
let values = [data];
for (const key of keys) {
const temp = [];
for (const item of values) {
if (item === undefined || item === null) continue;
if (Array.isArray(item)) {
item.forEach(i => {
if (i !== null && i !== undefined) {
const val = i[key];
if (Array.isArray(val)) temp.push(...val);
else temp.push(val);
}
});
} else {
const val = item[key];
if (Array.isArray(val)) temp.push(...val);
else temp.push(val);
}
}
values = temp.filter(v => v !== undefined);
}
return values;
};
const compare = (s, judge, target) => {
const targetArr = Array.isArray(target) ? target : [target];
const isString = val => typeof val === 'string';
switch (judge) {
case 1: // 等于
if(targetArr[0] === 'empty'){
return isEmpty(s)
}
return targetArr.some(t => {
if (t === s) return true; // 快速路径
// 尝试数值兼容
if (isNumeric(t) && isNumeric(s)) {
return Number(t) === Number(s);
}
// 或者更激进:所有情况都转字符串比?
// return String(t) === String(s);
return false;
});
case 2: // 不等于
if(targetArr[0] === 'empty'){
return !isEmpty(s)
}
return !targetArr.some(t => {
if (t === s) return true;
if (isNumeric(t) && isNumeric(s)) {
return Number(t) === Number(s);
}
return false;
});
case 3: { // 大于
if (!isNumeric(s)) return false;
const sNum = Number(s);
const validTargets = targetArr
.filter(isNumeric)
.map(Number);
return validTargets.length > 0 && sNum > Math.max(...validTargets);
}
case 4: { // 小于
if (!isNumeric(s)) return false;
const sNum = Number(s);
const validTargets = targetArr
.filter(isNumeric)
.map(Number);
return validTargets.length > 0 && sNum < Math.min(...validTargets);
}
case 5: // 属于(目标包含源)
return targetArr.some(t => {
if (Array.isArray(t)) {
return t.includes(s);
} if (isString(t) && isString(s)) {
return t.includes(s); // 修正为判断目标是否包含源
}
return t === s;
});
case 6: // 不属于
return !targetArr.some(t => {
if (Array.isArray(t)) {
return t.includes(s);
} if (isString(t) && isString(s)) {
return t.includes(s);
}
return t === s;
});
default:
return false;
}
};
export const evaluateRule = (rule, data, customValue = {}) => {
if(isEmpty(rule)){
return true
}
if ('rule' in rule) {
const results = rule.rule.map(r => evaluateRule(r, data, customValue));
return rule.type === 1 ? results.every(Boolean) : results.some(Boolean);
}
const { key, value: originalValue, valueKey, judge, judgeType, judgeMode = 1 } = rule;
const value = valueKey ? customValue[valueKey] : originalValue;
const keyValues = key === 'empty' ? ['empty'] : getValueByKey(data, key);
const targetValue = Array.isArray(value) ? value : [value];
const [source, target] = judgeType === 1
? [keyValues, targetValue]
: [targetValue, keyValues];
const checks = source.map(s => compare(s, judge, target));
return judgeMode === 1 ? checks.some(Boolean) : checks.every(Boolean);
};
示例
javascript
const customValue = {
'nameArr': ['wang', 'li'],
'id': '123'
};
const data = {
aaa: [{
aa: 1
}, {
aa: 11
}, {
aa: 111
}],
bbb: 2,
ccc: [{
cc: [{
c: [1, 2, 3]
}]
}],
name: 'wang',
crhCjPoliceSituationDTOS: [
{
notSignUser: '123,456'
}
],
or1: 123,
or2: 234
};
const rule = {
rule: [
{
key: 'aaa.aa',
value: 11, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'bbb',
value: 2, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'ccc.cc.c',
value: 2, // 我们要查找的值
judge: 5, // 属于
judgeType: 2, // value属于key的值
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'name',
valueKey: 'nameArr', // 动态值
judge: 5, // 属于
judgeType: 1, // key的值属于valueKey的值
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'crhCjPoliceSituationDTOS.notSignUser',
valueKey: 'id', // 动态值
judge: 5, // 属于
judgeType: 2, // value属于key的值
judgeMode: 1 // 只要有一个成立就成立
},
{
rule: [
{
key: 'or1',
value: 123, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'or2',
value: 123, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
}
],
type: 2
}
],
type: 1 // '与'关系,这里只有一个规则,所以type对结果无影响
};
evaluateRule(rule, data, customValue);
缺陷
目前对于数组对象[{},{}]这类数据,是做的单条件匹配,什么意思呢?比如a=[{id:1,val:2},{id:2,val:1}],我需要判定存在一条数据a=1且val=1的才为true,那么上述的规则引擎将无法实现。rule配置最多只能如下
bash
{
rule: [{
key: 'a.id',
value: 1, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
},
{
key: 'a.val',
value: 1, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
}],
type: 1 // 1代表且,2代表或,即rule规则同且还是或判断
}
而这个规则代表什么?当然是数组a中只要存在id=1的一条数据和val=1的一条数据就为true。
改动方案建议(此处不做实现):区分对象与数组,如果是对象{a:{val:1}},保持现状用a.val,如果是数组对象{a:[{val:1},{val:2}]},用a[].val。如此时,上述的规则可用作 "判定存在一条数据a=1且val=1" 。那又该如何写数组a中只要存在id=1的一条数据和val=1的一条数据就为true的规则,如下即可:
bash
{
rule: [{
rule: [{
key: 'a.id',
value: 1, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
}],
type: 1 // 1代表且,2代表或,即rule规则同且还是或判断
},
{
rule: [{
key: 'a.val',
value: 1, // 我们要查找的值
judge: 1, // 等于
judgeType: 1, // key的值等于value
judgeMode: 1 // 只要有一个成立就成立
}],
type: 1 // 1代表且,2代表或,即rule规则同且还是或判断
}],
type: 1 // 1代表且,2代表或,即rule规则同且还是或判断
}