前端可配置权限规则案例

目录

前言

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

听着很简单,写几个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规则同且还是或判断
} 
相关推荐
mldong31 分钟前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60612 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角3 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!3 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi5 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马6 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端