后台开发离不开权限,不同的用户登录,根据不同的权限,可以访问不同的管理目录。但在使用 vue-element-template 里内置的权限模块功能后,发现作者提供的权限模块扩展性不是很好,所以这篇文章就是记录我是如何基于作者原有的权限模块进行的一次重构。
为什么要重构
开篇的时候说了,因为扩展性不是很好,那具体是什么扩展性不好呢?我们先来看下作者原有的权限模块实现思路是怎么样的。
首先需要在路由里配置 roles
角色字段,代表该角色可以访问这个路由,可以配置多个角色,例如:
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin', 'editor']
}
}
然后在登录成功时,根据用户角色过滤生成能访问的路由,最后通过 addRoutes
进行动态路由挂载,这部分的实现可参考 permission.js 文件。
这样的实现有个缺点,就是角色无法动态增加,比如要增加一个角色,就需要到路由里将这个角色需要的权限全部添加一遍,删除修改同理,而这部分的操作无法交给用户或者管理员自行配置。
开始重构
依托于这样一个痛点,重构的思路其实就出来了,将路由里配置的角色改成具体的权限即可。
路由配置
我先把权限划分出了四大类:
browse
浏览权限create
新增权限edit
编辑权限delete
删除权限
再结合不同模块,就可以组成这样一个字符串 [moduleName].[authType]
,例如新闻管理模块下的新增权限就是 news.create
,最后将路由文件里 roles
字段替换成对应的模块权限即可,当然现在不能叫 roles
,我将字段名也替换成了 auth
,就像这样:
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
auth: ['module.browse', 'module.create']
}
}
接口配合
接口要如何配合我们修改呢?原先接口只需要返回当前用户的角色即可,现在则需要返回具体的权限列表,例如这样一个数组:
[
'news.browse',
'news.create',
'news.edit',
'category.browse',
'category.create',
'category.edit',
'category.delete',
'log.browse',
...
]
生成路由
这部分就是要修改原有根据用户角色生成能访问的路由的代码,这部分代码逻辑在全局状态 permission 里,可以看到 actions
里有个叫 generateRoutes
的方法,就是用于根据用户角色生成路由并返回的。
这部分改动比较大,具体直接看代码吧:
import { asyncRoutes, constantRoutes } from '@/router'
function hasAuthorization(authorization, route) {
if (route.meta && route.meta.auth) {
return authorization.some(auth => {
return route.meta.auth.some(routeAuth => {
return routeAuth === auth
})
})
} else {
return true
}
}
export function filterAsyncRoutes(routes, authorization) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasAuthorization(authorization, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, authorization)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, authList) {
return new Promise(resolve => {
const accessedRoutes = filterAsyncRoutes(asyncRoutes, authList)
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
当然了,在 permission.js 里调用 generateRoutes
这个方法的时候,入参也要修改掉,需要把用户权限列表传入进来。
这样,最终生成好的路由就是根据用户权限过滤出来的可访问路由了。
按钮级别权限控制
原框架中并没有提供按钮级别的权限控制,但业务中肯定避免不了这样的需求,例如下面这个员工管理的列表页。
这样一个页面,就刚好涵盖了权限四大类型,首先列表页是否能访问,是 browse
权限,页面中“添加新员工”按钮是 create
权限,列表中“编辑”和“删除”按钮分别是 edit
和 delete
权限,但这时候如果用户只有 browse
和 edit
权限的话,那对应没有权限的按钮,就应该在页面上去掉。
其实实现这个很简单,只需要自己写一个鉴权的方法就可以了,我的做法是在全局状态 permission 里增加一个叫 hasAuthorization
的 getters
:
const getters = {
hasAuthorization: state => authorization => {
return state.authorization.some(auth => {
return auth === authorization
})
}
}
然后在需要做权限控制的按钮上就可以调用了。
<el-button v-if="$store.getters['permission/hasAuthorization']('member.create')" type="primary" icon="el-icon-plus" @click="add">添加新员工</el-button>
自定义指令
$store.getters['permission/hasAuthorization']('xxx.yyy')
这样的方式还是过于麻烦了,毕竟要写这么一长端代码,于是我想到了 Vue 的自定义指令。
我把鉴权部分封装成一个方法,同时注册了 v-auth
的全局指令,并且还把鉴权的方法挂载到 Vue 的原型链上,方便功能点的鉴权。
const auth = value => {
let auth
if (typeof value === 'string') {
auth = store.getters['permission/hasAuthorization'](value)
} else {
auth = value.some(item => {
return store.getters['permission/hasAuthorization'](item)
})
}
return auth
}
// 注册 v-auth 指令
Vue.directive('auth', {
inserted: (el, binding) => {
if (!auth(binding.value)) {
el.remove()
}
}
})
// 挂载 this.$auth() 方法
Vue.prototype.$auth = auth
这个 v-auth
指令支持传入数组格式,只要数组其中一项满足则鉴权成功,如果需要数组每一项都满足才算成功,可以自行再注册一个全局指令实现。
完成重构
这样就完成了对权限模块的重构,因为路由直接和权限对接,用户就可以自行去配置角色的权限了,例如这样:
参考
手摸手,带你用vue撸后台 系列二(登录权限篇)
手摸手,带你用vue撸后台 系列五(v4.0新版本)
2019/07/31 更新:
昨天看到了《iView 2019 新品发布会录像》的视频,发现 iView Admin Pro 里提供的鉴权,和我这篇文章里提供的部分鉴权的思路,可以说是如出一辙,可以说是英雄所见略同。
尤其是自定义指令这部分,都采用了v-auth
的指令,也同样提供了单个和多个权限的鉴权,当然也有部分各自的特点,比如我在实际项目中还提供了v-auth-all
指令,iView 则还提供了一个<Auth>
的组件做功能块的鉴权。