在前端项目中只配置基础的静态路由,比如登录页、首页、404页面,登录时根据用户权限获取菜单,通过菜单转换为动态路由。
前置条件
- 根路由( / )代表登录后要访问的首页
- /login 登录页面
- 动态路由全部添加在根路由下
- 登录后将根路由重定向到动态路由中的第一个路由
路由关系图
实现流程
登录 -> 获取菜单配置 -> 将菜单配置转换为 vue-router 需要的路由配置 -> 添加路由 -> 将根路由重定向到动态路由中的第一个路由
基础静态路由配置
后续的动态路由都将添加到根路由下。
const routeItems = [
{
name: 'home',
path: '/',
component: () => import('@renderer/views/home/main.vue'),
meta: {},
children: []
},
{
name: 'login',
path: '/login',
component: () => import('@renderer/views/login/main.vue'),
meta: {}
},
{
name: 'notFound',
path: '/404',
component: () => import('@renderer/views/notFound/main.vue'),
meta: {}
}
]
登录后获取菜单
登录后调用接口获取对应权限下的菜单配置。
[
{
id: 3001,
parentId: 3000,
name: '用户管理',
path: 'management/userMgmt',
component: null,
componentName: null,
icon: null,
visible: true,
keepAlive: true,
alwaysShow: true,
children: []
}
]
将获取到的菜单转换为路由配置
将获取到的菜单通过递归的形式转换为路由配置。
export function mapServerRoutesToVueRoutes(serverRoutes) {
return serverRoutes.map((item) => {
const route = {
path: item.path.startsWith('/') ? item.path : `/${item.path}`,
name: item.name,
component: getViewComponent(item),
meta: {
title: item.name,
icon: item.icon,
keepAlive: item.keepAlive,
visible: item.visible
},
children: []
}
if (item.children && item.children.length > 0) {
route.children = mapServerRoutesToVueRoutes(item.children)
}
return route
})
}
最终生成的路由配置文件中的 component 属性要映射为真正的路由组件
function getViewComponent(item) {
try {
// 将 / 开头的组件路径转换成相对路径
const _path = item.path.startsWith('/') ? item.path.substring(1) : item.path
return () => import(`@renderer/views/${_path}/main.vue`)
} catch (e) {
// 若组件文件不存在,加载一个通用的占位页
return () => import('@renderer/views/notFound/main.vue')
}
}
将动态路由添加到路由下
// dynamicRoutes 就是根据菜单生成的动态路由
dynamicRoutes.forEach((route) => {
// 将动态路由添加到 home 下
router.addRoute('home', route)
})
跳转到根路由
await router.replace({
path: '/'
})
路由守卫
let isRegisterDynamicRouter = false
// 伪代码
router.beforeEach(async (to, from, next) => {
console.log(`to: ${to.path}, from: ${from.path}`)
const userStore = useUserStore()
const accessToken = userStore.accessToken
// 未登录时只能访问登录页
if (!accessToken) {
if (to.name === 'login') {
return next()
}
// 访问 / 不添加 redirect
if (to.name === 'home') {
return next({
name: 'login'
})
}
return next({
name: 'login',
query: {
redirect: to.fullPath
}
})
}
// 已登录时访问登录页,跳转到首页
if (to.name === 'login') {
return next({ path: '/' })
}
// 已登录,访问非登录页面
if (!isRegisterDynamicRouter) {
try {
if (!userStore.roleMenu) {
userStore.logout()
return next({ name: 'login' })
}
// 获取当前选中的角色,获取动态路由
const routes = userStore.roleMenu[userStore.currentRoleCode]
await registerDynamicRoutes(routes)
isRegisterDynamicRouter = true
return next({ ...to, replace: true })
} catch (error) {
console.error('动态路由注册失败:', error)
// 注册失败,跳回登录
userStore.logout()
return next({ name: 'login' })
}
}
// 未匹配到任何路由 或 访问的是 /,跳转到第一个动态路由
if (!to.matched.length || to.path === '/') {
if (!userStore.roleMenu) {
userStore.logout()
return next({ name: 'login' })
}
const routes = userStore.roleMenu[userStore.currentRoleCode]
const dynamicRoutes = mapServerRoutesToVueRoutes(routes)
if (dynamicRoutes.length > 0) {
const firstPath = dynamicRoutes[0].path
console.warn(`访问不存在的路由,重定向到第一个动态路由: ${firstPath}`)
return next({ path: firstPath })
}
}
// 动态路由已注册,直接放行
next()
})