|
@@ -0,0 +1,566 @@
|
|
|
|
|
+# <!-- Powered by BMAD™ Core -->
|
|
|
|
|
+
|
|
|
|
|
+# Story 016.009: 创建 React 图表组件封装
|
|
|
|
|
+
|
|
|
|
|
+## Status
|
|
|
|
|
+
|
|
|
|
|
+Approved
|
|
|
|
|
+
|
|
|
|
|
+## Story
|
|
|
|
|
+
|
|
|
|
|
+**作为** 小程序开发者,
|
|
|
|
|
+**我想要** 创建现代 React 函数式组件封装 u-charts 核心库,
|
|
|
|
|
+**以便** 简化图表使用方式,无需手动管理 Canvas 上下文和事件处理,提高开发效率。
|
|
|
|
|
+
|
|
|
|
|
+## 背景
|
|
|
|
|
+
|
|
|
|
|
+u-charts 原库需要手动管理 Canvas 上下文和事件处理,使用类组件的方式较为繁琐。现代小程序开发使用 React 函数式组件和 Hooks,需要创建易于使用的图表组件。
|
|
|
|
|
+
|
|
|
|
|
+**参考实现**: `docs/小程序图表库示例/使用示例.md` 提供了完整的 React + Taro 使用示例,使用的是类组件方式。
|
|
|
|
|
+
|
|
|
|
|
+**前置故事完成状态**:
|
|
|
|
|
+- ✅ 故事 016.001-016.008: u-charts 核心库已完成模块化搬迁和类型定义
|
|
|
|
|
+- ✅ uCharts 类可通过 `@d8d/mini-charts` 导入使用
|
|
|
|
|
+- ✅ 所有核心功能函数已模块化,可供组件导入
|
|
|
|
|
+
|
|
|
|
|
+## 前置故事完成状态
|
|
|
|
|
+
|
|
|
|
|
+**故事 016.001**: ✅ 已完成 - 创建 mini-charts 包基础结构
|
|
|
|
|
+**故事 016.002**: ✅ 已完成 - 模块化 config 和 utils
|
|
|
|
|
+**故事 016.003**: ✅ 已完成 - 模块化 data-processing 函数
|
|
|
|
|
+**故事 016.004**: ✅ 已完成 - 模块化 charts-data 函数
|
|
|
|
|
+**故事 016.005**: ✅ 已完成 - 模块化 renderers 函数
|
|
|
|
|
+**故事 016.006**: ✅ 已完成 - 搬迁核心类并完成模块化
|
|
|
|
|
+**故事 016.007**: ✅ 已完成 - 搬迁遗漏的辅助函数
|
|
|
|
|
+**故事 016.008**: ✅ 已完成 - 搬迁核心绘制函数完成模块化
|
|
|
|
|
+
|
|
|
|
|
+## Acceptance Criteria
|
|
|
|
|
+
|
|
|
|
|
+1. 创建 BaseChart 基础组件,封装 Canvas 创建和销毁逻辑
|
|
|
|
|
+2. 至少实现5种图表组件(ColumnChart、LineChart、CandleChart、PieChart、RadarChart)
|
|
|
|
|
+3. 组件支持 Props 配置,类型定义完整
|
|
|
|
|
+4. 组件支持触摸事件处理(tooltip 显示)
|
|
|
|
|
+5. 组件支持响应式尺寸计算和像素比适配
|
|
|
|
|
+6. 类型定义完整,类型检查通过(`pnpm typecheck`)
|
|
|
|
|
+7. 构建成功(`pnpm build`),自动生成 .d.ts 声明文件
|
|
|
|
|
+
|
|
|
|
|
+## Tasks / Subtasks
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 1: 创建 BaseChart 基础组件 (AC: 1, 5, 6)
|
|
|
|
|
+ - [ ] 1.1 创建 `src/components/BaseChart.tsx` 基础组件
|
|
|
|
|
+ - [ ] 1.2 实现 Canvas 创建和销毁逻辑(useEffect cleanup)
|
|
|
|
|
+ - [ ] 1.3 实现响应式尺寸计算(useMemo + Taro.getSystemInfoSync)
|
|
|
|
|
+ - [ ] 1.4 实现像素比适配逻辑
|
|
|
|
|
+ - [ ] 1.5 定义 BaseChartProps 接口(canvasId, width, height, data, config等)
|
|
|
|
|
+ - [ ] 1.6 使用 useRef 管理 uCharts 实例
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 2: 创建 ColumnChart 柱状图组件 (AC: 2, 3, 4, 6)
|
|
|
|
|
+ - [ ] 2.1 创建 `src/components/ColumnChart.tsx`
|
|
|
|
|
+ - [ ] 2.2 定义 ColumnChartProps 接口
|
|
|
|
|
+ - [ ] 2.3 实现柱状图数据配置
|
|
|
|
|
+ - [ ] 2.4 实现 tooltip 事件处理(onTouchStart)
|
|
|
|
|
+ - [ ] 2.5 添加类型注解
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 3: 创建 LineChart 折线图组件 (AC: 2, 3, 4, 6)
|
|
|
|
|
+ - [ ] 3.1 创建 `src/components/LineChart.tsx`
|
|
|
|
|
+ - [ ] 3.2 定义 LineChartProps 接口
|
|
|
|
|
+ - [ ] 3.3 实现折线图数据配置(支持 dataPointShape、enableScroll等)
|
|
|
|
|
+ - [ ] 3.4 实现拖拽滚动事件处理(onTouchStart、onTouchMove、onTouchEnd)
|
|
|
|
|
+ - [ ] 3.5 添加类型注解
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 4: 创建 CandleChart K线图组件 (AC: 2, 3, 4, 6)
|
|
|
|
|
+ - [ ] 4.1 创建 `src/components/CandleChart.tsx`
|
|
|
|
|
+ - [ ] 4.2 定义 CandleChartProps 接口
|
|
|
|
|
+ - [ ] 4.3 实现K线图数据配置(支持移动平均线 MA5/MA10/MA30)
|
|
|
|
|
+ - [ ] 4.4 实现拖拽滚动和tooltip事件处理
|
|
|
|
|
+ - [ ] 4.5 添加类型注解
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 5: 创建 PieChart 饼图组件 (AC: 2, 3, 4, 6)
|
|
|
|
|
+ - [ ] 5.1 创建 `src/components/PieChart.tsx`
|
|
|
|
|
+ - [ ] 5.2 定义 PieChartProps 接口
|
|
|
|
|
+ - [ ] 5.3 实现饼图数据配置
|
|
|
|
|
+ - [ ] 5.4 实现tooltip事件处理
|
|
|
|
|
+ - [ ] 5.5 添加类型注解
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 6: 创建 RadarChart 雷达图组件 (AC: 2, 3, 4, 6)
|
|
|
|
|
+ - [ ] 6.1 创建 `src/components/RadarChart.tsx`
|
|
|
|
|
+ - [ ] 6.2 定义 RadarChartProps 接口
|
|
|
|
|
+ - [ ] 6.3 实现雷达图数据配置
|
|
|
|
|
+ - [ ] 6.4 实现tooltip事件处理
|
|
|
|
|
+ - [ ] 6.5 添加类型注解
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 7: 创建组件导出文件 (AC: 3, 6)
|
|
|
|
|
+ - [ ] 7.1 创建 `src/components/index.ts`
|
|
|
|
|
+ - [ ] 7.2 导出所有图表组件
|
|
|
|
|
+ - [ ] 7.3 导出所有Props类型定义
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 8: 更新 src/index.ts 主入口 (AC: 3, 6, 7)
|
|
|
|
|
+ - [ ] 8.1 从 components 导出所有图表组件
|
|
|
|
|
+ - [ ] 8.2 导出组件Props类型定义
|
|
|
|
|
+ - [ ] 8.3 更新 package.json 的 exports 字段支持 components 导出
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 9: 验证组件实现 (AC: 6, 7)
|
|
|
|
|
+ - [ ] 9.1 运行类型检查验证类型注解正确(`pnpm typecheck`)
|
|
|
|
|
+ - [ ] 9.2 运行构建验证生成 .d.ts 文件(`pnpm build`)
|
|
|
|
|
+ - [ ] 9.3 检查生成的 .d.ts 文件正确导出组件类型
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Task 10: 创建基础测试(可选) (AC: 3, 4)
|
|
|
|
|
+ - [ ] 10.1 创建测试 Canvas mock
|
|
|
|
|
+ - [ ] 10.2 为 BaseChart 创建基础渲染测试
|
|
|
|
|
+ - [ ] 10.3 为一个图表组件创建基础测试
|
|
|
|
|
+
|
|
|
|
|
+## Dev Notes
|
|
|
|
|
+
|
|
|
|
|
+### 前置故事见解
|
|
|
|
|
+
|
|
|
|
|
+**故事 016.001-016.008 完成状态总结**:
|
|
|
|
|
+- ✅ u-charts 核心库已完全模块化搬迁完成
|
|
|
|
|
+- ✅ 所有函数都有完整的 TypeScript 类型注解
|
|
|
|
|
+- ✅ `src/lib/charts/u-charts.ts` 导出 uCharts 主类
|
|
|
|
|
+- ✅ `src/lib/charts/u-charts-event.ts` 导出 uChartsEvent 事件类
|
|
|
|
|
+- ✅ 所有渲染函数、数据处理函数、辅助函数都已模块化
|
|
|
|
|
+- ✅ 类型检查通过(`pnpm typecheck`)
|
|
|
|
|
+- ✅ 构建成功(`pnpm build`),自动生成 .d.ts 声明文件
|
|
|
|
|
+- ✅ 原始 u-charts.ts 已备份为 u-charts.ts.backup
|
|
|
|
|
+
|
|
|
|
|
+**关键技术决策**:
|
|
|
|
|
+- 保持代码逻辑完全不变,只改变文件组织方式
|
|
|
|
|
+- 只在搬迁过程中添加 TypeScript 类型注解,不修改代码的实现逻辑
|
|
|
|
|
+- 使用 ES6 `export` 语法导出函数和类型
|
|
|
|
|
+
|
|
|
|
|
+### 技术栈要求
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [mini-charts/package.json](../../mini-ui-packages/mini-charts/package.json)
|
|
|
|
|
+
|
|
|
|
|
+- **React**: 18.0.0(mini 包使用 React 18,不是 React 19)
|
|
|
|
|
+- **Taro**: 4.1.4(包括 @tarojs/components, @tarojs/react, @tarojs/taro)
|
|
|
|
|
+- **TypeScript**: 5.4.5
|
|
|
|
|
+- **Node.js**: 20.18.3(运行时环境)
|
|
|
|
|
+- **包管理器**: pnpm workspace
|
|
|
|
|
+
|
|
|
|
|
+**重要**: mini 包使用 **React 18**,不是主项目使用的 React 19。
|
|
|
|
|
+
|
|
|
|
|
+### 项目结构指南
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [source-tree.md](../../architecture/source-tree.md)
|
|
|
|
|
+
|
|
|
|
|
+```
|
|
|
|
|
+mini-ui-packages/
|
|
|
|
|
+└── mini-charts/ # mini-charts 包
|
|
|
|
|
+ ├── src/
|
|
|
|
|
+ │ ├── index.ts # 主入口文件(需要更新)
|
|
|
|
|
+ │ ├── components/ # [本故事创建] React 图表组件
|
|
|
|
|
+ │ │ ├── BaseChart.tsx # 基础图表组件
|
|
|
|
|
+ │ │ ├── ColumnChart.tsx # 柱状图组件
|
|
|
|
|
+ │ │ ├── LineChart.tsx # 折线图组件
|
|
|
|
|
+ │ │ ├── CandleChart.tsx # K线图组件
|
|
|
|
|
+ │ │ ├── PieChart.tsx # 饼图组件
|
|
|
|
|
+ │ │ ├── RadarChart.tsx # 雷达图组件
|
|
|
|
|
+ │ │ └── index.ts # 组件导出
|
|
|
|
|
+ │ └── lib/ # u-charts 核心库(已完成)
|
|
|
|
|
+ │ ├── config.ts
|
|
|
|
|
+ │ ├── utils/
|
|
|
|
|
+ │ ├── data-processing/
|
|
|
|
|
+ │ ├── charts-data/
|
|
|
|
|
+ │ ├── renderers/
|
|
|
|
|
+ │ ├── helper-functions/
|
|
|
|
|
+ │ ├── draw-controllers/
|
|
|
|
|
+ │ └── charts/
|
|
|
|
|
+ │ ├── u-charts-event.ts
|
|
|
|
|
+ │ ├── u-charts.ts
|
|
|
|
|
+ │ └── index.ts
|
|
|
|
|
+ ├── tests/ # 测试目录
|
|
|
|
|
+ ├── package.json
|
|
|
|
|
+ ├── tsconfig.json
|
|
|
|
|
+ └── jest.config.cjs
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### TypeScript 配置规范
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [ui-package-standards.md](../../architecture/ui-package-standards.md#typescript配置)
|
|
|
|
|
+
|
|
|
|
|
+```json
|
|
|
|
|
+{
|
|
|
|
|
+ "compilerOptions": {
|
|
|
|
|
+ "target": "ES2020",
|
|
|
|
|
+ "lib": ["DOM", "DOM.Iterable", "ES2020"],
|
|
|
|
|
+ "module": "ESNext",
|
|
|
|
|
+ "strict": true,
|
|
|
|
|
+ "noUnusedLocals": true,
|
|
|
|
|
+ "noUnusedParameters": true,
|
|
|
|
|
+ "noFallthroughCasesInSwitch": true
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**本故事重要**: 需要为所有组件Props接口添加完整的类型注解,确保 `strict: true` 模式下无类型错误。
|
|
|
|
|
+
|
|
|
|
|
+### uCharts 使用参考
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [docs/小程序图表库示例/使用示例.md](../../小程序图表库示例/使用示例.md)
|
|
|
|
|
+
|
|
|
|
|
+#### 原始类组件使用方式
|
|
|
|
|
+
|
|
|
|
|
+```jsx
|
|
|
|
|
+import React, { Component } from 'react';
|
|
|
|
|
+import Taro from '@tarojs/taro';
|
|
|
|
|
+import { View, Canvas } from '@tarojs/components';
|
|
|
|
|
+import uCharts from '../../js_sdk/u-charts/u-charts.js';
|
|
|
|
|
+
|
|
|
|
|
+export default class Index extends Component {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ super(...arguments)
|
|
|
|
|
+ this.state = {
|
|
|
|
|
+ cWidth: '',
|
|
|
|
|
+ cHeight: '',
|
|
|
|
|
+ pixelRatio: 1,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ componentDidMount() {
|
|
|
|
|
+ const sysInfo = Taro.getSystemInfoSync();
|
|
|
|
|
+ let pixelRatio = 1;
|
|
|
|
|
+ if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
|
|
|
|
|
+ pixelRatio = sysInfo.pixelRatio;
|
|
|
|
|
+ }
|
|
|
|
|
+ const cWidth = pixelRatio * sysInfo.windowWidth;
|
|
|
|
|
+ const cHeight = 500 / 750 * cWidth;
|
|
|
|
|
+ this.setState({ cWidth, cHeight, pixelRatio }, () => this.getServerData());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ showColumn = (canvasId, chartData) => {
|
|
|
|
|
+ const { cWidth, cHeight, pixelRatio } = this.state;
|
|
|
|
|
+ let ctx = Taro.createCanvasContext(canvasId);
|
|
|
|
|
+ canvaColumn = new uCharts({
|
|
|
|
|
+ type: 'column',
|
|
|
|
|
+ context: ctx,
|
|
|
|
|
+ legend: true,
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ background: '#FFFFFF',
|
|
|
|
|
+ pixelRatio,
|
|
|
|
|
+ animation: true,
|
|
|
|
|
+ categories: chartData.categories,
|
|
|
|
|
+ series: chartData.series,
|
|
|
|
|
+ xAxis: { disableGrid: true },
|
|
|
|
|
+ yAxis: {},
|
|
|
|
|
+ dataLabel: true,
|
|
|
|
|
+ width: cWidth,
|
|
|
|
|
+ height: cHeight,
|
|
|
|
|
+ extra: {
|
|
|
|
|
+ column: {
|
|
|
|
|
+ type: 'group',
|
|
|
|
|
+ width: cWidth * 0.45 / chartData.categories.length
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ touchColumn = (e) => {
|
|
|
|
|
+ canvaColumn.showToolTip(e, {
|
|
|
|
|
+ formatter: function (item, category) {
|
|
|
|
|
+ return category + ' ' + item.name + ':' + item.data
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ render() {
|
|
|
|
|
+ const { cWidth, cHeight } = this.state;
|
|
|
|
|
+ return (
|
|
|
|
|
+ <View>
|
|
|
|
|
+ <Canvas
|
|
|
|
|
+ canvas-id="canvasColumn"
|
|
|
|
|
+ id="canvasColumn"
|
|
|
|
|
+ onTouchStart={this.touchColumn}
|
|
|
|
|
+ />
|
|
|
|
|
+ </View>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 关键API说明
|
|
|
|
|
+
|
|
|
|
|
+| API | 说明 |
|
|
|
|
|
+|-----|------|
|
|
|
|
|
+| `Taro.getSystemInfoSync()` | 获取系统信息(屏幕宽度、像素比等) |
|
|
|
|
|
+| `Taro.createCanvasContext(canvasId)` | 创建Canvas上下文 |
|
|
|
|
|
+| `Taro.getEnv()` | 获取当前环境(微信/支付宝/H5等) |
|
|
|
|
|
+| `new uCharts(config)` | 创建图表实例 |
|
|
|
|
|
+| `chart.showToolTip(e, options)` | 显示tooltip |
|
|
|
|
|
+| `chart.scrollStart(e)` / `chart.scroll(e)` / `chart.scrollEnd(e)` | 滚动事件处理 |
|
|
|
|
|
+
|
|
|
|
|
+### 组件设计模式
|
|
|
|
|
+
|
|
|
|
|
+#### BaseChart 基础组件
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// src/components/BaseChart.tsx
|
|
|
|
|
+import React, { useEffect, useRef, useMemo } from 'react';
|
|
|
|
|
+import Taro from '@tarojs/taro';
|
|
|
|
|
+import { Canvas } from '@tarojs/components';
|
|
|
|
|
+import { uCharts } from '../lib/charts';
|
|
|
|
|
+
|
|
|
|
|
+export interface BaseChartProps {
|
|
|
|
|
+ canvasId: string;
|
|
|
|
|
+ width?: number;
|
|
|
|
|
+ height?: number;
|
|
|
|
|
+ pixelRatio?: number;
|
|
|
|
|
+ type: string;
|
|
|
|
|
+ categories: string[];
|
|
|
|
|
+ series: any[];
|
|
|
|
|
+ config?: Record<string, any>;
|
|
|
|
|
+ onTouchStart?: (e: any) => void;
|
|
|
|
|
+ onTouchMove?: (e: any) => void;
|
|
|
|
|
+ onTouchEnd?: (e: any) => void;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const BaseChart: React.FC<BaseChartProps> = (props) => {
|
|
|
|
|
+ const {
|
|
|
|
|
+ canvasId,
|
|
|
|
|
+ width,
|
|
|
|
|
+ height,
|
|
|
|
|
+ pixelRatio,
|
|
|
|
|
+ type,
|
|
|
|
|
+ categories,
|
|
|
|
|
+ series,
|
|
|
|
|
+ config = {},
|
|
|
|
|
+ onTouchStart,
|
|
|
|
|
+ onTouchMove,
|
|
|
|
|
+ onTouchEnd,
|
|
|
|
|
+ } = props;
|
|
|
|
|
+
|
|
|
|
|
+ const chartRef = useRef<uCharts | null>(null);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算响应式尺寸
|
|
|
|
|
+ const { cWidth, cHeight, actualPixelRatio } = useMemo(() => {
|
|
|
|
|
+ const sysInfo = Taro.getSystemInfoSync();
|
|
|
|
|
+ const pr = pixelRatio ?? (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY ? sysInfo.pixelRatio : 1);
|
|
|
|
|
+ const cw = width ?? pr * sysInfo.windowWidth;
|
|
|
|
|
+ const ch = height ?? (500 / 750 * cw);
|
|
|
|
|
+ return { cWidth: cw, cHeight: ch, actualPixelRatio: pr };
|
|
|
|
|
+ }, [width, height, pixelRatio]);
|
|
|
|
|
+
|
|
|
|
|
+ // Canvas props
|
|
|
|
|
+ const canvasProps = useMemo(() => {
|
|
|
|
|
+ if (Taro.getEnv() === Taro.ENV_TYPE.ALIPAY) {
|
|
|
|
|
+ return {
|
|
|
|
|
+ width: cWidth,
|
|
|
|
|
+ height: cHeight,
|
|
|
|
|
+ style: { width: '100%', height: '100%' }
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ style: { width: `${cWidth}px`, height: `${cHeight}px` }
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [cWidth, cHeight]);
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化图表
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ const ctx = Taro.createCanvasContext(canvasId);
|
|
|
|
|
+ chartRef.current = new uCharts({
|
|
|
|
|
+ type,
|
|
|
|
|
+ context: ctx,
|
|
|
|
|
+ categories,
|
|
|
|
|
+ series,
|
|
|
|
|
+ width: cWidth,
|
|
|
|
|
+ height: cHeight,
|
|
|
|
|
+ pixelRatio: actualPixelRatio,
|
|
|
|
|
+ ...config,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ // 清理图表实例
|
|
|
|
|
+ chartRef.current = null;
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [canvasId, type, categories, series, cWidth, cHeight, actualPixelRatio, config]);
|
|
|
|
|
+
|
|
|
|
|
+ // 事件处理
|
|
|
|
|
+ const handleTouchStart = (e: any) => {
|
|
|
|
|
+ onTouchStart?.(e);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleTouchMove = (e: any) => {
|
|
|
|
|
+ onTouchMove?.(e);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleTouchEnd = (e: any) => {
|
|
|
|
|
+ onTouchEnd?.(e);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Canvas
|
|
|
|
|
+ canvas-id={canvasId}
|
|
|
|
|
+ id={canvasId}
|
|
|
|
|
+ {...canvasProps}
|
|
|
|
|
+ onTouchStart={handleTouchStart}
|
|
|
|
|
+ onTouchMove={handleTouchMove}
|
|
|
|
|
+ onTouchEnd={handleTouchEnd}
|
|
|
|
|
+ />
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default BaseChart;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### 具体图表组件示例
|
|
|
|
|
+
|
|
|
|
|
+```typescript
|
|
|
|
|
+// src/components/ColumnChart.tsx
|
|
|
|
|
+import React from 'react';
|
|
|
|
|
+import { BaseChart, BaseChartProps } from './BaseChart';
|
|
|
|
|
+
|
|
|
|
|
+export interface ColumnChartProps extends Omit<BaseChartProps, 'type'> {
|
|
|
|
|
+ // 柱状图特有配置
|
|
|
|
|
+ dataLabel?: boolean;
|
|
|
|
|
+ columnType?: 'group' | 'stack';
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const ColumnChart: React.FC<ColumnChartProps> = (props) => {
|
|
|
|
|
+ const { dataLabel = true, columnType = 'group', ...baseProps } = props;
|
|
|
|
|
+
|
|
|
|
|
+ const defaultConfig = {
|
|
|
|
|
+ legend: true,
|
|
|
|
|
+ fontSize: 11,
|
|
|
|
|
+ background: '#FFFFFF',
|
|
|
|
|
+ animation: true,
|
|
|
|
|
+ dataLabel,
|
|
|
|
|
+ xAxis: { disableGrid: true },
|
|
|
|
|
+ yAxis: {},
|
|
|
|
|
+ extra: {
|
|
|
|
|
+ column: {
|
|
|
|
|
+ type: columnType,
|
|
|
|
|
+ // width 会在组件内部动态计算
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // tooltip 事件处理
|
|
|
|
|
+ const handleTouchStart = (e: any) => {
|
|
|
|
|
+ if (baseProps.onTouchStart) {
|
|
|
|
|
+ baseProps.onTouchStart(e);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 使用 chartRef 调用 showToolTip
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <BaseChart
|
|
|
|
|
+ {...baseProps}
|
|
|
|
|
+ type="column"
|
|
|
|
|
+ config={{ ...defaultConfig, ...baseProps.config }}
|
|
|
|
|
+ onTouchStart={handleTouchStart}
|
|
|
|
|
+ />
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default ColumnChart;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 类型定义规范
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [ui-package-standards.md](../../architecture/ui-package-standards.md#类型定义规范)
|
|
|
|
|
+
|
|
|
|
|
+**类型定义原则**:
|
|
|
|
|
+1. 为所有组件Props定义 TypeScript 接口
|
|
|
|
|
+2. 导出所有公共类型定义
|
|
|
|
|
+3. 使用泛型增强类型安全性
|
|
|
|
|
+4. 避免使用 `any` 类型(除非必要)
|
|
|
|
|
+
|
|
|
|
|
+### 编码标准
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [coding-standards.md](../../architecture/coding-standards.md)
|
|
|
|
|
+
|
|
|
|
|
+- **代码风格**: TypeScript 严格模式,一致的缩进和命名
|
|
|
|
|
+- **组件类型**: 使用 React 函数式组件,使用 `React.FC<Props>` 类型
|
|
|
|
|
+- **Hooks**: 使用 useEffect、useRef、useMemo 等 Hooks 管理状态和副作用
|
|
|
|
|
+- **事件处理**: 使用箭头函数或 useCallback 处理事件
|
|
|
|
|
+- **导出规范**: 使用 ES6 `export` 语法导出组件和类型
|
|
|
|
|
+
|
|
|
|
|
+### 环境配置
|
|
|
|
|
+
|
|
|
|
|
+**来源**: [CLAUDE.md](../../CLAUDE.md)
|
|
|
|
|
+
|
|
|
|
|
+- **Node.js**: 20.19.2
|
|
|
|
|
+- **包管理器**: pnpm
|
|
|
|
|
+- **测试框架**: Jest(mini 包使用 Jest,不是 Vitest)
|
|
|
|
|
+- **类型检查**: 使用 `pnpm typecheck` 验证类型
|
|
|
|
|
+- **构建**: 使用 `pnpm build` 生成 .d.ts 声明文件
|
|
|
|
|
+
|
|
|
|
|
+### 文件位置规范
|
|
|
|
|
+
|
|
|
|
|
+所有新组件应创建在:
|
|
|
|
|
+- 组件目录: `mini-ui-packages/mini-charts/src/components/`
|
|
|
|
|
+- 组件导出: `mini-ui-packages/mini-charts/src/components/index.ts`
|
|
|
|
|
+- 主入口: `mini-ui-packages/mini-charts/src/index.ts`(需要更新)
|
|
|
|
|
+
|
|
|
|
|
+### 技术约束
|
|
|
|
|
+
|
|
|
|
|
+1. **React版本**: 必须使用 React 18 函数式组件,不能使用类组件
|
|
|
|
|
+2. **Taro版本**: 使用 @tarojs/taro 4.1.4 API
|
|
|
|
|
+3. **类型安全**: 避免使用 `any` 类型(除非必要)
|
|
|
|
|
+4. **Canvas上下文**: 使用 Taro.createCanvasContext 创建上下文
|
|
|
|
|
+5. **事件处理**: 正确处理触摸事件,支持 tooltip 和滚动
|
|
|
|
|
+6. **响应式设计**: 支持不同屏幕尺寸和像素比
|
|
|
|
|
+7. **清理逻辑**: useEffect cleanup 函数必须清理图表实例
|
|
|
|
|
+
|
|
|
|
|
+### 验证标准
|
|
|
|
|
+
|
|
|
|
|
+完成本故事后,应该满足:
|
|
|
|
|
+1. ✅ `src/components/` 目录下所有文件存在
|
|
|
|
|
+2. ✅ BaseChart 和5种图表组件有完整类型注解
|
|
|
|
|
+3. ✅ `src/index.ts` 正确导出所有组件
|
|
|
|
|
+4. ✅ 运行 `pnpm typecheck` 无类型错误
|
|
|
|
|
+5. ✅ 运行 `pnpm build` 成功,自动生成 .d.ts 声明文件
|
|
|
|
|
+6. ✅ 检查生成的 .d.ts 文件正确导出组件类型
|
|
|
|
|
+7. ✅ 组件可以被其他 mini 包引用使用
|
|
|
|
|
+
|
|
|
|
|
+### 不包含在本故事中的工作
|
|
|
|
|
+
|
|
|
|
|
+以下工作**不在**本故事范围内:
|
|
|
|
|
+- ❌ 创建使用示例和文档(故事 016.010)
|
|
|
|
|
+- ❌ 创建完整的测试套件(故事 016.011)
|
|
|
|
|
+- ❌ 在实际小程序页面中集成使用
|
|
|
|
|
+
|
|
|
|
|
+## Testing
|
|
|
|
|
+
|
|
|
|
|
+**测试框架**: Jest(mini 包使用 Jest,不是 Vitest)
|
|
|
|
|
+
|
|
|
|
|
+**测试要求**:
|
|
|
|
|
+1. 创建基础测试验证组件的正确导入和导出
|
|
|
|
|
+2. 测试组件渲染(可选)
|
|
|
|
|
+3. 测试 Props 传递(可选)
|
|
|
|
|
+4. 测试事件处理(可选)
|
|
|
|
|
+5. Mock Taro 和 Canvas 相关API
|
|
|
|
|
+
|
|
|
|
|
+**测试命令**:
|
|
|
|
|
+```bash
|
|
|
|
|
+# 运行所有测试
|
|
|
|
|
+pnpm test
|
|
|
|
|
+
|
|
|
|
|
+# 运行特定测试
|
|
|
|
|
+pnpm test --testNamePattern "图表组件"
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+**测试文件位置**: `tests/components/`
|
|
|
|
|
+
|
|
|
|
|
+## Change Log
|
|
|
|
|
+
|
|
|
|
|
+| Date | Version | Description | Author |
|
|
|
|
|
+|------|---------|-------------|--------|
|
|
|
|
|
+| 2025-12-24 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
|
|
|
|
|
+| 2025-12-24 | 1.1 | 更新状态为 Approved | Bob (Scrum Master) |
|
|
|
|
|
+
|
|
|
|
|
+## Dev Agent Record
|
|
|
|
|
+
|
|
|
|
|
+*此部分由开发代理在实施过程中填写*
|
|
|
|
|
+
|
|
|
|
|
+### Agent Model Used
|
|
|
|
|
+
|
|
|
|
|
+### Debug Log References
|
|
|
|
|
+
|
|
|
|
|
+### Completion Notes List
|
|
|
|
|
+
|
|
|
|
|
+### File List
|
|
|
|
|
+
|
|
|
|
|
+## QA Results
|
|
|
|
|
+
|
|
|
|
|
+*此部分由 QA 代理在审查完成后填写*
|