Vue平台项目-用户权限控制(含页面、操作按钮)解决案例概述

本文主要介绍了在Vue项目中实现资源权限管理的方法,包括如何处理后端返回的资源树数据,使用ElementUI的el-tree组件,以及通过导航守卫和自定义指令实现页面菜单和操作按钮的权限控制。此外,还探讨了不同权限管理方案的适用场景和优缺点。
摘要由CSDN通过智能技术生成


从jQuery项目到Vue项目开发过多次关于" 资源-角色-用户"的权限管理内容,这篇文章的侧重点在于vue项目 用户所拥有的资源的展示,而不是资源-角色-用户里table\form的curd。

准备工作

先看后端RD给的资源树数据结构:

[{
  "id": 1,
  "parentId": 0,
  "resourceName": "运营管理中心",
  "resourceInfo": "运营管理中心页面资源",
  "resourceType": 0,
  "resourceUrl": "/xxxx",
  "resourceCode": "/xxxx",
  "appName": "运营管理中心"
},{
  "id": 2,
  "parentId": 1,
  "resourceName": "设备管理",
  "resourceInfo": "设备管理页面资源",
  "resourceType": 0,
  "resourceUrl": "/xxxx",
  "resourceCode": "/xxxx",
  "appName": "运营管理中心"
},{
  "id": 3,
  "parentId": 2,
  "resourceName": "设备管理-新增",
  "resourceInfo": "设备管理-按钮操作新增设备",
  "resourceType": 1,
  "resourceUrl": "/xxxx",
  "resourceCode": "/xxxx",
  "appName": "运营管理中心"
},....]

返回的是一种简单JSON数据,以前用过jQuery树型插件zTree的同学或许很熟悉。但这种简单JSON数据结构目前并不能直接在Vue的ElementUI的el-tree组件中使用,需要转化为标准JSON数据结构,这里提供了一个转化方法,供参考:

// 转换el-tree数据格式:children级联
export const convertTreeData = (data: any[], config: any) => {
    let id = config.id || 'id';
    let pid = config.pid || 'pid';
    let children = config.children || 'children';
    let idMap: any = {};
    let jsonTree: any = [];
    data.forEach((v: any) => {
        idMap[v[id]] = v;
    });
    data.forEach((v: any) => {
        let parent = idMap[v[pid]];
        if (parent) {
            !parent[children] && (parent[children] = []);
            parent[children].push(v);
        } else {
            jsonTree.push(v);
        }
    });
    return jsonTree;
};
// 调用方法:将后端返回的简单json树responseTreeData转换为el-tree识别的标准树数据结构elTreeData
const elTreeData = convertTreeData(responseTreeData, {
   id: 'id',
   pid: 'parentId',
   children: 'children'
});

Note that:convertTreeData()这个方法里的any,尽量换成自己项目里需要的类型接口interface,使用大量any会让TypeScript的价值大打折扣。

页面菜单权限控制

关于页面菜单的权限控制这里有两种方案:
【方案一】展示全部的页面菜单项,使用导航守卫拦截提示没有权限的部分。
【方案二】仅展示有权限的页面菜单项(此方案在方案一的基础上进行,导航守卫拦截是不可缺少的)。

导航守卫

实现方式概述,这里是导航守卫的拦截片段,其核心思路就是:拿到路由的to.path后,与store中存储的当前用户所拥有的该app中的权限比对判断。

// 全局前置导航
router.beforeEach(beforeEachCallback);

拦截的代码片段beforeEachCallback:

// 验证权限
const getRoleCheckNextStep: GetRoleCheckNextStep = (toPath) => {
    // 以这些 url 卡头的路径不需要验证权限
    const isNoCheckPath = [
        CommonUrls.Login,
        CommonUrls.Home,
        CommonUrls.NotFound
    ].some((path) => toPath.toLowerCase().startsWith(path));
    if (isNoCheckPath) {
        return NextSteps.Next;
    }
    // 拦截无权限的页面菜单部分
    // grantedResouse为当前用户所拥有的该app中的权限,数据结构为后端RD提供的简单树JSON
    const grantedResouse = JSON.parse(store.getters.getGrantedResouse());
    const hasAuth = grantedResouse.some((item: any) => toPath.toLowerCase().endsWith(item.resourceUrl));
    if (!hasAuth) {
        return NextSteps.Stay;
    } else {
        return NextSteps.Next;
    }
};

const roleNextStep = getRoleCheckNextStep(to.path);

if (roleNextStep === NextSteps.Stay) {
    Message.error('您没有该模块的访问权限,如有需要请与管理员联系');
    next(false);
    return;
}
next();

递归过滤

若平台中的某个系统只需要展示用户有权限的部分,eg:菜单栏里只显示有权限的部分。这时就需要采用【方案二】,具体要根据PM的mrd来敲定。
FE的研发内容,相对好处理,做递归筛查出有权限的data,然后再渲染菜单即可:

// store中当前用户对应系统的权限资源
@Getter getGrantedResouse!: () => string;
/**
 * 递归过滤配置的静态菜单选项,返回符合用户角色权限的菜单
 * @param menus 配置静态的菜单
 */
filterUserAuthMenu(menus: any[]) {
    const grantedResouse = JSON.parse(this.getGrantedResouse());
    const accessedMenus = menus.filter((menu) => {
        if (this.hasPermission(menu, grantedResouse)) {
            if (menu.children && menu.children.length) {
                menu.children = this.filterUserAuthMenu(menu.children);
            }
            return true;
        } else {
            return false;
        }
    });
    return accessedMenus;
}
 /**
  * 判断是否有权限
  * @param menu 当前菜单
  * @param grantedResouse 当前中心的权限
  * */
 hasPermission(menu: MenuConfig, grantedResouse: any[]) {
     if (menu.title) {
         return grantedResouse.some((item: any) => {
             return menu.title === item.resourceName;
         });
     }
 }
 async mounted() {
     // 只展示用户拥有权限的菜单:使用filteredMenus替代配置的静态菜单menus
     // 暂不使用过滤后的菜单:避免使用递归循环从而减少性能的损耗
     this.filteredMenus = await this.filterUserAuthMenu(this.menus);
 }

Note that:考虑到平台内各个系统的体量、前端性能等因素,FE应尽可能避免使用递归循环。本项目未采用【方案二】。

操作按钮权限控制

自定义权限控制指令

若需要对系统内增删改等按钮操作做权限控制,这里采用了自定义指令的方式来控制:

// 注册一个全局自定义指令 `v-allow` 根据权限控制操作按钮的隐显
Vue.directive('allow', {
    // 当被绑定的元素插入到 DOM 中时……
    inserted: function(el: any, binding, vnode) {
        const grantedResouse = JSON.parse(store.getters.getGrantedResouse());
        const hasAuth = grantedResouse.some((item: any) => {
            return binding.value.name === item.resourceName;
        });
        if (!hasAuth) {
            el.parentNode.removeChild(el);
        }
    }
});

使用方式

<el-button v-allow="{name: '摄像头管理-新增'}">新增</el-button>

Note that:这里从项目后续可能的扩展性考虑,在v-allow指令里传入了一个对象,便于后续版本迭代增加新的key\value,来做不同的控制。

补充说明

这种由资源-角色-用户动态配置的权限管理方案,有较高的灵活性。以前也做过一种较为简单死板的权限管理控制:页面有固定的使用角色,用户有固定的角色,这种情况下,只需要FE在自己的router里配置固定的权限拥有着然后遍历即可,配置 meta.roles 如下,eg:

{
 path: '/accountData',
 name: 'accountData',
 meta: {title: '账户管理', icon: 'fa fa-user-plus', roles: ['admin','editor']},
 component: () => import('@/views/UserManage/AccountData.vue')
}

然后根据当前用户的固定角色,与router里固定的meta.roles进行逻辑操作即可完成权限控制:

// router.beforeEach()中,权限判断
if (hasPermission(key, to)) {
    next();
} else {
    next('/404'); // 没有权限进入
}
/**
 * 判断是否有权限
 * @param roles 当前角色
 * @param route 当前路由对象
 * */
function hasPermission(roles: string, route: any) {
    if (route.meta && route.meta.roles) {
        // 如果meta.roles是否包含角色的key值,如果包含那么就是有权限,否则无权限
        return route.meta.roles.some((role: string) => {
            return role.indexOf(roles) >= 0;
        });
    } else {
        // 默认不设置有权限
        return true;
    }
}

在补充说明中的这种权限处理方案有较大的局限性,适用于小型系统。现在回头再看,还是对于前端来说,还是jQuery时代做权限管理带劲,前端直接拿到资源树渲染菜单就行了,因为view路由都在后端的Controller里拦截。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值