Vue 如何进行权限管理(菜单权限/路由权限/按钮权限)

3,599 阅读6分钟

在项目中,尤其是在后台管理系统中, 不同的人员登录,看到的菜单是不一样的, 比如一个公司的后台管理系统,admin 管理员账号登录, 可以看到所有的页面,而普通员工登录则无法查看公司业绩,营收情况等页面; 除了页面的权限, 还会有一些按钮级别的权限控制;比如有些账号只有查看数据的权限, 有些则可以点击新增编辑删除等按钮进行数据操作等等;

权限是指对特定资源的访问许可,所谓的权限控制, 就是针对不同的角色用户显示不同的资源和允许不同的操作, 按照颗粒度来分的话,一般会分为页面级别权限控制, 和按钮级别权限控制

页面级别权限(菜单权限和路由权限)

针对不同的角色人员, 我们可以通过展示不同的菜单,这一块称为菜单权限控制; 但是菜单只是一个连接到其他页面的超链接;不展示这个菜单,只不过是这个超链接隐藏了, 但是用户还是可以手动在地址栏中输入对应页面的 url, 进行页面的访问, 所以我们还需要路由的权限控制, 在用户访问某个没有权限的路由的时候, 跳转到404页面;所以一般处理页面级别权限的时候, 需要同步考虑菜单权限和路由权限;

方案一:

菜单与路由分离, 前端定义好所有的路由信息, 菜单则由后端返回

{
    name: 'login',
    path: '/login',
    component: () => import('/@/pages/Login.vue')
}

由于菜单是后台根据权限过滤的结果, 所以菜单直接渲染即可,我们需要做的是路由的权限控制;

在全局路由守卫中做判断,通过定义的路由的 name 字段和后端返回的菜单做匹配, 如果匹配成功,则表明该路由是有权限的,进行放行即可; 如果匹配不成功,则表明该路由没有权限, 则进行拦截

如果路由有很多,可以在应用初始化的时候, 只挂载不需要权限控制的路由,比如login, 404等, 当后端返回菜单后, 在进行匹配,筛选出可以访问的路由, 通过 addRoutes 动态挂载

方案二:

菜单和路由统一由后端返回 前端只需要定义对应的路由组件引用

const Home = () => import('/@/views/Home.vue')
const UserInfo = () => import('/@/views/UserInfo.vue')

export default {
  home: Home,
  userInfo: UserInfo
}

后端路由返回一下格式

[
    {
        name: 'home',
        path: '/',
        component: 'home'
    },
    {
        name: 'userInfo',
        path: '/usreInfo',
        component: 'userInfo'
    }
]

再将后端返回的路由通过 addRoutes 动态挂载,

方案三:

前端定义路由的时候, 在路由的 meta 指定该路由对应的角色权限,在渲染菜单的时候, 对每个菜单进行权限判断, 只有该角色下的用户, 才会显示对应的菜单; 在全局路由守卫里面, 也一样进行筛选有权限访问的路由, 调用 addRoutes 动态添加路由

const routerMap = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/index',
    alwaysShow: true, // will always show the root menu
    meta: {
      title: 'permission',
      icon: 'lock',
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    children: [{
      path: 'page',
      component: () => import('@/views/permission/page'),
      name: 'pagePermission',
      meta: {
        title: 'pagePermission',
        roles: ['admin'] // or you can only set roles in sub nav
      }
    }, {
      path: 'directive',
      component: () => import('@/views/permission/directive'),
      name: 'directivePermission',
      meta: {
        title: 'directivePermission'
        // if do not set roles, means: this page does not require permission
      }
    }]
  }]


// 全局路由守卫
if (getToken()) { // determine if there has token
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done() // if current page is dashboard will not trigger	afterEach hook, so manually handle it
    } else {
      if (store.getters.roles.length === 0) { // 判断当前用户是否已拉取完user_info信息
        store.dispatch('GetUserInfo').then(res => { // 拉取user_info
          const roles = res.data.roles // note: roles must be a array! such as: ['editor','develop']
          store.dispatch('GenerateRoutes', { roles }).then(() => { // 根据roles权限生成可访问的路由表
            router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
        if (hasPermission(store.getters.roles, to.meta.roles)) {
          next()//
        } else {
          next({ path: '/401', replace: true, query: { noGoBack: true }})
        }
        // 可删 ↑
      }
    }
  } else {
    /* has no token*/
    if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
      next()
    } else {
      next('/login') // 否则全部重定向到登录页
      NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
    }
  }

按钮权限

方案一:

按钮权限也可以用v-if判断

但是如果页面过多,每个页面页面都要获取用户权限role和路由表里的meta.btnPermissions,然后再做判断

方案二:

通过自定义指令,进行权限按钮的判断,传递给按钮一组可操作的角色数组

<el-button @click='editClick' type="primary" v-permissions=['admin', 'supper']>编辑</el-button>

指令会传递一个可以访问的角色列表, 在自定义指令逻辑中,拿到当前用户的角色进行匹配, 如果角色匹配的上, 表示当前角色有权访问这个按钮, 则正常展示, 没有匹配上, 则不显示或者显示为禁用状态;

方案二一般用于按钮的权限和角色是固定关系的, 即某个按钮就是固定的某些角色能够访问,如果角色不固定,并且可以动态新增角色,同时对角色进行权限的动态编辑, 则方案二不太使用; 一般会使用方案三或方案四;

方案三:

通过自定义指令,传递一个值给对应按钮,不过这个不再是角色,而是一个可以代表当前按钮的唯一值

<el-button @clicl='editClick' type="primary" v-permissions="userInfo.add">新增</el-button>

用户登录后, 获取到登录用户对应的角色id, 通过角色id 获取角色权限信息,角色权限信息应该包含所有可操作的页面以及按钮 在自定义指令的逻辑中, 拿到传递过来的唯一值, 进行和缓存中的角色权限信息进行匹配, 如果有对应按钮的权限, 则正常显示, 没有的话, 则隐藏或者禁用该按钮

方案四:

方案四思路跟方案三一样,不过也可以通过自定义按钮而非自定义指令的方式进行实现, 也是传递一个可以代表当前按钮的唯一值, 处理逻辑也一样, 拿到传递过来的值进行和缓存中的角色权限信息进行匹配, 匹配成功正常显示, 不成功则隐藏或者禁用; 不过这种方法, 那需要进行按钮权限控制的地方,都需要使用这个定义的组件,而非框架的按钮组件了

    <Auth-button code='userInfo.edit'>编辑</Auth-button>