Files
workspace/code/fms/.qoder/repowiki/zh/content/前端架构/应用结构.md
T
2026-08-23 21:02:41 +08:00

20 KiB
Raw Blame History

应用结构

**本文引用的文件** - [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:脚本命令与依赖声明。
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 拦截器"]

图表来源

章节来源

核心组件

  • 应用入口 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、鉴权失败跳转登录、网络/服务端错误提示。

章节来源

架构总览

下图展示了从 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 : 淡出加载屏

图表来源

详细组件分析

应用启动流程(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 : "读取认证主体"

图表来源

章节来源

布局与页签缓存(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"]

图表来源

章节来源

性能与构建优化

  • 开发服务器
  • 构建优化
    • 路径别名 @ 指向 src,简化导入。
    • 仅引入 vue 插件,保持轻量。
  • 运行时优化
    • 路由懒加载:页面组件通过动态 import 按需加载。
    • 页签缓存:keep-alive 限制最大实例数,关闭页签即销毁缓存。
    • 主题同步:在挂载前应用主题,避免首屏闪烁。
    • 进度条:NProgress 仅在路由切换时显示,提升感知性能。

章节来源

故障排查指南

  • 启动失败
    • 现象:加载屏显示错误信息与重试按钮。
    • 原因:路由未就绪或权限加载异常。
    • 处理:检查网络连接、后端服务可用性;点击重试按钮重新加载。
  • 模块加载失败(file:// 打开)
    • 现象:HTML 中全局 error 监听显示“加载失败”。
    • 原因:直接以 file:// 协议打开 HTML,无法加载模块。
    • 处理:通过本地开发服务器访问(npm run dev)。
  • 鉴权失败
    • 现象:接口返回 401/40101,自动跳转登录页。
    • 原因:token 过期或被替换。
    • 处理:重新登录;检查后端会话策略。
  • 权限不足
    • 现象:访问受限路由跳转到工作台。
    • 原因:用户无对应模块或功能点权限。
    • 处理:联系管理员分配权限;确认路由 meta.moduleId 配置正确。
  • 页签缓存异常
    • 现象:页面状态未重置或缓存泄漏。
    • 原因:刷新计数未更新或包装组件未回收。
    • 处理:双击页签触发刷新;检查 visitedTabs 与 pageWrapperCache 清理逻辑。

章节来源

结论

FMS 前端应用以清晰的入口启动流程、完善的路由与权限体系、稳健的状态管理与主题同步机制为核心,结合 Vite 的开发体验与按需加载策略,提供了良好的用户体验与可维护性。布局层的页签缓存与资源回收策略进一步提升了性能与稳定性。建议在实际开发中遵循现有目录组织规范,合理划分功能域,持续优化首屏性能与错误处理体验。