前言
在前端项目开发中,大多数的中后台项目都需要做权限控制。权限一般分为页面权限、操作权限、数据权限,这些权限再和角色账号绑定,组合成复杂的权限模型。本文将介绍在Vue中页面权限和操作权限的实现方法,数据权限大多都是后端控制所以滤过。
RBAC权限模型
RBAC(Role Based Access Control,基于角色的访问控制),用户通过角色与权限进行关联。一个账号关联多个角色,一个角色可以关联多个权限,进而构成用户-角色-权限模型。
权限初始化
在用户登录后,我们首先需要获取用户配置信息,如角色、token等,在根据角色获取权限信息,如:菜单、访问页面权限、按钮操作权限。获取这个信息后将它存入Vuex中,以便后续读取。
页面权限控制
页面权限控制某一角色能否访问前端哪些页面,对应Vue工程就是前端路由。当角色访问不在权限范围内的页面时,前端页面应该重定向到一个403页面。在Vue中我用到的页面权限控制有两种方式,都是Vue-Router提供的方法:路由的全局钩子和动态添加路由
-
路由的全局钩子
Vue-Router为我们提供了很多路由导航的钩子beforeEach(全局前置守卫)、beforeResolve(全局解析守卫)、afterEach(全局后置钩子)等等。
beforeEach:当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。通过这个钩子是我们可以实现页面的权限控制。首先我们需要注册一个前置守卫,其中回调函数的三个参数详情查看全局前置守卫官方文档。
import store from '@/store';
router.beforeEach((to, from, next) => {
const { token, whiteList } = store.state; // 获取登录信息和页面访问白名单
const { path } = to;
const isToLoginPage = path === '/login';
if (!!token) {
if (isToLoginPage) {
//已登录,重定向到首页
next({ path: '/' });
} else {
const accessible = whiteList.includes(path); //是否能访问
const dest = accessible ? undefined : { path: '/403' };
next(dest);
}
} else { // 未登
const dest = isToLoginPage ? undefined : { path: '/login' };
next(dest);
}
});
-
动态添加路由
Vue-router除了使用钩子函数实现权限控制外,我们还能使用addRoute达到同样的效果。
addRoute:添加一条新路由规则。如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。 在用户未登录时,我们首先初始一些公共的路由,如:登录、404、403等页面路由
import VueRouter from 'vue-router'
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{ path: '/login', name: 'Login', component: 'XXX' },
{ path: '/403', name: '403',component: 'XXX' },
{ path: '*',name: 'NotFound', component: 'XXX' }
]
});
export default router;
初始完成公共路由后,当用户登录后,再获取账号信息:账号绑定的角色以及对应的页面权限
操作权限控制
操作权限暂时我所接触到的大多都是和按钮进行绑定的,指某一角色对某一按钮是否可见或者禁用。这里也介绍两种实现方法。首先我们需要获取到角色对应的操作权限
-
对按钮组件进行重写
这种方法首先你的按钮都是用的某一组件库的按钮组件,比如我用的是element-ui,在启动vue之前,获取按钮组件的props和render方法,然后用对props和render方法改写。
import store from '@/store';
import { Button } from 'element-ui';
const { props, render } = Button;
Button.props = {
...props,
// 按钮操作类型
actionType: {
type: String,
default: ''
}
};
Button.render = function () {
// 获取角色可操作权限列表
const { operationTable } = store.state;
const { actionType } = this;
// 是否具有操作权限
if (operationTable.includes(actionType) || !actionType) {
return render.call(this, ...arguments);
}else{
return null;
}
};
在模板中使用
<!--当按钮具有编辑权限时才会显示-->
<el-button actionType='eidt' type='primary'>编辑</el-button>
- 使用自定义指令
import store from '@/store';
Vue.directive('actionType', {
inserted(el, { value }) {
const { operationTable } = store.state;
// 是否具有操作权限
if (operationTable.includes(value) && !value) {
el.parentNode.removeChild(el); //移除当前按钮
}
}
});
在模板中使用
<!--当按钮具有编辑权限时才会显示-->
<el-button v-actionType='eidt' type='primary'>编辑</el-button>