基本概念
RBAC(Role-Based Access Control)是指 基于角色控制用户访问。通过给用户分配不同的角色,不同的角色拥有不同的权限,用户根据权限来查看不同的页面或者功能按钮。
整个权限系统分成了两部分:
- 页面权限:当前用户可以访问的页面
- 功能权限:当前用户可以使用的功能按钮
实现逻辑:
- 页面权限:根据不同权限数据,利用 addRoute API 动态配置路由对象。
- 功能权限:根据不同权限数据,利用 自定义指令 实现隐藏功能按钮。
具体实现:
页面权限代码实现:
- 定义专门处理权限路由的模块
import { publicRoutes, privateRoutes } from '@/router'
export default {
namespaced: true,
state: {
// 路由表:初始拥有静态路由权限
routes: publicRoutes
},
mutations: {
// 增加路由
setRoutes(state, newRoutes) {
// 永远在静态路由的基础上增加新路由
state.routes = [...publicRoutes, ...newRoutes]
}
},
actions: {
// 根据权限筛选路由
filterRoutes(context, menus) {
const routes = []
// 路由权限匹配
menus.forEach((key) => {
// 为每个私有路由指定一个 name , 每个 name 对应一个页面权限
// 通过 name 与 当前用户返回的 页面权限 数据进行匹配筛选
routes.push(...privateRoutes.filter((item) => item.name === key))
})
context.commit('setRoutes', routes)
return routes
}
}
}
- 在全局路由前置守卫中,获取用户当前页面权限数据后,筛选出用户需要添加的页面权限 。利用 addRoute() 循环添加。
import router from './router'
import store from './store'
// 白名单
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
if (store.getters.token) {
if (to.path === '/login') {
next('/')
} else {
// 判断用户数据是否已获取
// 若不存在用户信息,则需要获取用户信息
if (!store.getters.hasUserInfo) {
// 触发获取用户信息的 action,并获取用户当前权限
const { permission } = await store.dispatch('user/getUserInfo')
// 处理用户权限,筛选出需要添加的权限
const filterRoutes = await store.dispatch(
'permission/filterRoutes',
permission.menus
)
// 利用 addRoute 循环添加
filterRoutes.forEach((item) => {
router.addRoute(item)
})
// 添加完动态路由之后,需要在进行一次主动跳转
return next(to.path)
}
next()
}
} else {
if (whiteList.indexOf(to.path) > -1) {
next()
} else {
next('/login')
}
}
})
-
退出登录时,重置路由表数据,防止下次登录后,权限数据未刷新(需要强制刷新)。
利用 removeRoute()按名称删除路由
// 在路由表中定义 初始化路由表
export function resetRouter() {
if (
store.getters.userInfo && // 用户数据
store.getters.userInfo.permission && // 用户权限数据
store.getters.userInfo.permission.menus // 用户权限中 页面权限
) {
const menus = store.getters.userInfo.permission.menus
menus.forEach((menu) => {
router.removeRoute(menu)
})
}
}
// 退出登录时,触发该方法
功能权限代码实现:
- 通过 自定义指令 实现,在需要权限的标签上,通过指令传递权限的 key。 eg:v-permission="['importUser']"
- 然后在自定义指令中,根据按钮权限数据进行匹配。不匹配就移除 DOM。(parentNode.removeChild(domElement))
import store from '@/store'
function checkPermission(el, binding) {
// 获取绑定的值
const { value } = binding
// 获取用户数据中的功能权限数据
const points = store.getters.userInfo.permission.points
if (value && value instanceof Array) {
// 匹配对应的指令
const hasPermission = points.some((point) => {
return value.includes(point)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error('v-permission value is ["admin","editor"]')
}
}
export default {
// 在绑定元素的组件被挂载时调用
mounted(el, binding) {
checkPermission(el, binding)
},
// 在包含组件的 vNode 及其 子组件的 vNode 更新后调用
update(el, binding) {
checkPermission(el, binding)
}
}
- 在
directives/index
中绑定该指令
...
import permission from './permission'
export default (app) => {
...
app.directive('permission', permission)
}