Vue3-动态路由
outline: deep
Vue3-动态路由
动态菜单路由
这部分我也将其归类为RBAC权限之中的一部分
1、认识动态菜单路由
在做RBAC权限管理系统的时候,都会遇到这么一个需求
每个用户的权限是不一样的,他可以访问的页面,可以操作的菜单,包括可能操作的按钮都是不一样的
这个时候就需要由后端控制用户返回的页面以及菜单按钮权限,前端去实现动态路由,动态渲染侧边菜单栏
我们的后端部分已经写好了,接下来我们前端实现一下
2、功能实现
配置动态路由在获取后端给我们的路由表以后,我们逐个去对比我们本地的文件,返回路由对应的文件
这部分我们主要是对于权限的使用和拦截
👉主文件使用拦截器
🍎主文件之中引入路由权限模块
import '@/utils/authUserPermission' // 导入用户权限配置
🍎先看看我们最简单的路由守卫authUserPermission
接下来我们简单写一个最简单的路由守卫模块,看一下正常我们项目中的路由守卫模块应该如何做,然后再继续进行不断的迭代以及优化。这里就以前置路由守卫和后置路由守卫为主,之前没接触这里的可以看看vue2我们路由守卫的模块
// 授权用户权限
import router from '@/router'
// 引入token
import { getToken, removeToken } from '@/utils/auth'
//定义页面白名单
const whitePageList = ['/register', '/login'];
// 路由守卫
router.beforeEach((to, from, next) => {
console.log('进入路由守卫');
if (getToken()) {
if (to.path === '/login') {
console.log('已登录进入!');
next({ path: '/admin' });
} else {
next()
}
} else if (whitePageList.indexOf(to.path) !== -1) {
console.log('白名单进入!');
next();
} else {
console.log('去登陆');
next('/login');
}
});
router.afterEach(() => {
console.log('路由加载完成!');
});
🍎在路由中间拉取我们用户信息
// 引入用户信息
import useUserStore from '@/store/modules/user'
useUserStore().getInfo().then(() => {
console.log('获取用户信息成功');
}).catch(err => {})
🍎 拉取路由信息
这个时候我们就需要去拉取一下用户的信息,这里我们先完善一下我们路由部分的信息,添加一个静态路由以及一个动态路由部分,这里我先以一个文件为案例
📦router
┣ 📜dynamicRouter.ts
┣ 📜index.ts
┗ 📜staticRouter.ts
接下来我们改写我们的路由模块,在改写之前,确保你已经正确添加了属于自己的以下这些模块
@/layout/index.vue //后台布局主页
//重定向页面
src\views\redirect\index.vue
🍎这部分包含的一些模块
重定向页面src\views\redirect\index.vue
// src\views\redirect\index.vue
<template>
<div></div>
</template>
<script setup>
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const { params, query } = route
const { path } = params
router.replace({ path: '/' + path, query })
</script>
3、完善路由拦截功能
最后完善一下我们的功能,暂时完整如下
import router from '@/router'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import { isHttp } from '@/utils/validate'
import useUserStore from '@/store/modules/user'
import usePermissionStore from '@/store/modules/usePermissionStore'
const isRelogin={ show: true }; //是否需要重新拉取用户信息
NProgress.configure({ showSpinner: false });
const whiteList = ['/login', '/register','/flowerscreen'];
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
if (useUserStore().roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
useUserStore().getInfo().then(() => {
isRelogin.show = false
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
useUserStore().logOut().then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
4、路由权限校验
🍎添加位置
如何进行动态路由菜单的控制呢,也就是根据我们角色的权限然后生成对应的菜单,根据roles权限生成可访问的路由表,这个时候就需要在我们路由进入之前进行我们动态路由的判断,当前用户拉取完user_info信息以后,拿到他授权的菜单信息
也就是在我们路由拦截之中进行判断和操作
router.beforeEach((to, from, next) => {})
userRouteInterception路由权限拦截,拉取完用户信息以后我们就可以访问用户对应的信息,然后在静态路由之中加上动态路由
useUserStore().getInfo().then(() => {
isRelogin.show = false
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
})
🍎添加权限配置仓库
借助于权限配置仓库我们动态获取路由
// 引入权限配置仓库
import usePermissionStore from '@/store/modules/usePermissionStore'
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
src\store\modules\usePermissionStore.ts权限仓库的主要目的就是为了将路由和我们已经有的路由结合,同时将我们的路由和我们本地的文件匹配起来
import auth from '@/store/modules/auth'
import { webSetting } from "@/config/webSetting";
import ParentView from '@/layout/ParentView/index.vue'
import InnerLink from '@/layout/InnerLink/index.vue'
import { defineStore } from 'pinia'; //引入pinia
import { getRouters } from '@/api/common/common' //导入路由接口
// 引入动态静态路由
import router, { constantRoutes, dynamicRoutes } from '@/router'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('./../../views/**/*.vue')
const usePermissionStore = defineStore(
'permission',
{
state: () => ({
routes: [],
addRoutes: [],
defaultRoutes: [],
topbarRouters: [],
sidebarRouters: []
}),
actions: {
setRoutes(routes) {
this.addRoutes = routes
this.routes = constantRoutes.concat(routes);
// console.log('this.routes', this.routes)
},
setDefaultRoutes(routes) {
this.defaultRoutes = constantRoutes.concat(routes)
},
setTopbarRoutes(routes) {
this.topbarRouters = routes
},
setSidebarRouters(routes) {
this.sidebarRouters = routes
},
// roles为当前用户角色
generateRoutes() {
return new Promise(resolve => {
// 向后端请求路由数据
})
}
}
},
)
🍎处理后端接口返回路由
generateRoutes方法我们主要是处理来自后端给我们的数据
getRouters().then(res => {
const sdata = JSON.parse(JSON.stringify(res.data))
const rdata = JSON.parse(JSON.stringify(res.data))
// console.log('sdata', sdata)
// console.log('rdata', rdata)
const defaultData = JSON.parse(JSON.stringify(res.data))
// 处理侧边栏菜单路由
const sidebarRoutes = filterAsyncRouter(sdata)
// console.log('sidebarRoutes', sidebarRoutes)
// 处理实际路由配置(带重写)
const rewriteRoutes = filterAsyncRouter(rdata, false, true)
// console.log('rewriteRoutes', rewriteRoutes)
rewriteRoutes.forEach(item => {
item.component = webSetting.Layout
})
// 处理默认路由
const defaultRoutes = filterAsyncRouter(defaultData)
// console.log('defaultRoutes', defaultRoutes)
// 处理动态路由(根据权限)
const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
// console.log('asyncRoutes', asyncRoutes)
asyncRoutes.forEach(route => { router.addRoute(route) })
this.setRoutes(rewriteRoutes)
this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
this.setDefaultRoutes(sidebarRoutes)
this.setTopbarRoutes(defaultRoutes)
resolve(rewriteRoutes)
})
逐层来看,首先处理我们的侧边栏菜单,处理后端返回的动态路由配置,将其转换为 Vue Router 可用的格式
// filterAsyncRouter
// 处理后端返回的动态路由配置,将其转换为 Vue Router 可用的格式
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
return asyncRouterMap.filter(route => {
if (type && route.children) {
route.children = filterChildren(route.children)
}
if (route.component) {
// Layout ParentView 组件特殊处理
if (route.component === 'Layout') {
route.component = webSetting.Layout
} else if (route.component === 'ParentView') {
route.component = ParentView
} else if (route.component === 'InnerLink') {
route.component = InnerLink
} else {
route.component = loadView(route.component)
}
}
if (route.children != null && route.children && route.children.length) {
route.children = filterAsyncRouter(route.children, route, type)
} else {
delete route['children']
delete route['redirect']
}
return true
})
}
接下来遍历动态路由,查看用户是否确实拥有这个权限
// 动态路由遍历,验证是否具备权限
export function filterDynamicRoutes(routes) {
const res = []
routes.forEach(route => {
if (route.permissions) {
if (auth.hasPermiOr(route.permissions)) {
res.push(route)
}
} else if (route.roles) {
if (auth.hasRoleOr(route.roles)) {
res.push(route)
}
}
})
return res
}
// 验证用户是否含有指定权限,只需包含其中一个
hasPermiOr(permissions) {
return permissions.some(item => {
return authPermission(item)
})
},
然后我们依次添加拼接路由,最后返回就可以了
- 点赞
- 收藏
- 关注作者
评论(0)