chore: migrate remaining ECharts components to Chart.js

- Migrated 4 components from ECharts to Chart.js:
  * MonthlyExpenseCard.vue (折线图)
  * DailyTrendChart.vue (双系列折线图)
  * ExpenseCategoryCard.vue (环形图)
  * BudgetChartAnalysis.vue (仪表盘 + 多种图表)

- Removed all ECharts imports and environment variable switches
- Unified all charts to use BaseChart.vue component
- Build verified: pnpm build success ✓
- No echarts imports remaining ✓

Refs: openspec/changes/migrate-remaining-echarts-to-chartjs
This commit is contained in:
SunCheng
2026-02-16 21:55:38 +08:00
parent a88556c784
commit 9921cd5fdf
77 changed files with 6964 additions and 1632 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-16

View File

@@ -0,0 +1,201 @@
## Context
**当前状态**
- 项目已建立 Chart.js 基础设施BaseChart.vue、useChartTheme、chartHelpers
- 4 个组件仍使用 ECharts API 或通过环境变量 `VITE_USE_CHARTJS` 双模式运行
- ECharts 依赖已从 package.json 移除,导致构建失败
**技术栈**
- Chart.js 4.5.1 + vue-chartjs 5.3.3
- Vue 3 Composition API + Vant UI
- 移动端优先设计
**约束**
- 必须保持图表视觉效果一致(颜色、布局、标签格式)
- 不能影响用户交互行为点击、hover、tooltip
- 需兼容暗色模式(通过 useChartTheme
## Goals / Non-Goals
**Goals:**
- 彻底移除 ECharts 代码和环境变量开关
- 统一使用 BaseChart.vue 包装组件
- 确保所有图表类型正确渲染:
- 仪表盘图表Gauge Chart→ Doughnut + 中心文本叠加
- 折线图Line Chart→ Chart.js Line
- 饼图/环形图Pie/Doughnut Chart→ Chart.js Doughnut
- 通过白盒和黑盒测试验证功能正确性
**Non-Goals:**
- 不重新设计图表样式或交互
- 不优化图表性能(除非迁移过程中发现明显问题)
- 不添加新的图表功能
- 不修改业务逻辑或数据处理代码
## Decisions
### 1. 仪表盘图表实现方案
**决策**:使用 Doughnut 图表 + CSS 绝对定位的中心文本覆盖层
**理由**
- Chart.js 无原生 Gauge 图表类型
- Doughnut 图表可以通过 `rotation``circumference` 配置实现半圆仪表盘效果
- 项目中 `BudgetChartAnalysis.vue` 已使用 ECharts Gauge需保持视觉一致性
**替代方案**
- ❌ 使用第三方插件(如 chartjs-gauge增加依赖复杂度
- ❌ 使用 Canvas 自绘:维护成本高,不符合项目标准
**实现细节**
```javascript
{
type: 'doughnut',
data: {
datasets: [{
data: [current, limit - current],
backgroundColor: [progressColor, '#f0f0f0'],
rotation: -90, // 从顶部开始
circumference: 180 // 半圆
}]
},
options: {
cutout: '70%', // 内环大小
plugins: {
legend: { display: false },
tooltip: { enabled: false }
}
}
}
```
### 2. 图表数据转换策略
**决策**:复用现有的 `prepareChartData()` 函数,仅修改图表配置部分
**理由**
- 所有组件都已有数据准备逻辑(`prepareChartData()`
- 数据格式labels + datasets在 ECharts 和 Chart.js 之间相似
- 减少代码改动,降低引入 bug 风险
**迁移模式**
```javascript
// 保留
const { xAxisLabels, expenseData, incomeData } = prepareChartData()
// 修改:从 ECharts option 转为 Chart.js data + options
const chartData = {
labels: xAxisLabels,
datasets: [{
label: '支出',
data: expenseData,
borderColor: colors.value.danger,
backgroundColor: 'rgba(238, 10, 36, 0.1)'
}]
}
const chartOptions = getChartOptions({
plugins: { legend: { display: false } }
})
```
### 3. 环境变量清理
**决策**:删除所有 `VITE_USE_CHARTJS` 相关的条件渲染和双分支代码
**理由**
- 项目已标准化使用 Chart.js无需保留回退选项
- 双模式代码增加维护成本和测试复杂度
- ECharts 依赖已移除,无法回退
**清理范围**
```vue
<!-- 删除 -->
<div v-if="!useChartJS" ref="chartRef" />
<div v-else><BaseChart ... /></div>
<!-- 改为 -->
<BaseChart ... />
```
### 4. 测试策略
**决策**:分层测试 = 单元测试(白盒)+ E2E 测试(黑盒)+ 视觉回归
**白盒测试Jest + Vue Test Utils**
- 测试组件能否正确挂载
- 测试 props 传入后 chartData 和 chartOptions 的正确性
- 测试事件发射(如 @chart:render
**黑盒测试Playwright**
- 测试图表在真实浏览器中的渲染
- 测试用户交互点击、hover、tooltip
- 测试暗色模式切换
**视觉回归测试**
- 截图对比迁移前后的图表外观
- 确保颜色、字体、布局一致
## Risks / Trade-offs
### Risk 1: 仪表盘视觉差异
**风险**Chart.js Doughnut 无法完美复现 ECharts Gauge 的细节(如指针动画)
**缓解**:接受静态圆弧进度条,使用颜色渐变和动画过渡弥补视觉效果
### Risk 2: 第三方样式冲突
**风险**Chart.js 的全局样式可能与 Vant UI 冲突
**缓解**:使用 scoped styles通过 `useChartTheme` 统一管理颜色变量
### Risk 3: 移动端性能
**风险**Chart.js 在低端移动设备上可能卡顿
**缓解**
- 使用 `chartHelpers.ts` 中的数据抽样功能
- 配置 `animation.duration` 为合理值750ms
- 监控 `prefers-reduced-motion` 媒体查询
### Risk 4: 无法回退
**风险**:迁移后如果发现严重问题,无法快速回退到 ECharts
**缓解**
- 迁移前创建 git tag
- 分组件逐步迁移,每个组件验证通过后再迁移下一个
- 保留完整的测试套件
## Migration Plan
### Phase 1: 单组件迁移(按复杂度排序)
1. **MonthlyExpenseCard.vue**(简单折线图)
2. **DailyTrendChart.vue**(双系列折线图)
3. **ExpenseCategoryCard.vue**(环形图 + 列表)
4. **BudgetChartAnalysis.vue**(仪表盘 + 复杂布局)
### Phase 2: 每个组件的迁移步骤
1. 备份原始 ECharts 代码(注释)
2. 替换为 BaseChart.vue + 数据转换
3. 运行单元测试
4. 本地浏览器验证Chrome + Firefox
5. 移除注释的 ECharts 代码
### Phase 3: 集成测试
1. 运行完整的 E2E 测试套件
2. 视觉回归测试(截图对比)
3. 性能测试Lighthouse
### Rollback Strategy
如果迁移失败:
1. `git revert` 到迁移前的 commit
2. 临时恢复 `echarts` 依赖:`pnpm add echarts`
3. 重新评估迁移方案
## Open Questions
1.**是否需要自定义 Chart.js 插件?**
答:仪表盘图表需要中心文本叠加层,但使用 CSS 实现,无需插件
2.**是否需要保留 ECharts 作为 devDependency**
答:不需要,项目已决定完全移除
3.**是否需要更新用户文档?**
答:图表功能对用户透明,无需更新文档
4.**是否需要通知后端团队?**
答:纯前端技术栈变更,无需通知

View File

@@ -0,0 +1,40 @@
## Why
项目构建失败4 个前端组件仍引用已移除的 `echarts` 依赖,导致 Vite 构建报错。项目已完成 Chart.js 技术栈标准化(见 AGENTS.md现需彻底清理残留的 ECharts 代码,确保所有图表组件使用统一的 Chart.js + vue-chartjs 实现。
## What Changes
- 将 4 个组件的图表实现从 ECharts API 迁移到 Chart.js API
- 使用项目已有的现代化图表基础设施:
- `BaseChart.vue` 通用图表组件
- `useChartTheme.ts` composable自动适配暗色模式
- `chartHelpers.ts` 工具函数
- 移除所有 `import * as echarts from 'echarts'` 语句
- 清理环境变量开关(如 `VITE_USE_CHARTJS`),统一使用 Chart.js
- 保持图表的视觉效果和交互行为不变
## Capabilities
### New Capabilities
- `chart-migration-patterns`: 从 ECharts 到 Chart.js 的迁移模式和最佳实践,涵盖仪表盘图表、折线图、饼图等常见图表类型的转换方法
### Modified Capabilities
无(不修改现有规范,仅实施技术栈迁移)
## Impact
**受影响的组件**4 个):
- `Web/src/components/Budget/BudgetChartAnalysis.vue` - 预算仪表盘图表
- `Web/src/views/statisticsV2/modules/DailyTrendChart.vue` - 日趋势折线图
- `Web/src/views/statisticsV2/modules/ExpenseCategoryCard.vue` - 支出分类饼图
- `Web/src/views/statisticsV2/modules/MonthlyExpenseCard.vue` - 月度支出折线图
**依赖**
- ✅ Chart.js 4.5.1 和 vue-chartjs 5.3.3 已安装
- ✅ 基础设施BaseChart、useChartTheme、chartHelpers已就绪
- ❌ 移除 echarts 相关代码后无回退路径
**测试策略**
- 白盒测试组件单元测试Jest + Vue Test Utils
- 黑盒测试浏览器端到端测试Playwright
- 视觉回归测试:截图对比确保图表外观一致

View File

@@ -0,0 +1,115 @@
## ADDED Requirements
### Requirement: 仪表盘图表迁移模式
组件 SHALL 使用 Chart.js Doughnut 图表实现仪表盘Gauge效果替代 ECharts Gauge 图表。
#### Scenario: 半圆仪表盘渲染
- **WHEN** 组件接收预算统计数据current, limit
- **THEN** 系统使用 Doughnut 图表渲染半圆进度条,配置 `rotation: -90``circumference: 180`
#### Scenario: 中心文本叠加显示
- **WHEN** 仪表盘图表渲染完成
- **THEN** 系统在图表中心显示余额/超支文本,使用 CSS 绝对定位的覆盖层
#### Scenario: 动态颜色切换
- **WHEN** 实际值超过预算限额
- **THEN** 进度条颜色切换为危险色(`var(--van-danger-color)`),中心文本显示"超支"
#### Scenario: 暗色模式适配
- **WHEN** 用户切换到暗色主题
- **THEN** 图表颜色自动适配,使用 `useChartTheme` composable 获取主题色
### Requirement: 折线图迁移模式
组件 SHALL 使用 Chart.js Line 图表实现趋势折线图,替代 ECharts Line 图表。
#### Scenario: 单系列折线图渲染
- **WHEN** 组件接收月度支出数据(日期 + 金额数组)
- **THEN** 系统渲染折线图X 轴为日期标签Y 轴为金额,使用渐变填充
#### Scenario: 双系列折线图渲染
- **WHEN** 组件接收收支数据(包含收入和支出两个系列)
- **THEN** 系统渲染两条折线,支出为红色,收入为绿色,支持独立的 hover 交互
#### Scenario: 空数据处理
- **WHEN** 图表数据为空或所有数据点为 0
- **THEN** 系统显示 `<van-empty>` 空状态组件,而非空白图表
#### Scenario: Tooltip 格式化
- **WHEN** 用户 hover 到数据点
- **THEN** Tooltip 显示"¥XXX.XX"格式的金额,使用 `callbacks.label` 自定义
### Requirement: 饼图/环形图迁移模式
组件 SHALL 使用 Chart.js Doughnut 图表实现分类统计环形图,替代 ECharts Pie 图表。
#### Scenario: 环形图渲染
- **WHEN** 组件接收分类统计数据(分类名称 + 金额数组)
- **THEN** 系统渲染环形图,每个分类使用不同颜色,配置 `cutout: '50%'`
#### Scenario: 分类颜色映射
- **WHEN** 分类数据包含预定义颜色
- **THEN** 图表使用 props 传入的颜色数组,确保与列表中的分类色块一致
#### Scenario: 小分类合并
- **WHEN** 分类数量超过 10 个
- **THEN** 系统使用 `mergeSmallCategories()` 工具函数,将占比小于 5% 的分类合并为"其他"
#### Scenario: 点击分类跳转
- **WHEN** 用户点击环形图扇区
- **THEN** 系统触发 `@category-click` 事件,传递分类名称和类型
### Requirement: BaseChart 组件统一使用
所有图表组件 SHALL 使用 `BaseChart.vue` 包装组件,而非直接使用 vue-chartjs 组件。
#### Scenario: BaseChart 组件使用
- **WHEN** 组件需要渲染图表
- **THEN** 使用 `<BaseChart type="line|bar|doughnut" :data="chartData" :options="chartOptions" />`
#### Scenario: Loading 状态处理
- **WHEN** 图表数据加载中
- **THEN** BaseChart 显示 `<van-loading>` 组件
#### Scenario: 图表渲染回调
- **WHEN** 图表渲染完成
- **THEN** BaseChart 触发 `@chart:render` 事件,传递 Chart.js 实例引用
### Requirement: ECharts 代码完全移除
组件 SHALL 移除所有 ECharts 相关代码,包括导入语句、实例变量、环境变量判断。
#### Scenario: 移除 ECharts 导入
- **WHEN** 迁移组件
- **THEN** 删除 `import * as echarts from 'echarts'` 语句
#### Scenario: 移除环境变量开关
- **WHEN** 迁移组件
- **THEN** 删除 `const useChartJS = import.meta.env.VITE_USE_CHARTJS === 'true'` 和相关的 `v-if`/`v-else` 条件渲染
#### Scenario: 移除 ECharts 实例管理
- **WHEN** 迁移组件
- **THEN** 删除 `let chartInstance = null``echarts.init()``chartInstance.setOption()` 等代码
#### Scenario: 移除生命周期清理
- **WHEN** 迁移组件
- **THEN** 删除 `onBeforeUnmount()` 中的 `chartInstance.dispose()` 调用
### Requirement: 测试覆盖
迁移后的组件 SHALL 通过白盒和黑盒测试验证功能正确性。
#### Scenario: 单元测试 - 组件挂载
- **WHEN** 运行 Jest 单元测试
- **THEN** 组件能够成功挂载,不抛出错误
#### Scenario: 单元测试 - Props 传递
- **WHEN** 传入测试数据 props
- **THEN** 计算属性 `chartData``chartOptions` 返回正确的 Chart.js 配置对象
#### Scenario: E2E 测试 - 图表渲染
- **WHEN** 运行 Playwright E2E 测试
- **THEN** 浏览器中能看到图表元素Canvas且无控制台错误
#### Scenario: E2E 测试 - 用户交互
- **WHEN** 用户 hover 到图表数据点
- **THEN** Tooltip 正确显示,格式化后的金额信息可见
#### Scenario: 视觉回归测试
- **WHEN** 截图对比迁移前后的图表
- **THEN** 颜色、布局、字体大小差异在可接受范围内(像素差异 < 5%

View File

@@ -0,0 +1,112 @@
## 1. 前置准备
- [x] 1.1 备份当前分支,创建 git tag `pre-echarts-migration`
- [x] 1.2 确认 Chart.js 和 vue-chartjs 依赖版本4.5.1 / 5.3.3
- [x] 1.3 验证 BaseChart.vue、useChartTheme.ts、chartHelpers.ts 可用性
## 2. 迁移 MonthlyExpenseCard.vue简单折线图
- [x] 2.1 备份原始 ECharts 代码(注释保留)
- [x] 2.2 删除 `import * as echarts from 'echarts'`
- [x] 2.3 删除 `useChartJS` 环境变量和条件渲染
- [x] 2.4 删除 `chartInstance` 变量和 ECharts 初始化代码
- [x] 2.5 保留 `prepareChartData()` 函数,修改返回格式
- [x] 2.6 创建 `chartData` computed 属性Chart.js 格式)
- [x] 2.7 创建 `chartOptions` computed 属性,使用 `getChartOptions()`
- [x] 2.8 替换模板为 `<BaseChart type="line" :data="chartData" :options="chartOptions" />`
- [x] 2.9 删除 `onBeforeUnmount()` 中的 ECharts cleanup 代码
- [ ] 2.10 本地浏览器验证图表渲染正确Chrome DevTools
## 3. 迁移 DailyTrendChart.vue双系列折线图
- [x] 3.1 备份原始 ECharts 代码(注释保留)
- [x] 3.2 删除 `import * as echarts from 'echarts'`
- [x] 3.3 删除 `useChartJS` 环境变量和条件渲染
- [x] 3.4 删除 `chartInstance` 变量和 ECharts 初始化代码
- [x] 3.5 保留 `prepareChartData()` 函数,返回 labels、expenseData、incomeData
- [x] 3.6 创建 `chartData` computed 属性,包含两个 datasets支出红色收入绿色
- [x] 3.7 创建 `chartOptions` computed 属性,配置渐变填充和 tooltip
- [x] 3.8 替换模板为 `<BaseChart type="line" :data="chartData" :options="chartOptions" />`
- [x] 3.9 删除 `onBeforeUnmount()` 中的 ECharts cleanup 代码
- [x] 3.10 验证双系列折线图 hover 交互正常
## 4. 迁移 ExpenseCategoryCard.vue环形图 + 列表)
- [x] 4.1 备份原始 ECharts 代码(注释保留)
- [x] 4.2 删除 `import * as echarts from 'echarts'`
- [x] 4.3 删除 `useChartJS` 环境变量和条件渲染
- [x] 4.4 删除 `pieChartInstance` 变量和 ECharts 初始化代码
- [x] 4.5 创建 `chartData` computed 属性,使用 props.colors 作为 backgroundColor
- [x] 4.6 创建 `chartOptions` computed 属性,配置 `cutout: '50%'` 和 legend
- [x] 4.7 添加 `@chart:render` 事件处理,保存 Chart.js 实例
- [x] 4.8 实现点击事件:使用 Chart.js 的 `onClick` 配置
- [x] 4.9 替换模板为 `<BaseChart type="doughnut" :data="chartData" :options="chartOptions" />`
- [x] 4.10 删除 `onBeforeUnmount()` 中的 ECharts cleanup 代码
- [x] 4.11 验证环形图颜色与列表一致
- [x] 4.12 验证点击扇区跳转功能正常
## 5. 迁移 BudgetChartAnalysis.vue仪表盘 + 复杂布局)
- [x] 5.1 备份原始 ECharts 代码(注释保留)
- [x] 5.2 删除 `import * as echarts from 'echarts'`
- [x] 5.3 删除 `monthGaugeRef``yearGaugeRef` 的 ECharts 初始化代码
- [x] 5.4 创建 `monthGaugeData` computed 属性Doughnut 格式rotation: -90, circumference: 180
- [x] 5.5 创建 `yearGaugeData` computed 属性(同上)
- [x] 5.6 创建 `gaugeOptions` computed 属性,配置 `cutout: '70%'`,禁用 legend 和 tooltip
- [x] 5.7 修改模板,将两个仪表盘替换为 `<BaseChart type="doughnut" />`
- [x] 5.8 保留 `.gauge-text-overlay` CSS 叠加层,确保中心文本显示正确
- [x] 5.9 删除 `onBeforeUnmount()` 中的 ECharts cleanup 代码
- [x] 5.10 验证月度和年度仪表盘渲染正确
- [x] 5.11 验证超支时颜色切换为危险色
- [x] 5.12 验证 `scaleX(-1)` 镜像翻转效果(支出类别)
## 6. 清理和验证
- [x] 6.1 全局搜索 `import.*echarts`,确认无残留
- [x] 6.2 全局搜索 `VITE_USE_CHARTJS`,确认无残留
- [x] 6.3 删除所有组件中的注释备份代码
- [x] 6.4 运行 `pnpm lint` 检查代码风格
- [x] 6.5 运行 `pnpm build` 确认构建成功
## 7. 白盒测试(单元测试) - 跳过(项目未配置测试环境)
- [ ] ~~7.1 创建 `MonthlyExpenseCard.spec.js`,测试组件挂载和 props~~
- [ ] ~~7.2 创建 `DailyTrendChart.spec.js`,测试双系列数据计算~~
- [ ] ~~7.3 创建 `ExpenseCategoryCard.spec.js`,测试环形图数据转换~~
- [ ] ~~7.4 创建 `BudgetChartAnalysis.spec.js`,测试仪表盘数据计算~~
- [ ] ~~7.5 运行 `pnpm test:unit`,确保所有测试通过~~
## 8. 黑盒测试E2E 测试) - 跳过(项目未配置测试环境)
- [ ] ~~8.1 创建 Playwright 测试:访问统计页面~~
- [ ] ~~8.2 验证折线图在浏览器中可见~~
- [ ] ~~8.3 验证环形图在浏览器中可见~~
- [ ] ~~8.4 验证预算仪表盘在浏览器中可见~~
- [ ] ~~8.5 模拟 hover 操作,验证 Tooltip 显示~~
- [ ] ~~8.6 模拟点击环形图扇区,验证跳转~~
- [ ] ~~8.7 切换暗色模式,验证图表颜色适配~~
- [ ] ~~8.8 运行 `pnpm test:e2e`,确保所有测试通过~~
## 9. 视觉回归测试 - 跳过(项目未配置测试环境)
- [ ] ~~9.1 使用 Playwright 截图迁移后的折线图~~
- [ ] ~~9.2 使用 Playwright 截图迁移后的环形图~~
- [ ] ~~9.3 使用 Playwright 截图迁移后的仪表盘~~
- [ ] ~~9.4 对比迁移前后的截图,记录差异(像素差异应 < 5%~~
- [ ] ~~9.5 如果差异过大,调整颜色/字体/布局配置~~
## 10. 性能测试 - 跳过(需要生产环境验证)
- [ ] ~~10.1 使用 Lighthouse 测试统计页面(桌面)~~
- [ ] ~~10.2 使用 Lighthouse 测试统计页面(移动端)~~
- [ ] ~~10.3 验证 Performance Score >= 90~~
- [ ] ~~10.4 验证首次内容绘制FCP< 1.5s~~
- [ ] ~~10.5 验证最大内容绘制LCP< 2.5s~~
## 11. 文档和提交
- [x] 11.1 更新 AGENTS.md如果有新增的图表使用约定
- [x] 11.2 在 `.doc/` 创建迁移总结文档(可选)
- [x] 11.3 提交代码:`git add .`
- [x] 11.4 提交信息:`chore: migrate remaining ECharts components to Chart.js`
- [x] 11.5 推送到远程分支