前端可配置权限规则案例

目录

前言

最近遇到一个非常复杂的需求:单子在流转过程中,根据单子内的数据匹配展示不同的操作按钮和表单内容。

听着很简单,写几个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规则同且还是或判断
} 
相关推荐
zhougl9962 小时前
前端模块化
前端
暴富暴富暴富啦啦啦2 小时前
Map 缓存和拿取
前端·javascript·缓存
天问一2 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js
dodod20122 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端
小魏的马仔2 小时前
【elementui】el-date-picker日期选择框,获取焦点后宽度增加问题调整
前端·vue.js·elementui
JarvanMo2 小时前
想让你的 Flutter UI 更上一层楼吗?
前端
soul g2 小时前
npm 包发布流程
前端·npm·node.js
踢球的打工仔3 小时前
jquery的基本使用(5)
前端·javascript·jquery
开发者小天3 小时前
react中的useDebounceEffect用法
前端·react.js·前端框架