# 路由系统
**本文引用的文件**
- [fms-vue/src/router/index.js](file://fms-vue/src/router/index.js)
- [fms-vue/src/layouts/DefaultLayout.vue](file://fms-vue/src/layouts/DefaultLayout.vue)
- [fms-vue/src/stores/auth.js](file://fms-vue/src/stores/auth.js)
- [fms-vue/src/stores/permissions.js](file://fms-vue/src/stores/permissions.js)
- [fms-vue/src/layouts/components/AppSidebar.vue](file://fms-vue/src/layouts/components/AppSidebar.vue)
- [fms-vue/src/utils/cacheKey.js](file://fms-vue/src/utils/cacheKey.js)
- [fms-vue/tests/router-auth.spec.js](file://fms-vue/tests/router-auth.spec.js)
- [fms-vue/src/views/login/index.vue](file://fms-vue/src/views/login/index.vue)
- [fms-vue/src/views/dashboard/index.vue](file://fms-vue/src/views/dashboard/index.vue)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向 FMS 前端(基于 Vue Router)的路由系统,系统性说明静态与动态路由定义、路由守卫(beforeEach)的权限校验与菜单加载逻辑、路由与权限系统的集成方式(动态菜单生成与页面访问控制)、布局组件 DefaultLayout 的结构与嵌套路由实现、路由懒加载与代码分割配置,以及路由参数传递与查询字符串处理的最佳实践。
## 项目结构
FMS 前端采用“路由 + 布局 + 状态管理”的分层组织:
- 路由层:集中定义静态路由、重定向、通配符与全局守卫。
- 布局层:DefaultLayout 提供侧栏、顶栏、页签与内容区,承载嵌套路由渲染与 keep-alive 缓存策略。
- 状态层:auth 负责登录态;permissions 负责模块与功能点授权数据;app 负责主题、侧栏折叠、页签列表与路由刷新计数。
- 视图层:登录、工作台、详情页等页面按路由懒加载。
```mermaid
graph TB
subgraph "路由层"
R["router/index.js"]
end
subgraph "布局层"
L["layouts/DefaultLayout.vue"]
S["layouts/components/AppSidebar.vue"]
end
subgraph "状态层"
A["stores/auth.js"]
P["stores/permissions.js"]
APP["stores/app.js"]
end
subgraph "视图层"
V1["views/login/index.vue"]
V2["views/dashboard/index.vue"]
V3["views/detail/DetailPage.vue"]
end
R --> L
L --> S
R --> A
R --> P
L --> APP
L --> V1
L --> V2
L --> V3
```
图表来源
- [fms-vue/src/router/index.js:10-105](file://fms-vue/src/router/index.js#L10-L105)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [fms-vue/src/stores/auth.js:1-49](file://fms-vue/src/stores/auth.js#L1-L49)
- [fms-vue/src/stores/permissions.js:13-196](file://fms-vue/src/stores/permissions.js#L13-L196)
章节来源
- [fms-vue/src/router/index.js:10-105](file://fms-vue/src/router/index.js#L10-L105)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
## 核心组件
- 路由配置与守卫:集中定义静态路由、重定向、通配符,并在 beforeEach 中完成登录态校验、权限数据加载与模块级访问控制。
- 布局组件 DefaultLayout:封装侧栏、顶栏、页签与内容区,使用 keep-alive 与命名包装组件实现细粒度缓存与销毁。
- 权限 Store:拉取模块树与用户授权,提供 canAccess/canPower 判断,支持管理员提权与并发去重。
- 侧边栏 AppSidebar:将权限模块树转换为导航菜单,并注入 moduleId 以配合路由守卫进行访问控制。
- 工具函数 cacheKeyOf:为详情页等“同组件多参数”场景生成唯一 keep-alive include 名,确保按 fullPath 精确缓存与销毁。
章节来源
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
- [fms-vue/src/layouts/DefaultLayout.vue:16-58](file://fms-vue/src/layouts/DefaultLayout.vue#L16-L58)
- [fms-vue/src/stores/permissions.js:41-109](file://fms-vue/src/stores/permissions.js#L41-L109)
- [fms-vue/src/layouts/components/AppSidebar.vue:62-84](file://fms-vue/src/layouts/components/AppSidebar.vue#L62-L84)
- [fms-vue/src/utils/cacheKey.js:1-11](file://fms-vue/src/utils/cacheKey.js#L1-L11)
## 架构总览
下图展示了从浏览器发起路由到页面渲染的完整流程,包括登录态检查、权限数据加载、菜单构建与页面渲染。
```mermaid
sequenceDiagram
participant U as "用户"
participant R as "Router
index.js"
participant A as "AuthStore
auth.js"
participant P as "PermissionStore
permissions.js"
participant L as "DefaultLayout
DefaultLayout.vue"
participant V as "视图组件"
U->>R : 访问受保护路由
R->>A : 读取 isAuthenticated
alt 未登录
R-->>U : 重定向到 /login?redirect=...
else 已登录
R->>P : load() 获取模块与授权
P-->>R : 返回 modules/powerCodes
R->>R : 校验 meta.moduleId 是否可访问
alt 无权限
R-->>U : 重定向到 /dashboard
else 有权限
R->>L : 渲染嵌套路由
L->>V : 懒加载并渲染页面
V-->>U : 展示页面
end
end
```
图表来源
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
- [fms-vue/src/stores/permissions.js:41-109](file://fms-vue/src/stores/permissions.js#L41-L109)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
## 详细组件分析
### 路由配置与守卫
- 静态路由:包含登录页、带默认重定向的主布局及其子路由(工作台、测试页、演示页、附件演示、详情页、模块管理等)。
- 通配符:未匹配路径统一重定向至工作台。
- 懒加载:所有页面组件通过动态 import() 按需加载,减少首屏体积。
- 全局守卫 beforeEach:
- 登录态校验:未登录访问受保护路由时重定向到登录页并携带 redirect 查询参数;已登录访问登录页则跳转工作台。
- 权限数据加载:对需要认证的路由,在守卫中调用权限 store 的 load(),保证首屏与业务页在进入前完成权限数据准备,避免“先白屏后出菜单”的闪烁。
- 模块级访问控制:若路由 meta 中声明 moduleId,则调用 canAccess 校验,无权限时重定向到工作台。
- 进度条:进入路由时启动 NProgress,离开时结束。
```mermaid
flowchart TD
Start(["进入路由"]) --> CheckLogin{"是否登录?"}
CheckLogin --> |否| ToLogin["重定向到 /login?redirect=当前路径"]
CheckLogin --> |是| LoadPerm["加载权限数据 load()"]
LoadPerm --> CheckModule{"是否声明 moduleId?"}
CheckModule --> |否| Render["渲染页面"]
CheckModule --> |是| CanAccess{"canAccess(moduleId)?"}
CanAccess --> |否| ToDash["重定向到 /dashboard"]
CanAccess --> |是| Render
ToLogin --> End(["结束"])
ToDash --> End
Render --> End
```
图表来源
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
- [fms-vue/src/stores/permissions.js:131-138](file://fms-vue/src/stores/permissions.js#L131-L138)
章节来源
- [fms-vue/src/router/index.js:10-105](file://fms-vue/src/router/index.js#L10-L105)
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
### 布局组件 DefaultLayout 与嵌套路由
- 结构:左侧 SplitterPanel 承载 AppSidebar,右侧主面板包含 AppTopbar、AppTabs 与 router-view 内容区。
- 嵌套路由:根路由指向 DefaultLayout,children 下挂载各业务页面,router-view 负责渲染对应子组件。
- 缓存策略:
- 外层 keep-alive 的 include 来自 appStore.cachedViews(当前打开页签对应的组件名集合),关闭页签即销毁实例。
- 普通页面使用 componentName 作为 include 名;详情页根据 fullPath 生成唯一 name(page_+encodeURIComponent(fullPath)),实现“同组件不同参数独立缓存”。
- 内层 key 使用 `${fullPath}:${refreshKey}`,刷新页签仅触发内层重新挂载,不破坏外层缓存槽位。
- 资源回收:监听 visitedTabs 变化,清理已关闭详情页的包装组件定义,防止 pageWrapperCache 无限增长。
```mermaid
classDiagram
class DefaultLayout {
+getPageWrapper(name, fullPath, innerComponent)
+sidebarWidth
+cachedViews
}
class AppSidebar {
+navMain
}
class AppTabs {
+visitedTabs
}
class KeepAlive {
+include
+max
}
DefaultLayout --> AppSidebar : "渲染侧栏"
DefaultLayout --> AppTabs : "渲染页签"
DefaultLayout --> KeepAlive : "包裹 router-view"
```
图表来源
- [fms-vue/src/layouts/DefaultLayout.vue:16-58](file://fms-vue/src/layouts/DefaultLayout.vue#L16-L58)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [fms-vue/src/stores/app.js:20-28](file://fms-vue/src/stores/app.js#L20-L28)
章节来源
- [fms-vue/src/layouts/DefaultLayout.vue:16-58](file://fms-vue/src/layouts/DefaultLayout.vue#L16-L58)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [fms-vue/src/stores/app.js:20-28](file://fms-vue/src/stores/app.js#L20-L28)
### 路由与权限系统集成
- 动态菜单生成:AppSidebar 从 permissions.store.modules 过滤出可显示模块(b_canmenu=1、b_canuse=1、类型为 directory/page),构建父子树并映射为导航项;非目录项附带 moduleId,供路由守卫校验。
- 页面访问控制:路由 meta 中声明 moduleId 时,beforeEach 调用 canAccess 校验;无权限直接重定向到工作台。
- 管理员提权:当账号为 g3soft 时,权限 store 直接加载全部启用模块,跳过授权表查询,简化开发调试。
```mermaid
sequenceDiagram
participant SB as "AppSidebar"
participant PS as "PermissionStore"
participant RS as "Router Guard"
SB->>PS : 读取 modules
PS-->>SB : 返回可用模块数组
SB->>SB : 构建树并生成导航项(含moduleId)
RS->>PS : canAccess(moduleId)
PS-->>RS : true/false
alt false
RS-->>RS : 重定向到 /dashboard
end
```
图表来源
- [fms-vue/src/layouts/components/AppSidebar.vue:62-84](file://fms-vue/src/layouts/components/AppSidebar.vue#L62-L84)
- [fms-vue/src/stores/permissions.js:131-138](file://fms-vue/src/stores/permissions.js#L131-L138)
- [fms-vue/src/router/index.js:128-131](file://fms-vue/src/router/index.js#L128-L131)
章节来源
- [fms-vue/src/layouts/components/AppSidebar.vue:62-84](file://fms-vue/src/layouts/components/AppSidebar.vue#L62-L84)
- [fms-vue/src/stores/permissions.js:131-138](file://fms-vue/src/stores/permissions.js#L131-L138)
- [fms-vue/src/router/index.js:128-131](file://fms-vue/src/router/index.js#L128-L131)
### 路由懒加载与代码分割
- 所有页面组件均通过动态 import() 引入,例如登录页、工作台、详情页、演示页等,实现路由级代码分割,降低首屏包体。
- 结合 keep-alive 与命名包装组件,既保证首次加载的性能,又能在切换时复用已加载的模块。
章节来源
- [fms-vue/src/router/index.js:13-99](file://fms-vue/src/router/index.js#L13-L99)
### 路由参数传递与查询字符串处理
- 动态路由参数:详情页使用 /detail/:id,组件通过 props 接收 id;每个 fullPath 对应独立的 keep-alive 缓存,确保不同参数间状态隔离。
- 查询字符串:
- 登录后重定向:未登录访问受保护路由时,重定向到 /login?redirect=原路径,登录成功后通过 query.redirect 跳回目标页。
- 会话提示:登录页根据 route.query.reason 显示“账号在其他设备登录”或“登录过期”等提示。
- 最佳实践:
- 敏感信息不要放在 URL;复杂对象建议通过状态管理或 sessionStorage 传递。
- 查询参数用于简单筛选与重定向;路由参数用于资源标识(如 id)。
章节来源
- [fms-vue/src/router/index.js:84-89](file://fms-vue/src/router/index.js#L84-L89)
- [fms-vue/src/router/index.js:118-121](file://fms-vue/src/router/index.js#L118-L121)
- [fms-vue/src/views/login/index.vue:17-23](file://fms-vue/src/views/login/index.vue#L17-L23)
- [fms-vue/src/views/login/index.vue:59-62](file://fms-vue/src/views/login/index.vue#L59-L62)
- [fms-vue/src/utils/cacheKey.js:1-11](file://fms-vue/src/utils/cacheKey.js#L1-L11)
## 依赖关系分析
- 路由依赖 auth 与 permissions 两个 store,在 beforeEach 中读取登录态与权限数据。
- 布局依赖 app store 管理页签与主题,同时依赖 keep-alive 与动态组件渲染机制。
- 侧边栏依赖权限 store 的模块树,生成导航菜单并注入 moduleId。
- 详情页通过 cacheKeyOf 生成唯一 include 名,确保 keep-alive 精确缓存与销毁。
```mermaid
graph LR
Router["router/index.js"] --> Auth["stores/auth.js"]
Router --> Perm["stores/permissions.js"]
Layout["layouts/DefaultLayout.vue"] --> App["stores/app.js"]
Sidebar["layouts/components/AppSidebar.vue"] --> Perm
Detail["views/detail/DetailPage.vue"] --> Cache["utils/cacheKey.js"]
```
图表来源
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [fms-vue/src/layouts/components/AppSidebar.vue:62-84](file://fms-vue/src/layouts/components/AppSidebar.vue#L62-L84)
- [fms-vue/src/utils/cacheKey.js:1-11](file://fms-vue/src/utils/cacheKey.js#L1-L11)
章节来源
- [fms-vue/src/router/index.js:107-137](file://fms-vue/src/router/index.js#L107-L137)
- [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [fms-vue/src/layouts/components/AppSidebar.vue:62-84](file://fms-vue/src/layouts/components/AppSidebar.vue#L62-L84)
- [fms-vue/src/utils/cacheKey.js:1-11](file://fms-vue/src/utils/cacheKey.js#L1-L11)
## 性能考量
- 路由级懒加载:所有页面组件通过动态 import() 按需加载,减少首屏体积。
- 权限数据并发加载:权限 store 使用 Promise.all 并发拉取模块、用户模块、功能点与模块功能点,缩短加载时间。
- 并发去重:同一主体多次调用 load() 共享同一请求,避免重复网络开销。
- 缓存策略:keep-alive 的 include 与命名包装组件结合,既保留切换体验,又避免内存泄漏;关闭页签即销毁实例。
- 进度反馈:NProgress 提升感知性能。
章节来源
- [fms-vue/src/router/index.js:1-8](file://fms-vue/src/router/index.js#L1-L8)
- [fms-vue/src/stores/permissions.js:77-82](file://fms-vue/src/stores/permissions.js#L77-L82)
- [fms-vue/src/stores/permissions.js:71-73](file://fms-vue/src/stores/permissions.js#L71-L73)
- [fms-vue/src/layouts/DefaultLayout.vue:16-58](file://fms-vue/src/layouts/DefaultLayout.vue#L16-L58)
## 故障排查指南
- 未登录被重定向到登录页:检查路由 meta.requiresAuth 与 authStore.isAuthenticated;确认登录成功并已设置 session。
- 登录后仍无法访问业务页:确认权限 store 已成功加载 modules 与 powerCodes;检查路由 meta.moduleId 是否与权限匹配。
- 刷新后菜单空白:确认 persist 插件正确恢复登录态;守卫在 beforeEach 中会再次 load() 权限数据。
- 详情页缓存异常:确认详情页 meta.cacheByKey=true,且使用 cacheKeyOf(fullPath) 生成唯一 include 名;关闭页签应销毁对应实例。
- 切换账号后权限错乱:权限 store 会在主体变化时清空缓存并重新加载;确保 clear() 逻辑生效。
章节来源
- [fms-vue/src/router/index.js:113-131](file://fms-vue/src/router/index.js#L113-L131)
- [fms-vue/src/stores/permissions.js:155-182](file://fms-vue/src/stores/permissions.js#L155-L182)
- [fms-vue/src/utils/cacheKey.js:1-11](file://fms-vue/src/utils/cacheKey.js#L1-L11)
- [fms-vue/tests/router-auth.spec.js:45-108](file://fms-vue/tests/router-auth.spec.js#L45-L108)
## 结论
FMS 前端路由系统以 Vue Router 为核心,结合 Pinia 状态管理与 keep-alive 缓存策略,实现了安全的权限控制、灵活的动态菜单与高效的页面渲染。通过路由懒加载与并发权限加载,兼顾了首屏性能与用户体验;通过命名包装组件与精确缓存键,解决了“同组件多参数”的缓存难题。整体设计清晰、可扩展性强,适合企业级后台应用。
## 附录
- 新增路由建议:
- 在 router/index.js 的 children 中添加新路由,必要时设置 meta.title、componentName、moduleId。
- 如需权限控制,确保权限系统中存在对应模块并启用菜单显示。
- 详情页缓存:
- 为详情页路由设置 meta.cacheByKey=true,并使用 cacheKeyOf(fullPath) 生成唯一 include 名。
- 查询参数使用:
- 登录重定向使用 query.redirect;会话提示使用 query.reason;其他简单筛选可使用 query。