运营平台系统在菜单权限的基础上进一步细化到按钮权限管理(Vue)

3,248 阅读5分钟

需求背景

原先风控人员与业务人员都是在不同的菜单下操作业务,但最近开发的一些业务有存在风控人员与业务人员会在同一菜单下操作不同的功能。比如业务人员只能查看数据而不能点击页面上操作数据的按钮功能,之前运营平台只是菜单的权限,并没向下细致到按钮的级别。经过三次前后端会议的讨论、设计方案的评审、人员的投入,最终确定了开始开发。

修改前后对比

原系统设计以及修改后的设计,用些图和数据可能更方便阅读者理解。

1.数据对比

原后端返回个人权限树示例

[
    {
        resourceId: 5,
        parentId: 0,
        name: "一级菜单",
        type: "page",
        icon: null,
        orderNum: 2
        isCheck: null,
        key: "*******",
        url: null,
        perms: null,
        children: [
            {
                resourceId: 5,
                parentId: 0,
                name: "二级菜单",
                type: "page",
                icon: null,
                orderNum: 2,
                isCheck: null,
                key: "********",
                url: null,
                perms: null,
            }
        ]
    }
]

修改后的返回权限树示例

[
    {
        resourceId: 5,
        parentId: 0,
        name: "一级菜单",
        type: "page",
        icon: null,
        orderNum: 2
        isCheck: null,
        key: "*******",
        url: null,
        perms: null,
        children: [
            {
                resourceId: 309,
                parentId: 5,
                name: "二级菜单",
                type: "page",
                icon: null,
                orderNum: 2,
                isCheck: null,
                key: "********",
                url: null,
                perms: null,
                children:[
                    {
                        resourceId: 1215,
                        parentId: 309,
                        name: "删除",
                        type: "button",
                        icon: null,
                        orderNum: 2,
                        isCheck: null,
                        key: "********",
                        url: null,
                        perms: null,  
                    },
                    {
                        resourceId: 1216,
                        parentId: 309,
                        name: "修改",
                        type: "button",
                        icon: null,
                        orderNum: 2,
                        isCheck: null,
                        key: "********",
                        url: null,
                        perms: null,  
                    }
                ]
            }
        ]
    }
]

在权限数据的修改是向下增加了一层children,第三层children的数据主要都是细致到按钮的数据。权限树中的每一项数据我们叫做资源,每一个资源都可以在数据库中绑定一个或者多个接口,根据需要也可以不绑定接口。

2.整个权限流程的设计

前端原系统上修改以及增加功能点

  1. 原菜单只是对比下后端数据中是否存在某个菜单,但是排序还是按照前端定义的路由树来排序。这次需要修改为按后端排序来排序菜单的顺序。
  2. 新增一个v-permission自定义指令,用于处理按钮的显示隐藏。
  3. 新增资源管理接口管理用于开发者录入资源和接口的数据(原先数据库走脚本插入数据的方式可视化)。

前端实现

1.之前实现动态路由的地方需要进行修改。

做之前思考了两种修改方式:
1.循环权限树,动态的component: () => import('@/views/.../.../...')
webpack打包后文件加上了hash值,生成的文件目录也不是简单的按照当前的目录分布文件。要走通这种方式有点复杂,需要针对的增加一些打包的方式。
2.因为权限树中每个资源都有唯一的key值,可建立一个根据key值为键值的存储Router树对象。
例如:


export const constantRouterObj = {
    'key1':{
        path: 'business',
        name: ' business',
        hidden: true,
        component: () => import('@/views/credit/business/index'),
        meta: { title: '业务管理', icon: 'el-icon-document' },
      },
    'key2':{
        path: 'apply',
        name: ' apply',
        hidden: true,
        component: () => import('@/views/credit/apply/index'),
        meta: { title: '业务管理', icon: 'el-icon-document' },
      }
};

//在循环服务端返回资源树的数据时,可在循环时通过
constantRouterObj[elment['key']]  //取到对应的路由对象,在自行组合成addRouters需要的数据格式。

但是之前的实现方式Router对象是树形的嵌套方式,几十个路由。需要全改为key value的对象形式太麻烦了,改了还担心影响老的业务,毕竟这么大的运营平台、这么多业务在使用。也担心测试在测试的时候,回归测试点有漏掉的,或者是没涉及到的点。最后导致影响线上业务,还是有一丝担心。

之前路由树部分代码截图

2.思考了第三种方案,也是最终的方案。

终极方案的优势:
1.原先前端定义的路由树不做数据格式变化。
2.最小化对原有实现方式的影响。
3.减少代码修改处。
缺点:
1.多进行了一些遍历。

前端路由树和后端权限树做对比

/**
 * 前端路由树和后端权限树做对比
 * @param {*} routers 前端自定义的路由树
 * @param {*} permission 后端返回的权限数据
 */
function filterRouters(routers, permission) {
  for (const router of routers) {
    for (const item of permission) {
      if (router.name.trim() === item.key.trim() && item.type === 'page') {
        router.hidden = false;
        router.meta.orderNum = item.orderNum;   //后端权限树给出的当前资源排序位置,导航菜单在渲染的时候需要依赖这个排序字段来排序菜单
        if (router.children && router.children.length && item.children && item.children.length) {
          router.children = filterRouters(router.children, item.children, key).routers;
        }
      }
    }
  }
  return routers;
}

遍历出服务端返回权限树中的所有key

/**
 * 遍历所有key,实现v-permission自定义指令的时候会用到
 * @param {*} permission
 * @param {*} key
 */
function filterKey(permission, key = []) {
  for (const item of permission) {
    key.push(item.key);
    if (item.children && item.children.length) {
      filterKey(item.children, key);
    }
  }
//返回所有key的Array
  return key;
}

vuex中处理

//返回的Promise方便router导航守卫调用
actions: {
    generateRouters({ commit, getters }) {
      return new Promise((resolve) => {
        const routers = filterRouters(constantRouterMap, getters.userInfo.resList);
        const key = filterKey(getters.userInfo.resList);
        commit('setRouters', { routers, key });
        resolve();
      });
    }
}

路由导航守卫实现

router.beforeEach((to, from, next) => {
    //判断白名单路由等等一些其他业务自行处理
    store.dispatch('initUserInfo').then(() => {   //根据token换取用户信息
        store.dispatch('generateRouters').then(() => { // 根据roles权限生成可访问的路由表
          router.addRoutes(store.getters.addRouters); // 动态添加可访问路由表
          next({ ...to, replace: true }); // hack方法 确保addRoutes已完成
        });
      }).catch((err) => {
        store.dispatch('logOut').then(() => {
          Message.error(err || '用户校验失败,请重新登陆');
          next({ path: '/' });
        }, () => {
          Message.error(err || '用户校验失败,请重新登陆');
          next({ path: '/' });
        });
    });
});

最后循环遍历导航菜单的时候根据orderNum字段进行排序。

后续过多的杂乱代码就不一一贴出来了

5.v-permission自定义指令的实现

//permission.js
import store from '@/store';

export default {
  inserted(el, binding) {
    // bind.value 即为 按钮权限的key值
    if (binding.value) {
      /**
       * key不存在该数组中 移除元素
       */
      if (!store.state.user.permissionKey.includes(binding.value)) {
        el.parentNode.removeChild(el);
      }
    } else {
      throw new Error('need permission key! Like v-permission="\'addUser\'"');
    }
  },
};
//index.js
import permission from './permission';

const install = (Vue) => {
  Vue.directive('permission', permission);
};
export default { install };

自定义指令使用的方式

<button v-permission="key">解冻</button>

4.资源管理和接口管理两个菜单的说明

  1. 接口管理:
    把走脚本把接口录入数据库的操作可视化。
  2. 资源管理:
    新建权限资源,并把资源和接口关联的过程。

总结

本人水平有限,搬砖不易!
文采一般,但每一次分享都是认真对待,不足之处请多指教!