vue3 动态路由菜单权限控制
前置:
1.路由数据由后端接口返回
2.通过 pinia、路由导航守卫实现动态路由控制
一.定义常量路由和任意路由
// routes.ts
// 常量路由
const constantRoute = [
{
path: '/',
name: 'Layout',
component: Layout,
redirect: '/dashboard',
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { title: '首页', icon: 'HomeFilled', hidden: false }
}
]
},
{
path: '/login',
name: 'Login',
component: Login,
meta: { title: '登录', hidden: true }
},
{
path: '/404',
name: '404',
component: Error,
meta: { title: '404', hidden: true }
}
]
// 任意路由
const anyRoute = [
{
// 任意路由,匹配不到路由重定向到404
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'Any',
meta: { title: '任意路由', hidden: true }
}
]
export default [...constantRoute, ...anyRoute]
二.获取、存储动态路由
// store/modules/user.ts
import { defineStore } from 'pinia'
import routeList from '@/router/routes'
import { reqAsyncRoutes } from '@/api/user/index'
import router from '@/router/index.ts'
import Layout from '@/layout/index.vue'
import { markRaw } from 'vue'
// const modules = import.meta.glob('@/views/**/index.vue')
export const useUserStore = defineStore('User', {
state: (): UserState => {
return {
menuRoute: routeList,
asyncRoute: []
}
},
actions: {
userLoginOut() {
this.token = ''
// 清除路由信息
this.asyncRoute.forEach((route: any) => {
router.removeRoute(route.name)
})
this.asyncRoute = []
},
async fetchAsyncRoutes() {
let result: any = await reqAsyncRoutes(this.userInfo.roleCode)
if (result.code === 200) {
// markRaw转成非响应式数据,减少vue开销
this.asyncRoute = markRaw(this.filterASyncRoutes(result.data))
this.asyncRoute.forEach(route => {
router.addRoute(route)
})
return 'ok'
} else {
return Promise.reject(new Error(result.msg))
}
},
filterASyncRoutes(routes: any) {
const asyncRoute = routes.map((route: any) => {
if (route.component === 'Layout') {
route.component = Layout
} else {
// 第二次坑,route.component不能直接放里面
const componentPath = route.component
// 第三次坑,@/无法解析,使用/src代替
/*
* 1.@/无法解析,使用/src
* 2.@vite-ignore 注释,vite不能分析导入的路径,实际应用无问题,使用注释解决,消除warning
*/
route.component = () => import(/* @vite-ignore */ `/src/views${componentPath}/index.vue`)
// route.component =modules[`/src/views${route.component}/index.vue`];
}
if (route.children) {
// 递归 ,第一次坑,没返回
route.children = this.filterASyncRoutes(route.children)
}
return route
})
return asyncRoute
}
},
getters: {
// 渲染菜单用
allRoute: state => {
if (state.asyncRoute.length > 0) {
return state.menuRoute.concat(state.asyncRoute)
} else {
return state.menuRoute
}
}
}
})
1.第一次坑是递归调用filterASyncRoutes
的后没有赋值,所以子路由数据没有正确处理
2.第二次坑是 route.component 不能直接放里面,一开始是这样写的
route.component = () => import(`@/views${route.component}/index.vue`)
// 当前赋值看似没有问题,但是到addroute的时候,route.component为() => import(`@/views${route.component}/index.vue`),就会解析成下图的样子了
改:
const componentPath = route.component
route.component = () => import(`@/views${componentPath}/index.vue`)
3.第三次坑是@/
无法解析,使用/src
代替
最终:
const componentPath = route.component
route.component = () => import(/* @vite-ignore */ `/src/views${componentPath}/index.vue`)
4.关于上面两行注释的代码
const modules = import.meta.glob('@/views/**/index.vue')
route.component = modules[`/src/views${route.component}/index.vue`]
// 使用这种方法也可以导入路由组件,感觉和映射的方式差不多
三.路由守卫控制数据获取
// permisstion.ts
router.beforeEach(async (to, from, next) => {
nprogress.start()
if (userStore.token) {
// 登录成功,不允许访问登录页
if (to.path === '/login') {
next({ path: '/' })
} else {
// 异步路由为空,则获取
if (userStore.asyncRoute.length == 0) {
await userStore.fetchAsyncRoutes()
if (to.path == '/404') {
// 跳转到原始路由
next({ path: to.redirectedFrom?.path, replace: true })
} else {
next()
}
// 异步路由添加完成之后再添加404路由和任意路由,否则会直接跳转到404页面
// router.addRoute({
// path: '/404',
// name: '404',
// component: Error,
// meta: { title: '404', hidden: true }
// })
// router.addRoute({
// // 任意路由,匹配不到路由重定向到404
// path: '/:pathMatch(.*)*',
// redirect: '/404',
// name: 'Any',
// meta: { title: '任意路由', hidden: true }
// })
// 路由表里面没有当前访问的路由,则会跳转到404页面
// next({ ...to, replace: true })
// next(to.path)
} else {
next()
}
}
} else {
if (to.path === '/login') {
next()
} else {
next({ path: '/login', query: { redirect: to.path } })
ElMessage.error('请先登录')
}
}
})
关于页面刷新,路由还未存在的处理
我使用的方法是:
1.将 404 和任意路由添加到常量路由里面,任意路由重定向到 404;
2.如果访问的路由在路由表里面没有,则跳转到任意路由,任意路由会重定向到 404;
3.在路由守卫里面判断,如果访问的路由为 404,则跳转到原始路由;
这样处理虽然感觉有点奇怪,但是控制台不会再出现 warning 了
关于上面代码注释的内容
router.addRoute({
path: '/404',
name: '404',
component: Error,
meta: { title: '404', hidden: true }
})
router.addRoute({
// 任意路由,匹配不到路由重定向到404
path: '/:pathMatch(.*)*',
redirect: '/404',
name: 'Any',
meta: { title: '任意路由', hidden: true }
})
next({ ...to, replace: true })
1.不在常量路由里面添加 404 和任意路由,在动态添加路由之后,再添加 404 和任意路由,这样页面不会跳转到 404 页面,但是控制台会报 warning
2.还了解到一种方法,直接在常量路由里面添加任意路由,但是不重定向到 404,在路由导航守卫里面直接next({ ...to, replace: true })
,能实现一样的功能(页面不空白,不 404,控制台不 warning),但是打印出来的要跳转的路由,看着怪怪的
const anyRoute = [
{
path: '/:pathMatch(.*)*',
component: Error,
name: 'Any',
meta: { title: '任意路由', hidden: true }
}
]
本质上我使用的方法和上述方法是一样的,我拿的 to.redirectedFrom 的数据,meta 也是图片中的样子
3.我的代码中next(to.path)
和next({ ...to, replace: true })
好像没有区别
记录:
通过查找资料,发现将路由添加到路由表里面还有很多钟方法
1.前端把路由写好,通过后端传递的角色信息筛选添加到路由表里面
2.前端定义好路由组件的名称,根据后端返回的 component 值,进行映射,添加到路由表里面