coder-common-thin-frontend/doc/路由管理系统文档.md
2025-07-04 15:08:19 +08:00

604 lines
12 KiB
Markdown

# Nova Admin 路由系统文档
## 概述
Nova Admin 基于 Vue Router 4 构建了一套完整的路由管理系统,支持静态路由和动态路由,具备完善的路由守卫、权限控制、菜单生成等功能。
## 🏗️ 路由架构
### 核心组件
1. **路由配置** (`src/router/`)
2. **路由守卫** (`src/router/guard.ts`)
3. **路由状态管理** (`src/store/router/`)
4. **菜单生成** (`src/store/router/helper.ts`)
5. **标签页管理** (`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 处理)
```typescript
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`
业务功能路由配置:
```typescript
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 接口
```typescript
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)
```typescript
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. 路由初始化流程
```mermaid
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. 路由守卫执行流程
```mermaid
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`
#### 主要功能
1. **外链处理**: 检测并打开外部链接
2. **进度条控制**: 开始 LoadingBar
3. **登录验证**: 检查用户认证状态
4. **路由初始化**: 动态加载和注册路由
5. **重定向处理**: 处理各种重定向场景
#### 核心逻辑
```typescript
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 守卫
```typescript
router.beforeResolve((to) => {
// 设置菜单高亮
routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath)
// 添加标签页
tabStore.addTab(to)
// 设置当前标签页
tabStore.setCurrentTab(to.fullPath)
})
```
### afterEach 守卫
```typescript
router.afterEach((to) => {
// 设置页面标题
document.title = `${to.meta.title} - ${title}`
// 结束进度条
appStore.showProgress && window.$loadingBar?.finish()
})
```
## 🏪 路由状态管理
### RouteStore 状态
```typescript
interface RoutesStatus {
/** 路由是否已初始化 */
isInitAuthRoute: boolean
/** 菜单数据 */
menus: MenuOption[]
/** 原始路由数据 */
rowRoutes: AppRoute.RowRoute[]
/** 当前激活菜单 */
activeMenu: string | null
/** 缓存路由 */
cacheRoutes: string[]
}
```
### 核心方法
#### initAuthRoute()
初始化认证路由的完整流程:
```typescript
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()
根据配置模式获取路由信息:
```typescript
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
}
}
```
## 🎨 菜单生成
### 菜单生成流程
```typescript
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)
}
```
### 菜单数据结构
```typescript
interface MenuOption {
label: string // 菜单标题
key: string // 菜单键值
icon?: () => VNode // 菜单图标
children?: MenuOption[] // 子菜单
disabled?: boolean // 是否禁用
}
```
## 🏷️ 标签页系统
### TabStore 管理
标签页系统与路由系统紧密集成:
```typescript
// 添加标签页
tabStore.addTab(route)
// 设置当前标签页
tabStore.setCurrentTab(route.fullPath)
// 关闭标签页
tabStore.closeTab(fullPath)
```
### 特殊标签页类型
1. **固定标签页** (`pinTab: true`): 不可关闭的标签页
2. **无标签页** (`withoutTab: true`): 不显示在标签栏的页面
## ⚙️ 配置选项
### 环境变量
```bash
# 路由加载模式
VITE_ROUTE_LOAD_MODE=static|dynamic
# 默认首页路径
VITE_HOME_PATH=/dashboard/monitor
# 应用名称
VITE_APP_NAME=Nova Admin
```
### 路由模式配置
```typescript
// 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` 中添加:
```typescript
{
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. 路由权限配置
```typescript
{
name: 'adminPage',
path: '/admin',
title: '管理页面',
requiresAuth: true,
roles: ['admin', 'super'], // 权限控制
// ...
}
```
### 3. 嵌套路由
```typescript
// 父路由
{
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. 路由缓存
```typescript
{
name: 'cachedPage',
path: '/cached',
title: '缓存页面',
keepAlive: true, // 启用路由缓存
// ...
}
```
## 🐛 常见问题
### 1. 动态路由不生效
检查:
- 路由数据格式是否正确
- 组件路径是否存在
- 权限配置是否正确
### 2. 菜单不显示
检查:
- `hide` 属性是否为 `true`
- 权限验证是否通过
- `menuType` 是否正确设置
### 3. 404 页面循环
确保:
- 静态路由已正确配置
- 路由初始化完成
- 通配符路由放在最后
### 4. 标签页问题
检查:
- `withoutTab` 设置
- 路由路径是否正确
- TabStore 状态是否正常
## 🎯 最佳实践
### 1. 路由命名规范
```typescript
// 推荐命名方式
name: 'userManagement' // 驼峰命名
name: 'user-management' // 中划线命名
name: 'user_management' // 下划线命名
```
### 2. 路径规范
```typescript
// 路径应该语义化
path: '/user/management' // ✅ 好的
path: '/user/mgmt' // ❌ 避免缩写
path: '/u/m' // ❌ 避免单字符
```
### 3. 权限设计
```typescript
// 明确的权限配置
roles: ['admin'] // ✅ 单角色
roles: ['admin', 'manager'] // ✅ 多角色
requiresAuth: true // ✅ 明确需要认证
```
## 📚 相关文档
- [登录鉴权文档](./登录鉴权系统文档)
- [权限系统文档](./权限管理系统文档)
- [整体架构文档](./整体架构文档)