- 添加公共静态资源(public/) - 添加项目文档(doc/) - 添加项目说明文档(README.md, README.zh-CN.md) - 添加Claude开发规范文档(CLAUDE.md) - 添加开源许可证(LICENSE) - 添加构建脚本(build/) - 添加国际化资源(locales/) - 添加编辑器配置(.vscode/) - 添加GitHub工作流配置(.github/)
12 KiB
12 KiB
Nova Admin 路由系统文档
概述
Nova Admin 基于 Vue Router 4 构建了一套完整的路由管理系统,支持静态路由和动态路由,具备完善的路由守卫、权限控制、菜单生成等功能。
🏗️ 路由架构
核心组件
- 路由配置 (
src/router/) - 路由守卫 (
src/router/guard.ts) - 路由状态管理 (
src/store/router/) - 菜单生成 (
src/store/router/helper.ts) - 标签页管理 (
src/store/tab.ts)
文件结构
src/router/
├── index.ts # 路由主配置文件
├── guard.ts # 路由守卫
├── routes.inner.ts # 内置路由(登录、错误页等)
└── routes.static.ts # 静态路由配置
src/store/router/
├── index.ts # 路由状态管理
└── helper.ts # 路由处理辅助函数
🛣️ 路由类型
1. 内置路由 (Inner Routes)
位置:src/router/routes.inner.ts
包含系统基础路由:
- 根路由 (
/) - 登录页 (
/login) - 错误页 (
/403,/404,/500) - 通配符路由 (404 处理)
export const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'root',
redirect: '/appRoot',
},
{
path: '/login',
name: 'login',
component: () => import('@/views/login/index.vue'),
meta: {
title: '登录',
withoutTab: true,
},
},
// ... 错误页路由
]
2. 静态路由 (Static Routes)
位置:src/router/routes.static.ts
业务功能路由配置:
export const staticRoutes: AppRoute.RowRoute[] = [
{
name: 'monitor',
path: '/dashboard/monitor',
title: '仪表盘',
requiresAuth: true,
icon: 'icon-park-outline:anchor',
menuType: 'page',
componentPath: '/dashboard/monitor/index.vue',
id: 3,
pid: null,
},
// ... 其他业务路由
]
3. 动态路由 (Dynamic Routes)
通过 API 从后端获取的路由配置,结构与静态路由相同。
📋 路由数据结构
AppRoute.RowRoute 接口
interface AppRoute.RowRoute {
/** 路由名称 */
name: string
/** 路由路径 */
path: string
/** 页面标题 */
title: string
/** 是否需要认证 */
requiresAuth?: boolean
/** 访问角色 */
roles?: Entity.RoleType[]
/** 图标 */
icon?: string
/** 菜单类型 */
menuType?: 'dir' | 'page'
/** 组件路径 */
componentPath?: string | null
/** 路由ID */
id: number
/** 父路由ID */
pid: number | null
/** 是否隐藏 */
hide?: boolean
/** 排序权重 */
order?: number
/** 外链地址 */
href?: string
/** 激活菜单 */
activeMenu?: string
/** 不显示标签页 */
withoutTab?: boolean
/** 固定标签页 */
pinTab?: boolean
/** 是否缓存 */
keepAlive?: boolean
}
路由元信息 (Meta)
interface RouteMeta {
/** 页面标题 */
title: string
/** 图标 */
icon?: string
/** 是否需要认证 */
requiresAuth?: boolean
/** 访问角色 */
roles?: Entity.RoleType[]
/** 是否缓存 */
keepAlive?: boolean
/** 是否隐藏 */
hide?: boolean
/** 排序权重 */
order?: number
/** 外链地址 */
href?: string
/** 激活菜单 */
activeMenu?: string
/** 不显示标签页 */
withoutTab?: boolean
/** 固定标签页 */
pinTab?: boolean
/** 菜单类型 */
menuType?: 'dir' | 'page'
}
🔄 路由处理流程
1. 路由初始化流程
sequenceDiagram
participant App as 应用启动
participant Router as 路由系统
participant Guard as 路由守卫
participant Store as RouteStore
participant API as 后端API
App->>Router: 创建路由实例
Router->>Guard: 安装路由守卫
App->>Store: 初始化路由状态
Note over Router,Store: 用户访问路由时
Router->>Guard: beforeEach 触发
Guard->>Store: 检查路由是否初始化
Store->>Store: initAuthRoute()
alt 动态路由模式
Store->>API: fetchUserRoutes()
API-->>Store: 返回用户路由数据
else 静态路由模式
Store->>Store: 使用 staticRoutes
end
Store->>Store: createRoutes() 生成路由
Store->>Router: addRoute() 注册路由
Store->>Store: createMenus() 生成菜单
Guard->>Router: next() 继续导航
2. 路由守卫执行流程
flowchart TD
A[路由跳转] --> B{外链路由?}
B -->|是| C[打开新窗口]
B -->|否| D[开始 LoadingBar]
D --> E{登录页?}
E -->|是| F[直接放行]
E -->|否| G{requiresAuth=false?}
G -->|是| F
G -->|否| H{需要认证且未登录?}
H -->|是| I[重定向到登录页]
H -->|否| J{路由已初始化?}
J -->|否| K[初始化路由]
K --> L{是404页面?}
L -->|是| M[重新导航到正确路径]
L -->|否| N[继续导航]
J -->|是| O{已登录访问登录页?}
O -->|是| P[重定向到首页]
O -->|否| N
F --> N
N --> Q[设置菜单高亮]
Q --> R[添加标签页]
R --> S[设置页面标题]
S --> T[结束 LoadingBar]
🎯 路由守卫详解
beforeEach 守卫
位置:src/router/guard.ts
主要功能
- 外链处理: 检测并打开外部链接
- 进度条控制: 开始 LoadingBar
- 登录验证: 检查用户认证状态
- 路由初始化: 动态加载和注册路由
- 重定向处理: 处理各种重定向场景
核心逻辑
router.beforeEach(async (to, from, next) => {
// 1. 外链处理
if (to.meta.href) {
window.open(to.meta.href)
next(false)
return
}
// 2. 开始进度条
appStore.showProgress && window.$loadingBar?.start()
// 3. 登录验证
const isLogin = Boolean(local.get('accessToken'))
if (to.meta.requiresAuth === true && !isLogin) {
const redirect = to.name === '404' ? undefined : to.fullPath
next({ path: '/login', query: { redirect } })
return
}
// 4. 路由初始化
if (!routeStore.isInitAuthRoute) {
await routeStore.initAuthRoute()
if (to.name === '404') {
next({ path: to.fullPath, replace: true })
return
}
}
// 5. 登录页重定向
if (to.name === 'login' && isLogin) {
next({ path: '/' })
return
}
next()
})
beforeResolve 守卫
router.beforeResolve((to) => {
// 设置菜单高亮
routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath)
// 添加标签页
tabStore.addTab(to)
// 设置当前标签页
tabStore.setCurrentTab(to.fullPath)
})
afterEach 守卫
router.afterEach((to) => {
// 设置页面标题
document.title = `${to.meta.title} - ${title}`
// 结束进度条
appStore.showProgress && window.$loadingBar?.finish()
})
🏪 路由状态管理
RouteStore 状态
interface RoutesStatus {
/** 路由是否已初始化 */
isInitAuthRoute: boolean
/** 菜单数据 */
menus: MenuOption[]
/** 原始路由数据 */
rowRoutes: AppRoute.RowRoute[]
/** 当前激活菜单 */
activeMenu: string | null
/** 缓存路由 */
cacheRoutes: string[]
}
核心方法
initAuthRoute()
初始化认证路由的完整流程:
async initAuthRoute() {
this.isInitAuthRoute = false
// 1. 获取路由数据
const rowRoutes = await this.initRouteInfo()
if (!rowRoutes) return
this.rowRoutes = rowRoutes
// 2. 生成实际路由并注册
const routes = createRoutes(rowRoutes)
router.addRoute(routes)
// 3. 生成侧边菜单
this.menus = createMenus(rowRoutes)
// 4. 生成路由缓存
this.cacheRoutes = generateCacheRoutes(rowRoutes)
this.isInitAuthRoute = true
}
initRouteInfo()
根据配置模式获取路由信息:
async initRouteInfo() {
if (import.meta.env.VITE_ROUTE_LOAD_MODE === 'dynamic') {
// 动态路由:从 API 获取
const userInfo = local.get('userInfo')
const { data } = await fetchUserRoutes({ id: userInfo.id })
return data
} else {
// 静态路由:使用本地配置
this.rowRoutes = staticRoutes
return staticRoutes
}
}
🎨 菜单生成
菜单生成流程
export function createMenus(routes: AppRoute.RowRoute[]) {
const { hasPermission } = usePermission()
// 1. 权限过滤
let resultMenus = routes.filter(i => hasPermission(i.roles))
// 2. 隐藏项过滤
resultMenus = resultMenus.filter(i => !i.hide)
// 3. 转换菜单格式
const menus = resultMenus.map(transformRouteToMenu)
// 4. 生成菜单树
return arrayToTree(menus)
}
菜单数据结构
interface MenuOption {
label: string // 菜单标题
key: string // 菜单键值
icon?: () => VNode // 菜单图标
children?: MenuOption[] // 子菜单
disabled?: boolean // 是否禁用
}
🏷️ 标签页系统
TabStore 管理
标签页系统与路由系统紧密集成:
// 添加标签页
tabStore.addTab(route)
// 设置当前标签页
tabStore.setCurrentTab(route.fullPath)
// 关闭标签页
tabStore.closeTab(fullPath)
特殊标签页类型
- 固定标签页 (
pinTab: true): 不可关闭的标签页 - 无标签页 (
withoutTab: true): 不显示在标签栏的页面
⚙️ 配置选项
环境变量
# 路由加载模式
VITE_ROUTE_LOAD_MODE=static|dynamic
# 默认首页路径
VITE_HOME_PATH=/dashboard/monitor
# 应用名称
VITE_APP_NAME=Nova Admin
路由模式配置
// vite.config.ts 或 .env 文件
const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env
export const router = createRouter({
history: VITE_ROUTE_MODE === 'hash'
? createWebHashHistory(VITE_BASE_URL)
: createWebHistory(VITE_BASE_URL),
routes,
})
🛠️ 开发指南
1. 添加新路由
静态路由方式
在 src/router/routes.static.ts 中添加:
{
name: 'newPage',
path: '/new-page',
title: '新页面',
requiresAuth: true,
icon: 'icon-park-outline:new',
menuType: 'page',
componentPath: '/new/page/index.vue',
id: 100,
pid: null,
}
动态路由方式
通过后端 API 返回路由配置,格式与静态路由相同。
2. 路由权限配置
{
name: 'adminPage',
path: '/admin',
title: '管理页面',
requiresAuth: true,
roles: ['admin', 'super'], // 权限控制
// ...
}
3. 嵌套路由
// 父路由
{
name: 'parent',
path: '/parent',
title: '父级菜单',
menuType: 'dir',
componentPath: null,
id: 1,
pid: null,
}
// 子路由
{
name: 'child',
path: '/parent/child',
title: '子级菜单',
menuType: 'page',
componentPath: '/parent/child/index.vue',
id: 2,
pid: 1, // 指向父路由ID
}
4. 路由缓存
{
name: 'cachedPage',
path: '/cached',
title: '缓存页面',
keepAlive: true, // 启用路由缓存
// ...
}
🐛 常见问题
1. 动态路由不生效
检查:
- 路由数据格式是否正确
- 组件路径是否存在
- 权限配置是否正确
2. 菜单不显示
检查:
hide属性是否为true- 权限验证是否通过
menuType是否正确设置
3. 404 页面循环
确保:
- 静态路由已正确配置
- 路由初始化完成
- 通配符路由放在最后
4. 标签页问题
检查:
withoutTab设置- 路由路径是否正确
- TabStore 状态是否正常
🎯 最佳实践
1. 路由命名规范
// 推荐命名方式
name: 'userManagement' // 驼峰命名
name: 'user-management' // 中划线命名
name: 'user_management' // 下划线命名
2. 路径规范
// 路径应该语义化
path: '/user/management' // ✅ 好的
path: '/user/mgmt' // ❌ 避免缩写
path: '/u/m' // ❌ 避免单字符
3. 权限设计
// 明确的权限配置
roles: ['admin'] // ✅ 单角色
roles: ['admin', 'manager'] // ✅ 多角色
requiresAuth: true // ✅ 明确需要认证