Files
workspace/code/fms/.qoder/repowiki/zh/content/前端架构/路由系统.md
T
2026-08-23 21:02:41 +08:00

17 KiB
Raw Blame History

路由系统

**本文引用的文件** - [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 负责主题、侧栏折叠、页签列表与路由刷新计数。
  • 视图层:登录、工作台、详情页等页面按路由懒加载。
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

图表来源

章节来源

核心组件

  • 路由配置与守卫:集中定义静态路由、重定向、通配符,并在 beforeEach 中完成登录态校验、权限数据加载与模块级访问控制。
  • 布局组件 DefaultLayout:封装侧栏、顶栏、页签与内容区,使用 keep-alive 与命名包装组件实现细粒度缓存与销毁。
  • 权限 Store:拉取模块树与用户授权,提供 canAccess/canPower 判断,支持管理员提权与并发去重。
  • 侧边栏 AppSidebar:将权限模块树转换为导航菜单,并注入 moduleId 以配合路由守卫进行访问控制。
  • 工具函数 cacheKeyOf:为详情页等“同组件多参数”场景生成唯一 keep-alive include 名,确保按 fullPath 精确缓存与销毁。

章节来源

架构总览

下图展示了从浏览器发起路由到页面渲染的完整流程,包括登录态检查、权限数据加载、菜单构建与页面渲染。

sequenceDiagram
participant U as "用户"
participant R as "Router<br/>index.js"
participant A as "AuthStore<br/>auth.js"
participant P as "PermissionStore<br/>permissions.js"
participant L as "DefaultLayout<br/>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

图表来源

详细组件分析

路由配置与守卫

  • 静态路由:包含登录页、带默认重定向的主布局及其子路由(工作台、测试页、演示页、附件演示、详情页、模块管理等)。
  • 通配符:未匹配路径统一重定向至工作台。
  • 懒加载:所有页面组件通过动态 import() 按需加载,减少首屏体积。
  • 全局守卫 beforeEach:
    • 登录态校验:未登录访问受保护路由时重定向到登录页并携带 redirect 查询参数;已登录访问登录页则跳转工作台。
    • 权限数据加载:对需要认证的路由,在守卫中调用权限 store 的 load(),保证首屏与业务页在进入前完成权限数据准备,避免“先白屏后出菜单”的闪烁。
    • 模块级访问控制:若路由 meta 中声明 moduleId,则调用 canAccess 校验,无权限时重定向到工作台。
  • 进度条:进入路由时启动 NProgress,离开时结束。
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

图表来源

章节来源

布局组件 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 无限增长。
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"

图表来源

章节来源

路由与权限系统集成

  • 动态菜单生成:AppSidebar 从 permissions.store.modules 过滤出可显示模块(b_canmenu=1、b_canuse=1、类型为 directory/page),构建父子树并映射为导航项;非目录项附带 moduleId,供路由守卫校验。
  • 页面访问控制:路由 meta 中声明 moduleId 时,beforeEach 调用 canAccess 校验;无权限直接重定向到工作台。
  • 管理员提权:当账号为 g3soft 时,权限 store 直接加载全部启用模块,跳过授权表查询,简化开发调试。
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

图表来源

章节来源

路由懒加载与代码分割

  • 所有页面组件均通过动态 import() 引入,例如登录页、工作台、详情页、演示页等,实现路由级代码分割,降低首屏包体。
  • 结合 keep-alive 与命名包装组件,既保证首次加载的性能,又能在切换时复用已加载的模块。

章节来源

路由参数传递与查询字符串处理

  • 动态路由参数:详情页使用 /detail/:id,组件通过 props 接收 id;每个 fullPath 对应独立的 keep-alive 缓存,确保不同参数间状态隔离。
  • 查询字符串:
    • 登录后重定向:未登录访问受保护路由时,重定向到 /login?redirect=原路径,登录成功后通过 query.redirect 跳回目标页。
    • 会话提示:登录页根据 route.query.reason 显示“账号在其他设备登录”或“登录过期”等提示。
  • 最佳实践:
    • 敏感信息不要放在 URL;复杂对象建议通过状态管理或 sessionStorage 传递。
    • 查询参数用于简单筛选与重定向;路由参数用于资源标识(如 id)。

章节来源

依赖关系分析

  • 路由依赖 auth 与 permissions 两个 store,在 beforeEach 中读取登录态与权限数据。
  • 布局依赖 app store 管理页签与主题,同时依赖 keep-alive 与动态组件渲染机制。
  • 侧边栏依赖权限 store 的模块树,生成导航菜单并注入 moduleId。
  • 详情页通过 cacheKeyOf 生成唯一 include 名,确保 keep-alive 精确缓存与销毁。
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"]

图表来源

章节来源

性能考量

  • 路由级懒加载:所有页面组件通过动态 import() 按需加载,减少首屏体积。
  • 权限数据并发加载:权限 store 使用 Promise.all 并发拉取模块、用户模块、功能点与模块功能点,缩短加载时间。
  • 并发去重:同一主体多次调用 load() 共享同一请求,避免重复网络开销。
  • 缓存策略:keep-alive 的 include 与命名包装组件结合,既保留切换体验,又避免内存泄漏;关闭页签即销毁实例。
  • 进度反馈:NProgress 提升感知性能。

章节来源

故障排查指南

  • 未登录被重定向到登录页:检查路由 meta.requiresAuth 与 authStore.isAuthenticated;确认登录成功并已设置 session。
  • 登录后仍无法访问业务页:确认权限 store 已成功加载 modules 与 powerCodes;检查路由 meta.moduleId 是否与权限匹配。
  • 刷新后菜单空白:确认 persist 插件正确恢复登录态;守卫在 beforeEach 中会再次 load() 权限数据。
  • 详情页缓存异常:确认详情页 meta.cacheByKey=true,且使用 cacheKeyOf(fullPath) 生成唯一 include 名;关闭页签应销毁对应实例。
  • 切换账号后权限错乱:权限 store 会在主体变化时清空缓存并重新加载;确保 clear() 逻辑生效。

章节来源

结论

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。