路由系统
概述
Naive Admin Max 的路由分为静态路由和动态路由两部分。静态路由在应用初始化时注册,包含登录页、异常页等无需权限的页面;动态路由在用户登录后由后端菜单数据驱动生成。
静态路由
静态路由定义在 src/router/routes/staticRoutes.ts,应用启动时即注册,无需鉴权即可访问。
typescript
// src/router/routes/staticRoutes.ts
export const staticRoutes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录' },
},
{
path: '/403',
name: 'Forbidden',
component: () => import('@/views/error/403.vue'),
meta: { title: '403' },
},
{
path: '/404',
name: 'NotFound',
component: () => import('@/views/error/404.vue'),
meta: { title: '404' },
},
{
path: '/500',
name: 'ServerError',
component: () => import('@/views/error/500.vue'),
meta: { title: '500' },
},
]动态路由
动态路由在用户登录成功后,由 stores/user.ts 根据后端菜单接口返回的数据动态生成并挂载。
工作原理
- 用户登录成功,
userStore调用菜单接口获取菜单树 - 将菜单项转换为 Vue Router 路由记录
- 菜单项的
component字段对应src/views/下的相对路径 - 使用
import.meta.glob实现路由组件懒加载
typescript
// 使用 import.meta.glob 懒加载视图组件
const viewModules = import.meta.glob('@/views/**/*.vue')
// 将后端菜单项转换为路由
function transformMenuToRoutes(menus: MenuItem[]): RouteRecordRaw[] {
return menus.map((menu) => ({
path: menu.path,
name: menu.name,
component: viewModules[`/src/views/${menu.component}.vue`],
meta: {
title: menu.title,
icon: menu.icon,
keepAlive: menu.keepAlive,
actionList: menu.actionList, // 按钮权限码
},
children: menu.children ? transformMenuToRoutes(menu.children) : undefined,
}))
}路由懒加载
所有动态路由组件通过 import.meta.glob 预收集路径,实际加载时使用动态 import(),确保按需加载,减少初始包体积。
路由守卫
路由守卫在 src/router/index.ts 中配置,主要处理以下逻辑:
鉴权拦截
- 未登录用户访问受保护路由 → 重定向至
/login?redirect=当前路径 - 已登录用户访问
/login→ 重定向至首页
动态路由加载
- 首次进入受保护路由时,触发动态路由加载
- 加载完成后调用
router.addRoute()挂载路由
进度条
- 路由切换时显示顶部进度条(基于
nprogress) - 切换完成后自动隐藏
typescript
router.beforeEach(async (to, from, next) => {
NProgress.start()
const token = localStorage.getItem(VITE_TOKEN_KEY)
if (token) {
if (to.path === '/login') {
next({ path: '/' })
} else {
// 首次进入时加载动态路由
if (!userStore.menusLoaded) {
await userStore.fetchUserInfo()
// 动态路由已添加,重新导航
next({ ...to, replace: true })
} else {
next()
}
}
} else {
// 白名单路由直接放行
if (whiteList.includes(to.path)) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
router.afterEach(() => {
NProgress.done()
})注意
动态路由挂载后需要重新导航(next({ ...to, replace: true })),否则首次访问会命中 404。