# Vue Composition API开发
**本文引用的文件**
- [frontend/src/main.js](file://frontend/src/main.js)
- [frontend/src/App.vue](file://frontend/src/App.vue)
- [frontend/package.json](file://frontend/package.json)
- [frontend/vite.config.js](file://frontend/vite.config.js)
- [frontend/src/router/index.js](file://frontend/src/router/index.js)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js)
- [frontend/src/api/index.js](file://frontend/src/api/index.js)
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue)
- [frontend/src/views/Layout.vue](file://frontend/src/views/Layout.vue)
- [frontend/src/views/assessment/Assessments.vue](file://frontend/src/views/assessment/Assessments.vue)
- [frontend/src/views/basic/Departments.vue](file://frontend/src/views/basic/Departments.vue)
## 目录
1. [引言](#引言)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖分析](#依赖分析)
7. [性能考虑](#性能考虑)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 引言
本指南面向使用 Vue 3 Composition API 的开发者,围绕 setup 函数、响应式数据 ref 与 reactive 的区别与场景、计算属性 computed、监听器 watch 与 watchEffect、生命周期钩子(如 onMounted、onUnmounted 等)、组合函数(composables)的设计与复用、以及从 Options API 迁移到 Composition API 的最佳实践展开。文档结合仓库中的真实组件与状态管理,提供可操作的建议与图示。
## 项目结构
前端采用 Vite + Vue 3 + Pinia + Element Plus 技术栈,路由基于 vue-router。项目入口在 main.js 中创建应用实例并挂载;App.vue 作为根组件包裹路由视图;views 下按功能模块组织页面;stores 提供全局状态;router 定义导航与守卫;api 汇聚接口模块。
```mermaid
graph TB
A["main.js
创建应用实例"] --> B["App.vue
根组件"]
B --> C["router/index.js
路由与守卫"]
C --> D["views/*
页面组件"]
A --> E["stores/*
Pinia Store"]
A --> F["api/*
接口聚合"]
A --> G["Element Plus
UI库"]
A --> H["Vite 配置
代理/别名"]
```
图表来源
- [frontend/src/main.js](file://frontend/src/main.js#L1-L24)
- [frontend/src/App.vue](file://frontend/src/App.vue#L1-L17)
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L1-L116)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
- [frontend/src/api/index.js](file://frontend/src/api/index.js#L1-L9)
- [frontend/vite.config.js](file://frontend/vite.config.js#L1-L22)
章节来源
- [frontend/src/main.js](file://frontend/src/main.js#L1-L24)
- [frontend/src/App.vue](file://frontend/src/App.vue#L1-L17)
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L1-L116)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
- [frontend/src/api/index.js](file://frontend/src/api/index.js#L1-L9)
- [frontend/vite.config.js](file://frontend/vite.config.js#L1-L22)
## 核心组件
- 应用入口与插件注册:在 main.js 中创建应用、注册 Element Plus、Pinia、路由,并挂载到 DOM。
- 根组件 App.vue:提供语言环境配置,承载路由视图。
- 路由与守卫:router/index.js 定义多级菜单路由与登录守卫。
- 全局状态:stores/app.js 与 stores/user.js 使用组合式 Store(defineStore 返回函数式 Store),内部使用 ref 管理响应式状态。
- 页面组件:views 下包含 Dashboard、Layout、Assessments、Departments 等,广泛使用 setup 函数、ref、reactive、computed、watch/watchEffect、生命周期钩子与 API 调用。
章节来源
- [frontend/src/main.js](file://frontend/src/main.js#L1-L24)
- [frontend/src/App.vue](file://frontend/src/App.vue#L1-L17)
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L1-L116)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
## 架构总览
下图展示从入口到页面组件的数据与控制流:main.js 初始化应用与插件;App.vue 渲染路由视图;Layout.vue 作为布局容器,调用 stores 与 API;具体业务页面(如 Dashboard、Assessments、Departments)在 setup 中声明响应式数据、计算属性、监听器与生命周期钩子,并发起 API 请求。
```mermaid
sequenceDiagram
participant M as "main.js"
participant APP as "App.vue"
participant R as "router/index.js"
participant L as "Layout.vue"
participant P as "页面组件(Dashboard/Assessments/Departments)"
participant S as "stores/*"
participant API as "api/*"
M->>APP : 创建并挂载应用
APP->>R : 注入路由
R-->>L : 渲染布局
L->>S : 读取/写入全局状态
L->>API : 加载菜单树
P->>S : 访问全局状态
P->>API : 发起业务请求
API-->>P : 返回数据
P-->>L : 渲染视图
```
图表来源
- [frontend/src/main.js](file://frontend/src/main.js#L1-L24)
- [frontend/src/App.vue](file://frontend/src/App.vue#L1-L17)
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L1-L116)
- [frontend/src/views/Layout.vue](file://frontend/src/views/Layout.vue#L1-L241)
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L1-L1082)
- [frontend/src/views/assessment/Assessments.vue](file://frontend/src/views/assessment/Assessments.vue#L1-L311)
- [frontend/src/views/basic/Departments.vue](file://frontend/src/views/basic/Departments.vue#L1-L290)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
- [frontend/src/api/index.js](file://frontend/src/api/index.js#L1-L9)
## 详细组件分析
### setup 函数与响应式数据:ref vs reactive
- 在页面组件中,setup 函数用于声明响应式数据与逻辑。常见模式:
- 使用 ref 声明基本类型或简单对象,如字符串、数字、布尔值、DOM 引用等。
- 使用 reactive 声明复杂对象(如表单、分页参数),便于在模板中直接访问其属性。
- 示例组件中的使用:
- Dashboard.vue:大量使用 ref(如图表容器引用、周期选择、统计数据对象)与 reactive(如搜索条件、分页参数)。
- Assessments.vue:同时使用 ref(loading、对话框可见性)与 reactive(搜索表单、分页、批量创建表单)。
- Departments.vue:同样混合使用 ref 与 reactive。
- 设计建议:
- 对于简单标量或需要重新赋值的对象,优先使用 ref。
- 对于复杂对象且不需要整体替换,优先使用 reactive。
- 在模板中频繁访问深层属性时,reactive 更直观;需要整体替换或跨函数传递引用时,ref 更合适。
章节来源
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L225-L421)
- [frontend/src/views/assessment/Assessments.vue](file://frontend/src/views/assessment/Assessments.vue#L116-L293)
- [frontend/src/views/basic/Departments.vue](file://frontend/src/views/basic/Departments.vue#L103-L272)
### 计算属性 computed
- computed 用于派生状态,自动追踪依赖并在依赖变化时重新计算。
- 示例:
- Dashboard.vue 中的 assessedRate(完成率)与 hasAlerts(是否存在预警)均通过 computed 声明,避免在模板中重复计算。
- 最佳实践:
- 仅依赖响应式数据;避免在 getter 内部修改状态。
- 复杂计算尽量拆分为多个小的 computed,提升可维护性。
章节来源
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L306-L315)
### 监听器 watch 与 watchEffect
- watch:监听特定响应式数据的变化,适合需要在变化时执行副作用(如发起请求、更新图表)。
- 示例:Dashboard.vue 中对周期选择(chartPeriod、financePeriod、rankingPeriod)的监听,触发对应数据加载与图表更新。
- watchEffect:自动追踪其执行期间访问到的响应式数据,无需显式声明监听目标。
- 示例:Dashboard.vue 中在加载关键指标后使用 nextTick,再更新仪表盘图表,体现了 watchEffect 的自动依赖追踪特性。
- 最佳实践:
- watch 适用于明确的目标与旧值/新值对比;watchEffect 适用于“只要相关数据变化就刷新”的场景。
- 注意清理副作用(如在组件卸载时取消订阅或清空定时器)。
章节来源
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L362-L421)
### 生命周期钩子:onMounted、onUnmounted 等
- onMounted:在组件挂载后执行初始化逻辑,如加载菜单、初始化图表、绑定窗口 resize 事件等。
- Layout.vue:在 onMounted 中加载菜单树。
- Dashboard.vue:在 onMounted 中初始化多个 ECharts 实例并绑定 resize 事件。
- onUnmounted:用于清理副作用(如移除事件监听、销毁图表实例)。
- Dashboard.vue:在组件卸载时应移除 window.resize 监听与图表实例,避免内存泄漏。
- 最佳实践:
- 将副作用集中在 onMounted 中启动,在 onUnmounted 中统一清理。
- 对于需要等待 DOM 更新后再执行的逻辑,配合 nextTick 使用。
章节来源
- [frontend/src/views/Layout.vue](file://frontend/src/views/Layout.vue#L122-L124)
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L423-L448)
### 组合函数(Composables)设计与复用
- 当前项目中,全局状态通过 Pinia 的组合式 Store(函数式 Store)实现,内部使用 ref 管理状态,对外暴露方法与状态。
- app.js:封装侧边栏折叠状态与科室树加载。
- user.js:封装登录、登出、用户信息获取。
- 设计模式建议:
- 将可复用的逻辑(如图表初始化、分页加载、表单校验)抽象为独立的 Composable,返回 ref 或 reactive 数据与方法。
- 在组件中通过 import 使用这些 Composable,保持 setup 的简洁与关注点分离。
- 状态共享:
- 使用 Pinia Store 在组件间共享状态;对于轻量状态,也可通过 props/$emit 或 provide/inject 实现。
章节来源
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
### 从 Options API 迁移到 Composition API 的最佳实践
- 迁移步骤建议:
- 将 data 中的每个字段改为 setup 中的 ref 或 reactive。
- 将 methods 中的方法迁移到 setup 中的普通函数;将计算属性迁移到 computed。
- 将生命周期钩子迁移到 onMounted/onUpdated 等。
- 将 watch 改为 watch 或 watchEffect。
- 将混入(mixins)替换为组合函数(Composable)。
- 项目中的迁移体现:
- 各页面组件(Dashboard、Assessments、Departments)已全面使用 setup 函数与组合式 API。
- 菜单加载与图表初始化等逻辑集中在 setup 中,便于测试与复用。
- 注意事项:
- 避免在 setup 中进行过多同步阻塞操作;异步请求放在 onMounted 中。
- 对于需要在模板中直接使用的函数,保持箭头函数或普通函数形式,确保 this 不丢失(在 setup 中不会出现 this)。
章节来源
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L225-L421)
- [frontend/src/views/assessment/Assessments.vue](file://frontend/src/views/assessment/Assessments.vue#L116-L293)
- [frontend/src/views/basic/Departments.vue](file://frontend/src/views/basic/Departments.vue#L103-L272)
### 组件重构示例:Dashboard(从 Options API 到 Composition API)
- 重构要点:
- 将 data 中的统计、排名、趋势、图表容器等状态迁移为 ref/refs 与 reactive。
- 将 methods 中的加载函数(如 loadStats、loadRanking、loadTrendData 等)迁移为普通函数。
- 将 computed 中的 assessedRate、hasAlerts 迁移为 computed。
- 将 mounted 中的图表初始化与事件绑定迁移至 onMounted,并在 onUnmounted 中清理。
- 将 watch 中的周期切换逻辑迁移为 watch 或 watchEffect。
- 重构收益:
- setup 中逻辑集中,便于阅读与测试。
- 计算属性与监听器自动追踪依赖,减少手动维护。
- 与组合函数(Composable)结合,进一步提升复用性。
章节来源
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L225-L421)
## 依赖分析
- 依赖关系概览:
- main.js 依赖 Vue、Pinia、Element Plus、路由与样式。
- App.vue 依赖 Element Plus 国际化配置。
- router/index.js 依赖路由与守卫。
- stores/* 依赖 Pinia 与 API。
- views/* 依赖 Element Plus、ECharts、API 与 stores。
- 外部依赖版本参考:
- Vue 3、Pinia、Element Plus、vue-router、axios、echarts、dayjs 等。
```mermaid
graph LR
MJS["main.js"] --> V["vue"]
MJS --> P["pinia"]
MJS --> EP["element-plus"]
MJS --> VR["vue-router"]
MJS --> AX["axios"]
MJS --> SCSS["sass"]
MJS --> ECH["echarts"]
APP["App.vue"] --> EP
ROUTER["router/index.js"] --> VR
STORE_APP["stores/app.js"] --> P
STORE_USER["stores/user.js"] --> P
VIEWS["views/*"] --> EP
VIEWS --> ECH
VIEWS --> API["api/*"]
```
图表来源
- [frontend/src/main.js](file://frontend/src/main.js#L1-L24)
- [frontend/src/App.vue](file://frontend/src/App.vue#L1-L17)
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L1-L116)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L1-L31)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L1-L49)
- [frontend/src/api/index.js](file://frontend/src/api/index.js#L1-L9)
- [frontend/package.json](file://frontend/package.json#L11-L25)
章节来源
- [frontend/package.json](file://frontend/package.json#L11-L25)
## 性能考虑
- 图表渲染性能:
- 在 Dashboard 中,多个 ECharts 实例在 onMounted 中初始化,应在 onUnmounted 中销毁实例并移除 resize 监听,避免内存泄漏。
- 对于大数据量图表,建议在 watch 中按需更新选项,避免全量 setOption 导致重绘开销。
- 状态更新与重渲染:
- 使用 computed 缓存派生结果,减少不必要的重渲染。
- 将复杂计算拆分为多个小的 computed,降低依赖范围。
- API 请求节流:
- 对于高频变更(如日期选择、筛选条件),可在 watch 中加入防抖策略,减少请求次数。
- 路由懒加载:
- 路由组件已使用动态导入,有助于首屏加载性能。
## 故障排查指南
- 登录与路由守卫:
- 若进入受保护页面被重定向到登录页,检查本地存储 token 是否存在;路由守卫会根据 token 决定是否放行。
- 图表不显示或空白:
- 确认 onMounted 中已初始化图表实例并在数据就绪后 setOption;若使用 nextTick,请确保在数据更新后再执行更新逻辑。
- 菜单加载失败:
- Layout.vue 中的菜单加载失败会回退到默认菜单;检查后端接口与网络请求状态。
- 状态不同步:
- Pinia Store 中的状态更新需通过 ref.value 修改;确保在组件中正确读取与写入。
章节来源
- [frontend/src/router/index.js](file://frontend/src/router/index.js#L104-L113)
- [frontend/src/views/Layout.vue](file://frontend/src/views/Layout.vue#L86-L116)
- [frontend/src/views/Dashboard.vue](file://frontend/src/views/Dashboard.vue#L423-L448)
- [frontend/src/stores/app.js](file://frontend/src/stores/app.js#L14-L22)
- [frontend/src/stores/user.js](file://frontend/src/stores/user.js#L34-L39)
## 结论
本项目已全面采用 Vue 3 Composition API,结合 Pinia 实现状态管理,配合 Element Plus 与 ECharts 构建了功能完备的绩效管理系统前端。通过在 setup 中集中声明响应式数据、计算属性、监听器与生命周期钩子,提升了代码的可读性与可维护性。建议在后续开发中持续引入组合函数(Composable)以增强逻辑复用,并在图表与数据密集场景中优化渲染与请求策略,进一步提升用户体验与性能表现。
## 附录
- 开发与构建命令:
- dev:启动开发服务器
- build:打包生产资源
- preview:预览生产构建
- 代理配置:
- 前端开发时将 /api 代理到后端服务地址,便于联调。
章节来源
- [frontend/package.json](file://frontend/package.json#L6-L10)
- [frontend/vite.config.js](file://frontend/vite.config.js#L14-L19)