在项目中,尤其是在后台管理系统中, 不同的人员登录,看到的菜单是不一样的, 比如一个公司的后台管理系统,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>