heritage-frontend/doc/路由管理系统文档.md
Leo 80ab2736c9 文档:添加公共资源和项目文档
- 添加公共静态资源(public/)
- 添加项目文档(doc/)
- 添加项目说明文档(README.md, README.zh-CN.md)
- 添加Claude开发规范文档(CLAUDE.md)
- 添加开源许可证(LICENSE)
- 添加构建脚本(build/)
- 添加国际化资源(locales/)
- 添加编辑器配置(.vscode/)
- 添加GitHub工作流配置(.github/)
2025-10-08 02:24:28 +08:00

12 KiB

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 处理)
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

主要功能

  1. 外链处理: 检测并打开外部链接
  2. 进度条控制: 开始 LoadingBar
  3. 登录验证: 检查用户认证状态
  4. 路由初始化: 动态加载和注册路由
  5. 重定向处理: 处理各种重定向场景

核心逻辑

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)

特殊标签页类型

  1. 固定标签页 (pinTab: true): 不可关闭的标签页
  2. 无标签页 (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                  // ✅ 明确需要认证

📚 相关文档