# 路由系统 **本文引用的文件** - [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。