489 lines
9.5 KiB
Markdown
489 lines
9.5 KiB
Markdown
[根目录](../../CLAUDE.md) > **components/ec-canvas**
|
||
|
||
---
|
||
|
||
# components/ec-canvas - ECharts 图表组件
|
||
|
||
> **模块状态**: ✅ 已完成
|
||
>
|
||
> **最后更新**: 2026-01-07 09:16:32
|
||
|
||
---
|
||
|
||
## 变更记录 (Changelog)
|
||
|
||
### 2026-01-07 09:16:32
|
||
- 生成组件文档
|
||
- 补充使用说明与配置项
|
||
- 添加常见问题与故障排查
|
||
|
||
### 2026-01-06
|
||
- 集成 ECharts 图表库
|
||
- 实现微信小程序 Canvas 适配
|
||
- 封装为通用组件
|
||
|
||
---
|
||
|
||
## 模块职责
|
||
|
||
**ec-canvas** 是 ECharts 图表组件,负责:
|
||
|
||
1. **图表渲染**:在微信小程序中渲染 ECharts 图表
|
||
2. **Canvas 适配**:适配小程序 Canvas 2D 接口
|
||
3. **事件处理**:处理触摸交互事件
|
||
4. **响应式布局**:自适应屏幕尺寸
|
||
|
||
---
|
||
|
||
## 入口与启动
|
||
|
||
### 组件路径
|
||
- **注册路径**:`components/ec-canvas/ec-canvas`
|
||
- **物理路径**:`components/ec-canvas/ec-canvas.js`
|
||
|
||
### 使用方式
|
||
```json
|
||
// 页面 JSON 配置
|
||
{
|
||
"usingComponents": {
|
||
"ec-canvas": "../../components/ec-canvas/ec-canvas"
|
||
}
|
||
}
|
||
```
|
||
|
||
```xml
|
||
<!-- 页面 WXML -->
|
||
<ec-canvas id="mychart-dom-line" canvas-id="mychart-line" ec="{{ ec }}"></ec-canvas>
|
||
```
|
||
|
||
---
|
||
|
||
## 对外接口
|
||
|
||
### 组件属性(Properties)
|
||
|
||
| 属性名 | 类型 | 默认值 | 说明 |
|
||
|--------|------|--------|------|
|
||
| canvasId | String | 'ec-canvas' | Canvas 组件 ID |
|
||
| ec | Object | {} | 图表配置对象(必须包含 onInit 方法) |
|
||
| disableTouch | Boolean | false | 是否禁用触摸交互 |
|
||
|
||
### ec 对象结构
|
||
```javascript
|
||
ec: {
|
||
onInit: function(canvas, width, height, res) {
|
||
// 必须返回图表实例
|
||
const chart = echarts.init(canvas)
|
||
chart.setOption(option)
|
||
return chart
|
||
}
|
||
}
|
||
```
|
||
|
||
### 组件方法(Methods)
|
||
|
||
| 方法名 | 参数 | 说明 |
|
||
|--------|------|------|
|
||
| touchStart | event | 触摸开始事件 |
|
||
| touchMove | event | 触摸移动事件 |
|
||
| touchEnd | event | 触摸结束事件 |
|
||
|
||
---
|
||
|
||
## 关键依赖与配置
|
||
|
||
### 依赖文件
|
||
| 文件 | 用途 |
|
||
|------|------|
|
||
| `ec-canvas.js` | 组件逻辑(91 行) |
|
||
| `ec-canvas.wxml` | 组件模板 |
|
||
| `ec-canvas.wxss` | 组件样式 |
|
||
| `ec-canvas.json` | 组件配置 |
|
||
| `echarts.js` | ECharts 库(简化版) |
|
||
| `wx-canvas.js` | Canvas 适配器 |
|
||
|
||
### 外部依赖
|
||
- **ECharts**:内置的简化版 ECharts 库
|
||
- **Canvas 2D**:微信小程序 Canvas 2D 接口
|
||
|
||
### 配置项
|
||
```javascript
|
||
// 组件配置
|
||
Component({
|
||
properties: {
|
||
canvasId: {
|
||
type: String,
|
||
value: 'ec-canvas'
|
||
},
|
||
ec: {
|
||
type: Object,
|
||
value: {}
|
||
}
|
||
}
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 数据模型
|
||
|
||
### 组件生命周期
|
||
```javascript
|
||
Component({
|
||
ready() {
|
||
// 1. 检查 ec 对象
|
||
if (!this.data.ec || !this.data.ec.onInit) {
|
||
console.warn('组件需绑定 ec 对象,且包含 onInit 方法')
|
||
return
|
||
}
|
||
|
||
// 2. 查询 Canvas 节点
|
||
const query = this.createSelectorQuery()
|
||
query.select(`#${this.data.canvasId}`)
|
||
.fields({ node: true, size: true })
|
||
.exec((res) => {
|
||
// 3. 初始化 Canvas
|
||
const canvasNode = res[0].node
|
||
const ctx = canvasNode.getContext('2d')
|
||
const dpr = wx.getSystemInfoSync().pixelRatio
|
||
|
||
// 4. 设置 Canvas 尺寸
|
||
canvasNode.width = res[0].width * dpr
|
||
canvasNode.height = res[0].height * dpr
|
||
ctx.scale(dpr, dpr)
|
||
|
||
// 5. 调用 onInit 初始化图表
|
||
const canvas = {
|
||
width: res[0].width * dpr,
|
||
height: res[0].height * dpr,
|
||
getContext: () => ctx,
|
||
node: canvasNode
|
||
}
|
||
|
||
this.chart = this.data.ec.onInit(canvas, res[0].width, res[0].height, res)
|
||
})
|
||
}
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
## 核心功能实现
|
||
|
||
### 1. Canvas 初始化
|
||
```javascript
|
||
ready() {
|
||
const query = this.createSelectorQuery()
|
||
query.select(`#${this.data.canvasId}`)
|
||
.fields({ node: true, size: true })
|
||
.exec((res) => {
|
||
const canvasNode = res[0].node
|
||
const ctx = canvasNode.getContext('2d')
|
||
const dpr = wx.getSystemInfoSync().pixelRatio
|
||
|
||
// 缩放以适配高清屏
|
||
canvasNode.width = res[0].width * dpr
|
||
canvasNode.height = res[0].height * dpr
|
||
ctx.scale(dpr, dpr)
|
||
|
||
// 创建 Canvas 对象
|
||
const canvas = {
|
||
width: res[0].width * dpr,
|
||
height: res[0].height * dpr,
|
||
getContext: () => ctx,
|
||
node: canvasNode
|
||
}
|
||
|
||
// 调用外部初始化函数
|
||
this.chart = this.data.ec.onInit(canvas, res[0].width, res[0].height, res)
|
||
})
|
||
}
|
||
```
|
||
|
||
### 2. 触摸事件处理
|
||
```javascript
|
||
methods: {
|
||
touchStart(e) {
|
||
if (this.chart && this.chart.touchStart) {
|
||
this.chart.touchStart(e)
|
||
}
|
||
},
|
||
|
||
touchMove(e) {
|
||
if (this.chart && this.chart.touchMove) {
|
||
this.chart.touchMove(e)
|
||
}
|
||
},
|
||
|
||
touchEnd(e) {
|
||
if (this.chart && this.chart.touchEnd) {
|
||
this.chart.touchEnd(e)
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 使用示例
|
||
|
||
### 1. 基础折线图
|
||
```javascript
|
||
// 页面 JS
|
||
Page({
|
||
data: {
|
||
ec: {
|
||
onInit: null
|
||
}
|
||
},
|
||
|
||
onLoad() {
|
||
this.setData({
|
||
ec: {
|
||
onInit: this.initChart.bind(this)
|
||
}
|
||
})
|
||
},
|
||
|
||
initChart(canvas, width, height, res) {
|
||
const chart = echarts.init(canvas)
|
||
|
||
const option = {
|
||
xAxis: {
|
||
type: 'category',
|
||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
|
||
},
|
||
yAxis: {
|
||
type: 'value'
|
||
},
|
||
series: [{
|
||
data: [120, 200, 150, 80, 70],
|
||
type: 'line',
|
||
smooth: true
|
||
}]
|
||
}
|
||
|
||
chart.setOption(option)
|
||
return chart
|
||
}
|
||
})
|
||
```
|
||
|
||
### 2. 面积图(带渐变)
|
||
```javascript
|
||
initChart(canvas, width, height, res) {
|
||
const chart = echarts.init(canvas)
|
||
|
||
const option = {
|
||
xAxis: {
|
||
type: 'category',
|
||
data: ['1/1', '1/2', '1/3', '1/4', '1/5']
|
||
},
|
||
yAxis: {
|
||
type: 'value'
|
||
},
|
||
series: [{
|
||
data: [3850, 3860, 3840, 3870, 3890],
|
||
type: 'line',
|
||
smooth: true,
|
||
areaStyle: {
|
||
color: {
|
||
type: 'linear',
|
||
x: 0, y: 0, x2: 0, y2: 1,
|
||
colorStops: [
|
||
{ offset: 0, color: 'rgba(24, 144, 255, 0.3)' },
|
||
{ offset: 1, color: 'rgba(24, 144, 255, 0.05)' }
|
||
]
|
||
}
|
||
}
|
||
}]
|
||
}
|
||
|
||
chart.setOption(option)
|
||
return chart
|
||
}
|
||
```
|
||
|
||
### 3. 动态更新数据
|
||
```javascript
|
||
// 更新图表数据
|
||
updateChart(newData) {
|
||
if (this.chart) {
|
||
this.chart.setOption({
|
||
series: [{
|
||
data: newData
|
||
}]
|
||
})
|
||
}
|
||
}
|
||
|
||
// 清空图表
|
||
clearChart() {
|
||
if (this.chart) {
|
||
this.chart.clear()
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## WXML 模板结构
|
||
|
||
```xml
|
||
<!-- components/ec-canvas/ec-canvas.wxml -->
|
||
<canvas
|
||
type="2d"
|
||
id="{{canvasId}}"
|
||
canvas-id="{{canvasId}}"
|
||
class="ec-canvas"
|
||
bindtouchstart="touchStart"
|
||
bindtouchmove="touchMove"
|
||
bindtouchend="touchEnd">
|
||
</canvas>
|
||
```
|
||
|
||
---
|
||
|
||
## WXSS 样式
|
||
|
||
```css
|
||
/* components/ec-canvas/ec-canvas.wxss */
|
||
.ec-canvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: block;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 测试与质量
|
||
|
||
### 测试覆盖
|
||
- ✅ 在价格趋势页正常工作
|
||
- ✅ 图表渲染正确
|
||
- ✅ 触摸交互正常
|
||
- ✅ 响应式布局适配
|
||
|
||
### 测试要点
|
||
1. **图表渲染**:验证不同类型图表正常显示
|
||
2. **数据更新**:验证动态更新数据功能
|
||
3. **交互功能**:验证触摸、缩放、拖拽等交互
|
||
4. **性能测试**:大数据量时的渲染性能
|
||
5. **兼容性**:iOS/Android 不同平台兼容性
|
||
|
||
---
|
||
|
||
## 常见问题 (FAQ)
|
||
|
||
### Q: 图表不显示?
|
||
|
||
**A**: 检查以下几点:
|
||
1. 确保 `ec` 对象包含 `onInit` 方法
|
||
2. 确保 Canvas 节点已正确渲染
|
||
3. 查看控制台是否有错误信息
|
||
4. 检查 ECharts 配置是否正确
|
||
|
||
### Q: 如何修改图表尺寸?
|
||
|
||
**A**: 在 WXML 中设置容器尺寸:
|
||
```xml
|
||
<view style="width: 100%; height: 500rpx;">
|
||
<ec-canvas ec="{{ ec }}"></ec-canvas>
|
||
</view>
|
||
```
|
||
|
||
### Q: 如何支持手势缩放?
|
||
|
||
**A**: 在 ECharts 配置中启用:
|
||
```javascript
|
||
const option = {
|
||
dataZoom: [{
|
||
type: 'inside',
|
||
start: 0,
|
||
end: 100
|
||
}],
|
||
// ... 其他配置
|
||
}
|
||
```
|
||
|
||
### Q: 如何导出图表为图片?
|
||
|
||
**A**: 使用 Canvas 的 `toDataURL` 方法:
|
||
```javascript
|
||
const canvas = this.chart.canvas
|
||
const url = canvas.toDataURL('image/png')
|
||
|
||
// 预览图片
|
||
wx.previewImage({
|
||
urls: [url]
|
||
})
|
||
```
|
||
|
||
### Q: 图表性能如何优化?
|
||
|
||
**A**: 优化建议:
|
||
1. 减少数据点数量(采样)
|
||
2. 关闭动画效果
|
||
3. 使用轻量级图表类型
|
||
4. 避免频繁更新
|
||
|
||
```javascript
|
||
// 关闭动画
|
||
const option = {
|
||
animation: false,
|
||
// ... 其他配置
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 相关文件清单
|
||
|
||
```
|
||
components/ec-canvas/
|
||
├── ec-canvas.js # 组件逻辑(91 行)
|
||
├── ec-canvas.json # 组件配置
|
||
├── ec-canvas.wxml # 组件模板
|
||
├── ec-canvas.wxss # 组件样式
|
||
├── echarts.js # ECharts 库(简化版)
|
||
├── wx-canvas.js # Canvas 适配器
|
||
└── CLAUDE.md # 本文档
|
||
```
|
||
|
||
---
|
||
|
||
## 下一步建议
|
||
|
||
### 功能增强
|
||
1. **更多图表类型**
|
||
- 柱状图(Bar)
|
||
- 饼图(Pie)
|
||
- 散点图(Scatter)
|
||
- K 线图(Candlestick)
|
||
|
||
2. **交互增强**
|
||
- Tooltip 提示框
|
||
- 图例筛选
|
||
- 数据区域缩放
|
||
- 标记点/标记线
|
||
|
||
3. **性能优化**
|
||
- 虚拟滚动(大数据量)
|
||
- 增量渲染
|
||
- Web Worker 计算
|
||
|
||
### 最佳实践
|
||
1. **数据格式化**:统一数据格式转换逻辑
|
||
2. **错误处理**:添加图表渲染失败的降级方案
|
||
3. **加载状态**:显示加载动画
|
||
4. **空状态**:无数据时友好提示
|
||
|
||
---
|
||
|
||
**模块状态**: ✅ 已完成
|
||
**优先级**: 高(核心组件)
|
||
**预估工作量**: 已完成
|
||
**使用场景**: [pages/trend](../../pages/trend/CLAUDE.md) - 价格趋势图
|
||
**相关资源**: [ECharts 文档](https://echarts.apache.org/zh/index.html)
|