20 KiB
20 KiB
应用结构
**本文引用的文件** - [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)目录
简介
本文件面向 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:脚本命令与依赖声明。
graph TB
A["index.html<br/>HTML 入口"] --> B["src/main.js<br/>应用启动"]
B --> C["src/App.vue<br/>根组件"]
B --> D["src/router/index.js<br/>路由与守卫"]
B --> E["src/stores/*<br/>Pinia 状态"]
B --> F["src/theme/index.js<br/>主题同步"]
D --> G["src/layouts/DefaultLayout.vue<br/>布局壳"]
G --> H["src/views/*<br/>页面组件"]
E --> I["src/services/http.js<br/>HTTP 拦截器"]
图表来源
- fms-vue/index.html:1-295
- fms-vue/src/main.js:1-78
- fms-vue/src/App.vue:1-15
- fms-vue/src/router/index.js:1-140
- fms-vue/src/layouts/DefaultLayout.vue:1-200
- fms-vue/src/theme/index.js:1-16
- fms-vue/src/services/http.js:1-111
章节来源
核心组件
-
应用入口 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
- fms-vue/src/App.vue:1-15
- fms-vue/src/router/index.js:1-140
- fms-vue/src/stores/app.js:1-123
- fms-vue/src/stores/auth.js:1-49
- fms-vue/src/stores/permissions.js:1-196
- fms-vue/src/layouts/DefaultLayout.vue:1-200
- fms-vue/src/theme/index.js:1-16
- fms-vue/src/services/http.js:1-111
架构总览
下图展示了从 HTML 入口到应用挂载、路由与状态管理的整体流程。
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
- fms-vue/src/main.js:1-78
- fms-vue/src/App.vue:1-15
- fms-vue/src/layouts/DefaultLayout.vue:1-200
详细组件分析
应用启动流程(main.js)
- 初始化顺序
- 创建 Pinia 并启用 persistedstate 插件。
- 注册 stk-table 的区域选择特性。
- 创建 Vue 应用,安装 Pinia 与 Router。
- 在 mount 前调用 initThemeSync,恢复持久化主题并写入 DOM,避免首屏闪烁。
- 挂载时机与加载屏
- 等待 router.isReady() 确保路由守卫完成(含权限加载)。
- 将加载屏移动到 body 下,避免被 #app 替换导致闪烁。
- 挂载应用后,下一帧添加隐藏类触发淡出过渡,并在结束后清理节点与临时主题类。
- 错误处理
- 捕获启动期异常,切换加载屏为错误态,显示提示信息与重试按钮。
flowchart TD
Start(["启动 bootstrap"]) --> WaitRoute["等待路由就绪"]
WaitRoute --> MoveBoot["移动加载屏到 body"]
MoveBoot --> Mount["挂载应用到 #app"]
Mount --> FadeOut["下一帧淡出加载屏"]
FadeOut --> Cleanup["过渡结束后清理加载屏"]
WaitRoute --> |异常| ShowError["显示错误态与重试"]
图表来源
章节来源
根组件 App.vue
- 职责
- 作为应用根容器,仅渲染当前路由视图。
- 开发环境下异步引入 FPS 监控浮层,生产环境不打包,减少体积。
- 生命周期
- 无额外逻辑,完全交由路由与布局管理页面生命周期。
章节来源
路由与权限(router/index.js)
- 路由定义
- 登录页、工作台、测试页、演示页、附件演示、详情页、模块管理等。
- 详情页支持按参数独立缓存(cacheByKey),结合布局层的命名包装组件实现 per-param 缓存。
- 全局守卫
- beforeEach:启动 NProgress;未登录访问受保护路由跳转登录;已登录访问登录页重定向到工作台。
- 权限加载:对需要权限的路由,统一在守卫中调用权限 store 的 load,确保菜单一次性到位。
- 权限校验:若路由 meta 指定 moduleId,则检查 canAccess,无权限跳转工作台。
- 结束钩子
- afterEach:关闭 NProgress。
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
图表来源
章节来源
状态管理(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 判断功能点权限。
- 监听变化:当认证主体变化时清空权限缓存。
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
- fms-vue/src/stores/auth.js:4-49
- fms-vue/src/stores/permissions.js:13-196
章节来源
- fms-vue/src/stores/app.js:1-123
- fms-vue/src/stores/auth.js:1-49
- fms-vue/src/stores/permissions.js:1-196
布局与页签缓存(DefaultLayout.vue)
- 布局结构
- 使用 Splitter 分割侧栏与主区,支持拖拽调整宽度。
- 主区包含顶栏、页签与内容区。
- 页签缓存策略
- keep-alive 的 include 来自 app store 的 cachedViews。
- 外层 的 key = route.fullPath 保证稳定缓存键。
- 详情页使用 cacheKeyOf(fullPath) 生成唯一命名,不同参数独立缓存。
- 内层 的 key 附加刷新计数,双击页签可强制重新挂载。
- 资源回收
- 监听 visitedTabs 变化,回收已关闭详情页的包装组件定义,避免内存增长。
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["保持缓存"]
图表来源
章节来源
主题与样式
- 主题切换
- applyDarkMode 切换 html 的 dark class。
- applyPrimaryColor 写入 CSS 变量 --fms-primary。
- 首屏主题一致性
- index.html 在加载前读取持久化的主题偏好,提前写入 boot-dark 类,避免浅色闪屏。
- 主题同步
- main.js 在挂载前调用 initThemeSync,确保首次渲染即应用用户偏好。
章节来源
HTTP 与服务端交互
- 请求拦截
- 自动注入 Authorization 头(Bearer token)。
- 响应拦截
- 统一处理 ApiResult 结构,成功透传,失败抛出带 code/message/data 的错误对象。
- 鉴权失败(401/40101)清除会话并跳转登录页。
- 网络异常或服务端 5xx 弹出全局 Message 提示,并标记 toasts 去重。
- 超时与 baseURL
- 默认 baseURL 优先使用环境变量,否则回退到 /api;超时 15s。
章节来源
依赖关系分析
- 入口依赖
- index.html 引入 main.js,main.js 依赖 Vue、Pinia、Router、主题与应用状态。
- 路由依赖
- 路由守卫依赖认证与权限 store,权限 store 依赖 HTTP 服务。
- 布局依赖
- 布局依赖 Splitter 组件与页签组件,依赖 app store 管理缓存与刷新。
- 主题依赖
- 主题函数被 app store 的 watch 驱动,影响全局 CSS 变量与 dark class。
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
- fms-vue/src/main.js:1-78
- fms-vue/src/router/index.js:1-140
- fms-vue/src/stores/permissions.js:1-196
- fms-vue/src/services/http.js:1-111
- fms-vue/src/layouts/DefaultLayout.vue:1-200
- fms-vue/src/theme/index.js:1-16
章节来源
- fms-vue/src/router/index.js:1-140
- fms-vue/src/stores/permissions.js:1-196
- fms-vue/src/services/http.js:1-111
性能与构建优化
- 开发服务器
- 主机绑定 true,端口 5082,严格端口。
- 代理 /api 到后端 http://127.0.0.1:8088,跨域问题通过 changeOrigin 解决。
- 构建优化
- 路径别名 @ 指向 src,简化导入。
- 仅引入 vue 插件,保持轻量。
- 运行时优化
- 路由懒加载:页面组件通过动态 import 按需加载。
- 页签缓存:keep-alive 限制最大实例数,关闭页签即销毁缓存。
- 主题同步:在挂载前应用主题,避免首屏闪烁。
- 进度条:NProgress 仅在路由切换时显示,提升感知性能。
章节来源
- fms-vue/vite.config.js:1-25
- fms-vue/src/router/index.js:1-140
- fms-vue/src/layouts/DefaultLayout.vue:112-131
- fms-vue/src/main.js:21-78
故障排查指南
- 启动失败
- 现象:加载屏显示错误信息与重试按钮。
- 原因:路由未就绪或权限加载异常。
- 处理:检查网络连接、后端服务可用性;点击重试按钮重新加载。
- 模块加载失败(file:// 打开)
- 现象:HTML 中全局 error 监听显示“加载失败”。
- 原因:直接以 file:// 协议打开 HTML,无法加载模块。
- 处理:通过本地开发服务器访问(npm run dev)。
- 鉴权失败
- 现象:接口返回 401/40101,自动跳转登录页。
- 原因:token 过期或被替换。
- 处理:重新登录;检查后端会话策略。
- 权限不足
- 现象:访问受限路由跳转到工作台。
- 原因:用户无对应模块或功能点权限。
- 处理:联系管理员分配权限;确认路由 meta.moduleId 配置正确。
- 页签缓存异常
- 现象:页面状态未重置或缓存泄漏。
- 原因:刷新计数未更新或包装组件未回收。
- 处理:双击页签触发刷新;检查 visitedTabs 与 pageWrapperCache 清理逻辑。
章节来源
- fms-vue/src/main.js:24-78
- fms-vue/index.html:250-263
- fms-vue/src/services/http.js:19-38
- fms-vue/src/router/index.js:107-137
- fms-vue/src/layouts/DefaultLayout.vue:16-58
结论
FMS 前端应用以清晰的入口启动流程、完善的路由与权限体系、稳健的状态管理与主题同步机制为核心,结合 Vite 的开发体验与按需加载策略,提供了良好的用户体验与可维护性。布局层的页签缓存与资源回收策略进一步提升了性能与稳定性。建议在实际开发中遵循现有目录组织规范,合理划分功能域,持续优化首屏性能与错误处理体验。