前言
在开发后台管理系统的时候,难免会遇到不同角色不同权限的情况,近期在工作中就遇到了这个情况,也踩了比较多的坑,这里做一下总结。
路由权限
用户在登录的时候,服务端会返回给我们角色的信息,其中就包括了权限信息,如果该用户有对应的权限,我们在进入页面的时候,菜单也只显示这些权限对应的路由。
首先我们要配置一些通用的路由,比如404路由,首页的路由等。
export const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'layout',
component: Welcome,
children: [],
},
{
path: '/login',
name: 'login',
component: LoginPage
},
{
path: '/:pathMatch(.*)*',
name: '404',
component: NotFoundPage
}
]
然后我们需要配置需要动态加载的路由
export const asyncRoutes: RouteRecordRaw[] = [
{
path:'/commodity',
name: 'commodity',
component: Commodity,
children: [
{
path: '/commodity/rawMaterial',
name: 'rawMaterial',
component: RawMaterial,
},
{
path: 'commodity/parts',
name: 'parts',
component: parsts,
}
],
},
...
]
当我们的路由都配置完成后,通过和后端返回的数据进行匹配,最后得出我们需要为该用户添加的路由,我们可以把路由信息存放在pinia中,通过vue-router提供的router.addRoute来实现路由的动态添加。
// login.ts
store.setPermissionRoutes(res.data) // 服务端获取的数据
// mani.ts
setPermissionRoutes = (menu) => {
const routeList = defaultRoutes[0].children
const routeArr: any[] = []
routeList?.forEach((row) => {
const routeInfo: any = {
menu_name: '',
path: '',
children: [],
buttonPermissions: {}
}
const { path, children } = row
const info = menu.find((item) => item.path === path)
const array = children?.filter((route) => menu.find((item) => item.path === route.path))
if (info) {
routeInfo.menu_name = info.menu_name
routeInfo.path = info.path
routeInfo.buttonPermissions = { ...info.permissions }
}
if (array && array.length) {
routeInfo.children = [...array]
}
routeArr.push(routeInfo)
})
const routeData = routeArr.filter((row: any) => row.path !== '') as any
routeData.map((route) => {
// 将处理好的路由使用addRoute添加,我这里对应的是放在layout下
router.addRoute('layout', route)
})
}
这样我们在用户登录后就可以实现根据路由权限来动态添加路由,但是当我们刷新页面的时候会出现页面空白的情况,这是因为刷新的时候pinia中的数据是无法保存下来的,所以我们应该在路由守卫里面也加上动态添加路由的操作。
let hasInitAuth = false
router.beforeEach(async (to, from, next) => {
const token = localStorage.getItem('token')
if (to.path === '/login') {
next()
} else {
if (!token && to.path !== '/login') {
router.push({ path: '/login' })
} else {
if (!hasInitAuth) {
const routes = JSON.parse(localStorage.getItem('menu') as any)
generateRoutes(routes)
// 确保路由已经添加出来再进行跳转,需要添加replace:true
next({ ...to, replace: true })
hasInitAuth = true
} else {
next()
}
}
}
})
这样我们就基本实现了路由权限
按钮权限
实现了路由权限后,还需要将权限控制在按钮级别,按钮级别的权限的话,可以采用自定义指令的方法去实现。
export const buttonDirective = {
mounted(el, binding, vNode) {
if (!binding.value) return
let { typeName, path } = binding.value
const menu = JSON.parse(localStorage.getItem('menu'))
const childrenRoutes = menu.filter((item) => item.rank === 2)
const buttonRouteMessage = childrenRoutes.find((route) => route.path === path)
if (!buttonRouteMessage?.list) {
// 不显示按钮
el.parentNode && el.parentNode.removeChild(el)
} else {
const currentButton = buttonRouteMessage.list.find((item) => item.menu_name === typeName)
if (!currentButton) {
// 不显示按钮
el.parentNode && el.parentNode.removeChild(el)
}
}
}
}
接下来我们只需要在对应的button使用就可以了
<div v-has={directiveParams}>新增</div>
// 也可以将相应逻辑封装在函数中,通过v-if来实现
export const setIsShowOption = (name, path) => {
const menu = JSON.parse(localStorage.getItem('menu') as any)
const childrenRoutes = menu.filter((item) => item.rank === 2)
const buttonRouteMessage = childrenRoutes.find((route) => route.path === path)
if (!buttonRouteMessage.list) {
return false
} else {
return buttonRouteMessage.list.find((item) => item.menu_name === name)
}
}
<button v-if ="setIsShowOption('删除', $route.path)">删除</button>
这样基本就可以实现相关的需求了,因为是刚接触使用vue3,之前没有了解过自定义指令,这里也做一下简单的使用总结。
自定义指令
首先要在main.ts中注册指令,和组件一样,自定义指令在模板中使用前必须先注册
指令钩子
// main.ts
app.directive('button', {
// 在绑定元素的attribute前,或事件监听器应用前调用
created(el, binding,vNode) {
},
// 在元素被插入到dom前调用
beforeMount() {
},
// 在绑定元素的父组件以及他的所有子节点挂载完成后调用
mounted() {
},
// 绑定元素的父组件更新前调用
beforeUpdate() {
},
// 在绑定元素的父组件以及他的所有子节点都更新后调用
updated() {
},
// 绑定元素的父组件卸载前调用
beforeUnmount() {
},
// 绑定元素的父组件卸载后调用
unmounted() {
}
})
钩子参数
-
el:指令绑定到的元素,这可以用来直接操作dom。
-
binding:一个对象
-
value:传递给指令的值
-
oldvalue:之前的值,仅在beforeUpdate和update中可用
-
arg:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo中,参数是"foo"
-
instace:使用该指令的组件实例
-
dir:指令的定义对象
-
vnode:代表绑定元素的底层 VNode
-
prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在beforeUpdate和updated钩子中可用
自定义指令的介绍在vue官方文档中介绍的已经很详细了。