# 应用结构 **本文引用的文件** - [fms-vue/src/main.js](file://fms-vue/src/main.js) - [fms-vue/src/App.vue](file://fms-vue/src/App.vue) - [fms-vue/index.html](file://fms-vue/index.html) - [fms-vue/vite.config.js](file://fms-vue/vite.config.js) - [fms-vue/package.json](file://fms-vue/package.json) - [fms-vue/src/router/index.js](file://fms-vue/src/router/index.js) - [fms-vue/src/stores/app.js](file://fms-vue/src/stores/app.js) - [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/DefaultLayout.vue](file://fms-vue/src/layouts/DefaultLayout.vue) - [fms-vue/src/theme/index.js](file://fms-vue/src/theme/index.js) - [fms-vue/src/services/http.js](file://fms-vue/src/services/http.js) ## 目录 1. [简介](#简介) 2. [项目结构](#项目结构) 3. [核心组件](#核心组件) 4. [架构总览](#架构总览) 5. [详细组件分析](#详细组件分析) 6. [依赖关系分析](#依赖关系分析) 7. [性能与构建优化](#性能与构建优化) 8. [故障排查指南](#故障排查指南) 9. [结论](#结论) ## 简介 本文件面向 FMS 前端(Vue 3 + Vite)应用的“应用结构”主题,聚焦以下目标: - 解释应用初始化流程:入口启动、Pinia 状态管理、路由配置与插件注册。 - 说明根组件 App.vue 的职责与生命周期管理。 - 梳理 Vite 构建配置的关键设置:开发服务器、代理、别名与插件。 - 解析 HTML 入口结构与引导脚本、加载屏与错误处理机制。 - 总结目录组织规范与最佳实践。 ## 项目结构 FMS Vue 前端采用按功能域划分的目录组织方式,核心目录如下: - src/main.js:应用启动、插件注册、挂载与引导流程。 - src/App.vue:根组件,仅渲染当前路由与可选的性能监控浮层。 - src/router/index.js:路由定义、全局守卫(鉴权与权限)、进度条集成。 - src/stores/*:Pinia 状态管理,包括应用主题/页签、认证、权限等。 - src/layouts/*:布局容器,如 DefaultLayout 提供侧栏、顶栏、页签与内容区。 - src/components/ui/*:自研 UI 组件库(按钮、表单、消息、抽屉等)。 - src/services/*:HTTP 客户端封装与业务 API 调用。 - src/theme/*:主题切换与主色变量注入。 - src/utils/*:通用工具函数。 - index.html:HTML 入口,包含首屏加载屏、主题预置与错误兜底。 - vite.config.js:Vite 构建与开发服务器配置。 - package.json:脚本命令与依赖声明。 ```mermaid graph TB A["index.html
HTML 入口"] --> B["src/main.js
应用启动"] B --> C["src/App.vue
根组件"] B --> D["src/router/index.js
路由与守卫"] B --> E["src/stores/*
Pinia 状态"] B --> F["src/theme/index.js
主题同步"] D --> G["src/layouts/DefaultLayout.vue
布局壳"] G --> H["src/views/*
页面组件"] E --> I["src/services/http.js
HTTP 拦截器"] ``` 图表来源 - [fms-vue/index.html:1-295](file://fms-vue/index.html#L1-L295) - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) - [fms-vue/src/App.vue:1-15](file://fms-vue/src/App.vue#L1-L15) - [fms-vue/src/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/layouts/DefaultLayout.vue:1-200](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L200) - [fms-vue/src/theme/index.js:1-16](file://fms-vue/src/theme/index.js#L1-L16) - [fms-vue/src/services/http.js:1-111](file://fms-vue/src/services/http.js#L1-L111) 章节来源 - [fms-vue/index.html:1-295](file://fms-vue/index.html#L1-L295) - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) - [fms-vue/package.json:1-48](file://fms-vue/package.json#L1-L48) ## 核心组件 - 应用入口 main.js - 创建 Pinia 实例并启用持久化插件。 - 按需注册表格特性(区域选择),以支持表格行编辑事件。 - 创建 Vue 应用实例,注册 Pinia 与 Router。 - 在挂载前执行主题同步,避免首屏闪烁。 - 等待路由就绪后挂载到 #app,并通过过渡动画移除加载屏。 - 捕获启动期异常,展示错误信息与重试按钮。 - 根组件 App.vue - 仅渲染 router-view,作为页面容器。 - 开发环境异步引入 FPS 监控浮层,生产环境不打包。 - 路由 router/index.js - 使用 createWebHistory,定义登录、工作台、测试、演示、详情、模块管理等路由。 - 通过 beforeEach 实现鉴权与权限校验;未登录跳转登录页,已登录访问登录页重定向至工作台。 - 统一在路由守卫中加载权限数据,确保菜单一次性到位。 - afterEach 关闭 NProgress 进度条。 - 状态管理 stores - app.js:主题(深浅色、主色)、侧边栏折叠、语言、页签列表与刷新计数;提供 initThemeSync 在 store 激活后立即将状态同步到 DOM。 - auth.js:会话信息(token、用户)与用户信息(机构码、账号)的持久化策略(sessionStorage/localStorage)。 - permissions.js:拉取模块与授权数据,支持管理员提权、主体去重并发复用、按模块编码判断访问与功能点权限。 - 布局 DefaultLayout.vue - 组合侧栏、顶栏、页签与内容区。 - 基于 Splitter 实现侧栏宽度拖拽调整。 - 通过 keep-alive 与动态命名包装组件实现页签缓存与精确销毁。 - 详情页根据参数独立缓存,关闭即释放。 - 主题 theme/index.js - 纯函数写入 CSS 变量与 dark class,配合 app store 的 watch 实现即时生效。 - HTTP 服务 services/http.js - Axios 实例封装,请求拦截注入 Authorization。 - 响应拦截统一处理 ApiResult、鉴权失败跳转登录、网络/服务端错误提示。 章节来源 - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) - [fms-vue/src/App.vue:1-15](file://fms-vue/src/App.vue#L1-L15) - [fms-vue/src/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/stores/app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123) - [fms-vue/src/stores/auth.js:1-49](file://fms-vue/src/stores/auth.js#L1-L49) - [fms-vue/src/stores/permissions.js:1-196](file://fms-vue/src/stores/permissions.js#L1-L196) - [fms-vue/src/layouts/DefaultLayout.vue:1-200](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L200) - [fms-vue/src/theme/index.js:1-16](file://fms-vue/src/theme/index.js#L1-L16) - [fms-vue/src/services/http.js:1-111](file://fms-vue/src/services/http.js#L1-L111) ## 架构总览 下图展示了从 HTML 入口到应用挂载、路由与状态管理的整体流程。 ```mermaid sequenceDiagram participant U as "浏览器" participant H as "index.html" participant M as "main.js" participant P as "Pinia" participant R as "Router" participant A as "App.vue" participant L as "DefaultLayout.vue" U->>H : 加载 HTML H-->>U : 显示加载屏 H->>M : 执行 /src/main.js M->>P : 创建实例并启用持久化插件 M->>R : 注册路由 M->>M : 主题同步initThemeSync M->>R : 等待 isReady() M->>A : 挂载到 #app A->>L : 渲染布局 L->>L : keep-alive 缓存页签 M-->>U : 淡出加载屏 ``` 图表来源 - [fms-vue/index.html:1-295](file://fms-vue/index.html#L1-L295) - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) - [fms-vue/src/App.vue:1-15](file://fms-vue/src/App.vue#L1-L15) - [fms-vue/src/layouts/DefaultLayout.vue:1-200](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L200) ## 详细组件分析 ### 应用启动流程(main.js) - 初始化顺序 - 创建 Pinia 并启用 persistedstate 插件。 - 注册 stk-table 的区域选择特性。 - 创建 Vue 应用,安装 Pinia 与 Router。 - 在 mount 前调用 initThemeSync,恢复持久化主题并写入 DOM,避免首屏闪烁。 - 挂载时机与加载屏 - 等待 router.isReady() 确保路由守卫完成(含权限加载)。 - 将加载屏移动到 body 下,避免被 #app 替换导致闪烁。 - 挂载应用后,下一帧添加隐藏类触发淡出过渡,并在结束后清理节点与临时主题类。 - 错误处理 - 捕获启动期异常,切换加载屏为错误态,显示提示信息与重试按钮。 ```mermaid flowchart TD Start(["启动 bootstrap"]) --> WaitRoute["等待路由就绪"] WaitRoute --> MoveBoot["移动加载屏到 body"] MoveBoot --> Mount["挂载应用到 #app"] Mount --> FadeOut["下一帧淡出加载屏"] FadeOut --> Cleanup["过渡结束后清理加载屏"] WaitRoute --> |异常| ShowError["显示错误态与重试"] ``` 图表来源 - [fms-vue/src/main.js:21-78](file://fms-vue/src/main.js#L21-L78) 章节来源 - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) ### 根组件 App.vue - 职责 - 作为应用根容器,仅渲染当前路由视图。 - 开发环境下异步引入 FPS 监控浮层,生产环境不打包,减少体积。 - 生命周期 - 无额外逻辑,完全交由路由与布局管理页面生命周期。 章节来源 - [fms-vue/src/App.vue:1-15](file://fms-vue/src/App.vue#L1-L15) ### 路由与权限(router/index.js) - 路由定义 - 登录页、工作台、测试页、演示页、附件演示、详情页、模块管理等。 - 详情页支持按参数独立缓存(cacheByKey),结合布局层的命名包装组件实现 per-param 缓存。 - 全局守卫 - beforeEach:启动 NProgress;未登录访问受保护路由跳转登录;已登录访问登录页重定向到工作台。 - 权限加载:对需要权限的路由,统一在守卫中调用权限 store 的 load,确保菜单一次性到位。 - 权限校验:若路由 meta 指定 moduleId,则检查 canAccess,无权限跳转工作台。 - 结束钩子 - afterEach:关闭 NProgress。 ```mermaid sequenceDiagram participant R as "Router" participant G as "beforeEach" participant A as "Auth Store" participant P as "Permission Store" participant S as "Service/API" R->>G : 进入路由 G->>G : 启动进度条 alt 目标是登录页 G->>A : 检查是否已登录 alt 已登录 G-->>R : 重定向到 /dashboard else 未登录 G-->>R : 放行 end else 目标需要鉴权 G->>A : 检查是否已登录 alt 未登录 G-->>R : 重定向到 /login?redirect=... else 已登录 G->>P : 加载权限load P->>S : 并发请求模块/授权数据 S-->>P : 返回数据 P-->>G : 权限就绪 G->>G : 校验 moduleId 访问权限 alt 无权限 G-->>R : 重定向到 /dashboard else 有权限 G-->>R : 放行 end 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/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/stores/permissions.js:1-196](file://fms-vue/src/stores/permissions.js#L1-L196) ### 状态管理(stores) - app.js - 状态:深色模式、主色、侧边栏折叠、语言、已访问页签、路由刷新计数。 - 计算属性:cachedViews 用于 keep-alive include。 - 动作:页签增删改、批量关闭、刷新计数管理。 - 持久化:仅持久化主题与语言。 - 主题同步:initThemeSync 在 store 激活后立即建立 watch,将状态同步到 DOM。 - auth.js - 会话信息(token、用户)持久化到 sessionStorage;用户信息(机构码、账号)持久化到 localStorage。 - 提供 setSession、clearSession、saveLastLogin 等方法。 - permissions.js - 加载权限:管理员直接加载全部启用模块;普通用户并发拉取模块、用户模块、用户功能点、功能点映射。 - 去重与并发控制:同一主体重复调用共享请求;版本号防止竞态覆盖。 - 权限判断:canAccess 按模块编码向上查找 page 类型模块 ID 并判断授权;canPower 判断功能点权限。 - 监听变化:当认证主体变化时清空权限缓存。 ```mermaid classDiagram class AppStore { +darkMode +primaryColor +sidebarCollapsed +locale +visitedTabs +routeRefreshKeys +setDark(value) +toggleDark() +setPrimary(color) +toggleSidebar() +addVisitedTab(tab) +removeVisitedTab(key) +closeOthers(key) +closeLeft(key) +closeRight(key) +closeAll() +refreshRoute(key) +getRouteRefreshKey(key) } class AuthStore { +loginInfo +userInfo +isAuthenticated +setSession(session, organizationId) +clearSession() +saveLastLogin(orgId, account) } class PermissionStore { +load() +canAccess(moduleCode) +canPower(scopeCode, powerCode) +clear() +modules +grantedPageIds +moduleCodes +powerCodes +isG3soft } AppStore --> PermissionStore : "布局/页签使用" PermissionStore --> AuthStore : "读取认证主体" ``` 图表来源 - [fms-vue/src/stores/app.js:6-123](file://fms-vue/src/stores/app.js#L6-L123) - [fms-vue/src/stores/auth.js:4-49](file://fms-vue/src/stores/auth.js#L4-L49) - [fms-vue/src/stores/permissions.js:13-196](file://fms-vue/src/stores/permissions.js#L13-L196) 章节来源 - [fms-vue/src/stores/app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123) - [fms-vue/src/stores/auth.js:1-49](file://fms-vue/src/stores/auth.js#L1-L49) - [fms-vue/src/stores/permissions.js:1-196](file://fms-vue/src/stores/permissions.js#L1-L196) ### 布局与页签缓存(DefaultLayout.vue) - 布局结构 - 使用 Splitter 分割侧栏与主区,支持拖拽调整宽度。 - 主区包含顶栏、页签与内容区。 - 页签缓存策略 - keep-alive 的 include 来自 app store 的 cachedViews。 - 外层 的 key = route.fullPath 保证稳定缓存键。 - 详情页使用 cacheKeyOf(fullPath) 生成唯一命名,不同参数独立缓存。 - 内层 的 key 附加刷新计数,双击页签可强制重新挂载。 - 资源回收 - 监听 visitedTabs 变化,回收已关闭详情页的包装组件定义,避免内存增长。 ```mermaid flowchart TD Enter["进入页面"] --> Wrap["生成或复用页面包装组件"] Wrap --> CacheKey{"是否详情页?"} CacheKey -- 是 --> DetailWrap["使用 cacheKeyOf(fullPath) 命名"] CacheKey -- 否 --> NormalWrap["使用 componentName 命名"] DetailWrap --> KA["keep-alive include"] NormalWrap --> KA KA --> Render["渲染页面组件"] Render --> Refresh{"是否刷新页签?"} Refresh -- 是 --> ReMount["增加刷新计数,重新挂载内层组件"] Refresh -- 否 --> Idle["保持缓存"] ``` 图表来源 - [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/layouts/DefaultLayout.vue:1-200](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L200) ### 主题与样式 - 主题切换 - applyDarkMode 切换 html 的 dark class。 - applyPrimaryColor 写入 CSS 变量 --fms-primary。 - 首屏主题一致性 - index.html 在加载前读取持久化的主题偏好,提前写入 boot-dark 类,避免浅色闪屏。 - 主题同步 - main.js 在挂载前调用 initThemeSync,确保首次渲染即应用用户偏好。 章节来源 - [fms-vue/src/theme/index.js:1-16](file://fms-vue/src/theme/index.js#L1-L16) - [fms-vue/index.html:8-19](file://fms-vue/index.html#L8-L19) - [fms-vue/src/stores/app.js:104-123](file://fms-vue/src/stores/app.js#L104-L123) ### HTTP 与服务端交互 - 请求拦截 - 自动注入 Authorization 头(Bearer token)。 - 响应拦截 - 统一处理 ApiResult 结构,成功透传,失败抛出带 code/message/data 的错误对象。 - 鉴权失败(401/40101)清除会话并跳转登录页。 - 网络异常或服务端 5xx 弹出全局 Message 提示,并标记 toasts 去重。 - 超时与 baseURL - 默认 baseURL 优先使用环境变量,否则回退到 /api;超时 15s。 章节来源 - [fms-vue/src/services/http.js:1-111](file://fms-vue/src/services/http.js#L1-L111) ## 依赖关系分析 - 入口依赖 - index.html 引入 main.js,main.js 依赖 Vue、Pinia、Router、主题与应用状态。 - 路由依赖 - 路由守卫依赖认证与权限 store,权限 store 依赖 HTTP 服务。 - 布局依赖 - 布局依赖 Splitter 组件与页签组件,依赖 app store 管理缓存与刷新。 - 主题依赖 - 主题函数被 app store 的 watch 驱动,影响全局 CSS 变量与 dark class。 ```mermaid graph LR HTML["index.html"] --> MAIN["main.js"] MAIN --> APP["App.vue"] MAIN --> ROUTER["router/index.js"] MAIN --> PINIA["stores/*"] ROUTER --> AUTH["stores/auth.js"] ROUTER --> PERM["stores/permissions.js"] PERM --> HTTP["services/http.js"] APP --> LAYOUT["layouts/DefaultLayout.vue"] LAYOUT --> THEME["theme/index.js"] ``` 图表来源 - [fms-vue/index.html:1-295](file://fms-vue/index.html#L1-L295) - [fms-vue/src/main.js:1-78](file://fms-vue/src/main.js#L1-L78) - [fms-vue/src/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/stores/permissions.js:1-196](file://fms-vue/src/stores/permissions.js#L1-L196) - [fms-vue/src/services/http.js:1-111](file://fms-vue/src/services/http.js#L1-L111) - [fms-vue/src/layouts/DefaultLayout.vue:1-200](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L200) - [fms-vue/src/theme/index.js:1-16](file://fms-vue/src/theme/index.js#L1-L16) 章节来源 - [fms-vue/src/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/stores/permissions.js:1-196](file://fms-vue/src/stores/permissions.js#L1-L196) - [fms-vue/src/services/http.js:1-111](file://fms-vue/src/services/http.js#L1-L111) ## 性能与构建优化 - 开发服务器 - 主机绑定 true,端口 5082,严格端口。 - 代理 /api 到后端 http://127.0.0.1:8088,跨域问题通过 changeOrigin 解决。 - 构建优化 - 路径别名 @ 指向 src,简化导入。 - 仅引入 vue 插件,保持轻量。 - 运行时优化 - 路由懒加载:页面组件通过动态 import 按需加载。 - 页签缓存:keep-alive 限制最大实例数,关闭页签即销毁缓存。 - 主题同步:在挂载前应用主题,避免首屏闪烁。 - 进度条:NProgress 仅在路由切换时显示,提升感知性能。 章节来源 - [fms-vue/vite.config.js:1-25](file://fms-vue/vite.config.js#L1-L25) - [fms-vue/src/router/index.js:1-140](file://fms-vue/src/router/index.js#L1-L140) - [fms-vue/src/layouts/DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131) - [fms-vue/src/main.js:21-78](file://fms-vue/src/main.js#L21-L78) ## 故障排查指南 - 启动失败 - 现象:加载屏显示错误信息与重试按钮。 - 原因:路由未就绪或权限加载异常。 - 处理:检查网络连接、后端服务可用性;点击重试按钮重新加载。 - 模块加载失败(file:// 打开) - 现象:HTML 中全局 error 监听显示“加载失败”。 - 原因:直接以 file:// 协议打开 HTML,无法加载模块。 - 处理:通过本地开发服务器访问(npm run dev)。 - 鉴权失败 - 现象:接口返回 401/40101,自动跳转登录页。 - 原因:token 过期或被替换。 - 处理:重新登录;检查后端会话策略。 - 权限不足 - 现象:访问受限路由跳转到工作台。 - 原因:用户无对应模块或功能点权限。 - 处理:联系管理员分配权限;确认路由 meta.moduleId 配置正确。 - 页签缓存异常 - 现象:页面状态未重置或缓存泄漏。 - 原因:刷新计数未更新或包装组件未回收。 - 处理:双击页签触发刷新;检查 visitedTabs 与 pageWrapperCache 清理逻辑。 章节来源 - [fms-vue/src/main.js:24-78](file://fms-vue/src/main.js#L24-L78) - [fms-vue/index.html:250-263](file://fms-vue/index.html#L250-L263) - [fms-vue/src/services/http.js:19-38](file://fms-vue/src/services/http.js#L19-L38) - [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 前端应用以清晰的入口启动流程、完善的路由与权限体系、稳健的状态管理与主题同步机制为核心,结合 Vite 的开发体验与按需加载策略,提供了良好的用户体验与可维护性。布局层的页签缓存与资源回收策略进一步提升了性能与稳定性。建议在实际开发中遵循现有目录组织规范,合理划分功能域,持续优化首屏性能与错误处理体验。