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:
201
openspec/changes/migrate-remaining-echarts-to-chartjs/design.md
Normal file
201
openspec/changes/migrate-remaining-echarts-to-chartjs/design.md
Normal 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. ⏳ **是否需要通知后端团队?**
|
||||
答:纯前端技术栈变更,无需通知
|
||||
Reference in New Issue
Block a user