Files
EmailBill/openspec/changes/migrate-remaining-echarts-to-chartjs/design.md
SunCheng 9921cd5fdf 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
2026-02-16 21:55:38 +08:00

6.1 KiB
Raw Blame History

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 图表可以通过 rotationcircumference 配置实现半圆仪表盘效果
  • 项目中 BudgetChartAnalysis.vue 已使用 ECharts Gauge需保持视觉一致性

替代方案

  • 使用第三方插件(如 chartjs-gauge增加依赖复杂度
  • 使用 Canvas 自绘:维护成本高,不符合项目标准

实现细节

{
  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 风险

迁移模式

// 保留
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 依赖已移除,无法回退

清理范围

<!-- 删除 -->
<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. 是否需要通知后端团队?
    答:纯前端技术栈变更,无需通知