ソースを参照

docs(story): 创建故事016.002 - 搬迁配置和工具函数到独立模块并添加类型定义

- 搬迁 config、assign、util 对象到 config.ts
- 创建 utils/ 目录模块化工具函数
- 添加完整的 TypeScript 类型注解
- 包含 10 个主要任务和详细的类型定义示例
- 状态: Approved

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 週間 前
コミット
b4e41e08c7
1 ファイル変更602 行追加0 行削除
  1. 602 0
      docs/stories/016.002.modularize-utils.md

+ 602 - 0
docs/stories/016.002.modularize-utils.md

@@ -0,0 +1,602 @@
+# <!-- Powered by BMAD™ Core -->
+
+# Story 016.002: 搬迁配置和工具函数到独立模块并添加类型定义
+
+## Status
+
+Approved
+
+## Story
+
+**作为** 图表库开发者,
+**我想要** 将 u-charts 核心库中的配置和工具函数**搬迁**到独立模块并添加 TypeScript 类型定义,
+**以便** 提高代码的可维护性、可测试性和类型安全性,为后续 React 组件封装打下基础。
+
+## Acceptance Criteria
+
+1. `config.ts` 创建完成,包含 config、assign、util 并有完整类型注解
+2. `utils/` 目录下所有文件创建完成,每个函数都有类型注解
+3. 类型检查通过(pnpm typecheck),无 any 类型(除非必要)
+4. 每个文件控制在 500 行以内
+5. 代码逻辑与原始 u-charts.ts 完全一致
+
+## Tasks / Subtasks
+
+- [ ] Task 1: 分析配置和工具函数 (AC: 1, 2, 5)
+  - [ ] 1.1 识别 config、assign、util 等配置对象
+  - [ ] 1.2 识别颜色工具(hexToRgb等)
+  - [ ] 1.3 识别数学工具(findRange、normalInt等)
+  - [ ] 1.4 识别坐标转换工具(convertCoordinateOrigin等)
+  - [ ] 1.5 识别文本测量工具(measureText等)
+  - [ ] 1.6 识别碰撞检测工具(avoidCollision等)
+  - [ ] 1.7 识别其他工具函数(getH5Offset、isInAngleRange等)
+
+- [ ] Task 2: 创建模块化目录结构 (AC: 1, 2)
+  - [ ] 2.1 创建 `src/lib/config.ts`
+  - [ ] 2.2 创建 `src/lib/utils/` 目录
+  - [ ] 2.3 创建 `src/lib/utils/color.ts`
+  - [ ] 2.4 创建 `src/lib/utils/math.ts`
+  - [ ] 2.5 创建 `src/lib/utils/coordinate.ts`
+  - [ ] 2.6 创建 `src/lib/utils/text.ts`
+  - [ ] 2.7 创建 `src/lib/utils/collision.ts`
+  - [ ] 2.8 创建 `src/lib/utils/misc.ts`(其他工具函数)
+  - [ ] 2.9 创建 `src/lib/utils/index.ts` 统一导出
+
+- [ ] Task 3: **搬迁**配置对象并添加类型注解 (AC: 1, 3, 4, 5)
+  - [ ] 3.1 将 config 对象**搬迁**到 config.ts,添加类型注解
+  - [ ] 3.2 将 assign 函数**搬迁**到 config.ts,添加类型注解
+  - [ ] 3.3 将 util 对象**搬迁**到 config.ts,添加类型注解
+  - [ ] 3.4 为 config、assign、util 添加完整的 TypeScript 类型定义
+
+- [ ] Task 4: **搬迁**颜色工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 4.1 将 hexToRgb 函数**搬迁**到 utils/color.ts
+  - [ ] 4.2 为 hexToRgb 添加参数和返回值类型注解
+  - [ ] 4.3 导出函数并验证类型正确性
+
+- [ ] Task 5: **搬迁**数学工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 5.1 将 findRange 函数**搬迁**到 utils/math.ts
+  - [ ] 5.2 将 calCandleMA 函数**搬迁**到 utils/math.ts
+  - [ ] 5.3 为数学函数添加参数和返回值类型注解
+  - [ ] 5.4 导出函数并验证类型正确性
+
+- [ ] Task 6: **搬迁**坐标转换工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 6.1 将 convertCoordinateOrigin 函数**搬迁**到 utils/coordinate.ts
+  - [ ] 6.2 将 isInAngleRange 函数**搬迁**到 utils/coordinate.ts
+  - [ ] 6.3 为坐标转换函数添加参数和返回值类型注解
+  - [ ] 6.4 定义坐标点、中心点等类型接口
+  - [ ] 6.5 导出函数和类型并验证类型正确性
+
+- [ ] Task 7: **搬迁**文本测量工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 7.1 将 measureText 函数**搬迁**到 utils/text.ts
+  - [ ] 7.2 为 measureText 添加参数和返回值类型注解
+  - [ ] 7.3 定义文本度量相关类型接口
+  - [ ] 7.4 导出函数和类型并验证类型正确性
+
+- [ ] Task 8: **搬迁**碰撞检测工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 8.1 将 avoidCollision 函数**搬迁**到 utils/collision.ts
+  - [ ] 8.2 将 util.isCollision 函数**搬迁**到 utils/collision.ts
+  - [ ] 8.3 为碰撞检测函数添加参数和返回值类型注解
+  - [ ] 8.4 定义碰撞对象类型接口
+  - [ ] 8.5 导出函数和类型并验证类型正确性
+
+- [ ] Task 9: **搬迁**其他工具函数并添加类型注解 (AC: 2, 3, 4, 5)
+  - [ ] 9.1 将 getH5Offset 函数**搬迁**到 utils/misc.ts
+  - [ ] 9.2 将 createCurveControlPoints 函数**搬迁**到 utils/misc.ts
+  - [ ] 9.3 将 calValidDistance 函数**搬迁**到 utils/misc.ts
+  - [ ] 9.4 将 getTouches 函数**搬迁**到 utils/misc.ts
+  - [ ] 9.5 为其他工具函数添加参数和返回值类型注解
+  - [ ] 9.6 定义相关类型接口
+  - [ ] 9.7 导出函数和类型并验证类型正确性
+
+- [ ] Task 10: 更新导出和验证搬迁结果 (AC: 1, 2, 3, 5)
+  - [ ] 10.1 更新 src/lib/utils/index.ts 统一导出所有工具函数
+  - [ ] 10.2 更新 src/index.ts 导出新模块
+  - [ ] 10.3 运行类型检查验证所有类型注解正确
+  - [ ] 10.4 确保所有工具函数正确导出
+  - [ ] 10.5 确认代码逻辑与原始文件完全一致
+  - [ ] 10.6 验证每个文件控制在 500 行以内
+
+## Dev Notes
+
+### 前置故事见解
+
+**故事 016.001 完成状态**:
+- ✅ 已创建 `mini-charts` 包基础结构
+- ✅ 已迁移 u-charts 核心库到 `src/lib/u-charts.ts`(7680行代码)
+- ✅ 包可以成功构建和类型检查通过
+- ⚠️ 使用 `@ts-nocheck` 绕过原始库中的类型问题
+- ⚠️ 存在变量重复声明问题(将在本故事中通过模块化解决)
+
+**故事 016.001 关键技术决策**:
+- 使用 `@ts-nocheck` 绕过原始 u-charts 库的类型问题
+- 修复了 `process` 变量与 Node.js 全局对象的冲突(重命名为 `chartProcess`)
+- 修复了 `uChartsEvent` 类语法问题
+- 保留了原始 u-charts 库的所有功能逻辑
+
+**迁移策略**: 保持 u-charts 核心绘制逻辑不变,渐进式模块化并添加类型定义
+
+### 技术栈要求
+
+**来源**: [tech-stack.md](../../architecture/tech-stack.md)
+
+- **TypeScript**: 5.4.5 [来源: tech-stack.md]
+- **React**: 18.0.0(mini 包使用 React 18,不是 React 19)[来源: 故事 016.001 Dev Notes]
+- **Taro**: 4.1.4 [来源: tech-stack.md]
+- **Node.js**: 20.18.3(运行时环境)[来源: tech-stack.md]
+- **包管理器**: pnpm workspace [来源: source-tree.md]
+
+### 项目结构指南
+
+**来源**: [source-tree.md](../../architecture/source-tree.md)
+
+```
+mini-ui-packages/
+└── mini-charts/              # mini-charts 包
+    ├── src/
+    │   ├── index.ts          # 主入口文件
+    │   └── lib/
+    │       ├── u-charts.ts   # u-charts 核心库(7680行)
+    │       ├── config.ts     # [新增] 配置对象
+    │       └── utils/        # [新增] 工具函数目录
+    │           ├── index.ts  # 工具函数统一导出
+    │           ├── color.ts  # 颜色工具函数
+    │           ├── math.ts   # 数学工具函数
+    │           ├── coordinate.ts  # 坐标转换工具
+    │           ├── text.ts   # 文本测量工具
+    │           ├── collision.ts   # 碰撞检测工具
+    │           └── misc.ts   # 其他工具函数
+    ├── tests/
+    │   └── u-charts.test.ts
+    ├── 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",
+    "skipLibCheck": true,
+    "moduleResolution": "bundler",
+    "allowImportingTsExtensions": true,
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "react-jsx",
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true
+  },
+  "include": ["src"]
+}
+```
+
+**本故事重要**: 需要移除 `@ts-nocheck`,为所有函数添加完整的类型注解,确保 `strict: true` 模式下无类型错误。
+
+### u-charts 源文件分析
+
+**来源**: [mini-charts/src/lib/u-charts.ts](../../mini-ui-packages/mini-charts/src/lib/u-charts.ts)
+
+**文件统计**:
+- 总行数: 7680 行
+- 导出函数: 约 60+ 个工具/数据处理函数
+
+**配置对象**:
+1. **config**: 默认配置对象,包含版本、颜色、字体等配置
+2. **assign**: 深度合并对象的函数
+3. **util**: 工具函数集合(toFixed、isFloat、approximatelyEqual、isSameSign、isSameXCoordinateArea、isCollision)
+
+**颜色工具函数**:
+- `hexToRgb(hexValue, opc)`: 十六进制颜色转 RGBA
+
+**数学工具函数**:
+- `findRange(num, type, limit)`: 计算数值范围
+- `calCandleMA(dayArr, nameArr, colorArr, kdata)`: 计算 K 线图移动平均线
+- `createCurveControlPoints(points, i)`: 创建曲线控制点
+
+**坐标转换工具函数**:
+- `convertCoordinateOrigin(x, y, center)`: 坐标原点转换
+- `isInAngleRange(angle, startAngle, endAngle)`: 判断角度是否在范围内
+- `calValidDistance(self, distance, chartData, config, opts)`: 计算有效滚动距离
+
+**文本测量工具函数**:
+- `measureText(text, fontSize, context)`: 测量文本宽度
+
+**碰撞检测工具函数**:
+- `avoidCollision(obj, target)`: 避免碰撞
+- `util.isCollision(obj1, obj2)`: 检测两个对象是否碰撞
+
+**其他工具函数**:
+- `getH5Offset(e)`: 兼容 H5 点击事件
+- `getTouches(touches, opts, e)`: 获取触摸点坐标
+- `dataCombine(series)`: 数据合并
+- `dataCombineStack(series, len)`: 堆叠数据合并
+- `fixPieSeries(series, opts, config)`: 修复饼图系列数据
+- `fillSeries(series, opts, config)`: 填充系列数据
+- `fillCustomColor(linearType, customColor, series, config)`: 填充自定义颜色
+- `getDataRange(minData, maxData)`: 获取数据范围
+
+### 类型定义规范
+
+**来源**: [ui-package-standards.md](../../architecture/ui-package-standards.md#类型定义规范)
+
+**本故事重要**: 必须为所有函数添加完整的类型注解,避免使用 `any` 类型(除非必要)。
+
+**类型定义示例**:
+
+```typescript
+// config.ts 类型定义示例
+export interface UChartsConfig {
+  version: string;
+  yAxisWidth: number;
+  xAxisHeight: number;
+  padding: [number, number, number, number];
+  rotate: boolean;
+  fontSize: number;
+  fontColor: string;
+  dataPointShape: string[];
+  color: string[];
+  linearColor: string[];
+  pieChartLinePadding: number;
+  pieChartTextPadding: number;
+  titleFontSize: number;
+  subtitleFontSize: number;
+  radarLabelTextMargin: number;
+}
+
+export const config: UChartsConfig = {
+  version: 'v2.5.0-20230101',
+  yAxisWidth: 15,
+  // ... 其他配置
+};
+
+export function assign<T>(target: T, ...varArgs: Partial<T>[]): T {
+  // 实现逻辑
+}
+
+export interface Util {
+  toFixed(num: number, limit?: number): number | string;
+  isFloat(num: number): boolean;
+  approximatelyEqual(num1: number, num2: number): boolean;
+  isSameSign(num1: number, num2: number): boolean;
+  isSameXCoordinateArea(p1: Point, p2: Point): boolean;
+  isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean;
+}
+
+export const util: Util = {
+  toFixed: function toFixed(num, limit = 2) {
+    // 实现逻辑
+  },
+  // ... 其他工具函数
+};
+```
+
+```typescript
+// utils/color.ts 类型定义示例
+export interface RgbColor {
+  r: number;
+  g: number;
+  b: number;
+}
+
+export function hexToRgb(hexValue: string, opc: number): string {
+  // 实现逻辑
+}
+```
+
+```typescript
+// utils/math.ts 类型定义示例
+export function findRange(num: number, type?: 'upper' | 'lower', limit?: number): number {
+  // 实现逻辑
+}
+
+export interface CandleDataPoint {
+  [key: string]: number;
+}
+
+export interface MASeriesItem {
+  data: (number | null)[];
+  name: string;
+  color: string;
+}
+
+export function calCandleMA(
+  dayArr: number[],
+  nameArr: string[],
+  colorArr: string[],
+  kdata: CandleDataPoint[]
+): MASeriesItem[] {
+  // 实现逻辑
+}
+```
+
+```typescript
+// utils/coordinate.ts 类型定义示例
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export interface Center {
+  x: number;
+  y: number;
+}
+
+export function convertCoordinateOrigin(x: number, y: number, center: Center): Point {
+  // 实现逻辑
+}
+
+export function isInAngleRange(
+  angle: number,
+  startAngle: number,
+  endAngle: number
+): boolean {
+  // 实现逻辑
+}
+```
+
+```typescript
+// utils/text.ts 类型定义示例
+export interface TextMeasure {
+  width: number;
+}
+
+export function measureText(
+  text: string,
+  fontSize: number,
+  context: CanvasRenderingContext2D
+): TextMeasure {
+  // 实现逻辑
+}
+```
+
+```typescript
+// utils/collision.ts 类型定义示例
+export interface CollisionObject {
+  start: Point;
+  end?: Point;
+  width: number;
+  height: number;
+  center?: Point;
+  area?: {
+    start: Point;
+    end: Point;
+    width: number;
+    height: number;
+  };
+}
+
+export interface Point {
+  x: number;
+  y: number;
+}
+
+export function avoidCollision(
+  obj: CollisionObject,
+  target: CollisionObject[]
+): CollisionObject[] {
+  // 实现逻辑
+}
+
+export function isCollision(obj1: CollisionObject, obj2: CollisionObject): boolean {
+  // 实现逻辑(来自 util.isCollision)
+}
+```
+
+```typescript
+// utils/misc.ts 类型定义示例
+export interface H5Event {
+  offsetX: number;
+  offsetY: number;
+  mp: {
+    changedTouches: Array<{ x: number; y: number }>;
+  };
+}
+
+export function getH5Offset(e: { offsetX: number; offsetY: number }): H5Event {
+  // 实现逻辑
+}
+
+export interface TouchPoint {
+  x: number;
+  y: number;
+}
+
+export function getTouches(
+  touches: TouchPoint[],
+  opts: { width: number; area: number[] },
+  e: any
+): TouchPoint[] {
+  // 实现逻辑
+}
+```
+
+### 编码标准
+
+**来源**: [coding-standards.md](../../architecture/coding-standards.md)
+
+- **代码风格**: TypeScript 严格模式,一致的缩进和命名
+- **类型注解**: 必须为所有函数参数和返回值添加类型注解
+- **接口定义**: 为复杂数据结构定义 TypeScript 接口
+- **导出规范**: 使用 ES6 `export` 语法导出函数和类型
+
+### 测试策略
+
+**来源**: [testing-strategy.md](../../architecture/testing-strategy.md)
+
+**测试框架**: Jest(mini 包使用 Jest,不是 Vitest)
+
+**测试位置**: `tests/` 目录(与源码并列)
+
+**测试类型**:
+- 单元测试: 测试工具函数的正确性
+- **不要求**集成测试和 E2E 测试(这些在后续故事中)
+
+**测试要求**:
+1. 为每个工具函数编写单元测试
+2. 测试覆盖率目标: 基础测试即可,不要求高覆盖率
+3. Mock Canvas 上下文以支持依赖 Canvas 的函数测试
+
+**测试命令**:
+```bash
+# 进入包目录
+cd mini-ui-packages/mini-charts
+
+# 运行所有测试
+pnpm test
+
+# 运行特定测试
+pnpm test --testNamePattern "工具函数测试"
+
+# 类型检查
+pnpm typecheck
+```
+
+### 构建和发布规范
+
+**来源**: [ui-package-standards.md](../../architecture/ui-package-standards.md#构建和发布规范)
+
+mini-charts 包在 PNPM 工作空间中:
+- **开发模式**: 直接使用 TypeScript 源码
+- **构建**: 使用 `tsc` 编译到 `dist/` 目录
+- **类型检查**: 使用 `tsc --noEmit` 验证类型
+- **发布**: 本故事不要求发布,仅验证本地构建成功
+
+**重要**: 本故事完成后,应该可以:
+1. ✅ 运行 `pnpm build` 成功生成 `dist/` 目录
+2. ✅ 运行 `pnpm typecheck` 无类型错误
+3. ✅ 所有新模块正确导出,可以被主入口文件引用
+
+### 开发流程
+
+**来源**: [development-workflow.md](../../architecture/development-workflow.md)
+
+**开发命令**:
+```bash
+# 进入包目录
+cd mini-ui-packages/mini-charts
+
+# 安装依赖(在根目录)
+pnpm install
+
+# 开发模式(监听文件变化)
+pnpm dev
+
+# 类型检查
+pnpm typecheck
+
+# 运行测试
+pnpm test
+
+# 构建
+pnpm build
+```
+
+### 环境配置
+
+**来源**: [CLAUDE.md](../../CLAUDE.md)
+
+- **Node.js**: 20.19.2
+- **包管理器**: pnpm
+- **测试调试**: 使用 `pnpm test --testNamePattern "测试名称"` 运行特定测试
+- **Mini 测试**: 需要 Jest,不是 Vitest
+- **类型检查**: 使用 `pnpm typecheck` 加 grep 来过滤要检查的指定文件
+
+### 文件位置规范
+
+**来源**: [source-tree.md](../../architecture/source-tree.md)
+
+所有新文件应创建在:
+- 配置文件: `mini-ui-packages/mini-charts/src/lib/config.ts`
+- 工具函数目录: `mini-ui-packages/mini-charts/src/lib/utils/`
+- 工具函数文件:
+  - `color.ts`: 颜色工具函数
+  - `math.ts`: 数学工具函数
+  - `coordinate.ts`: 坐标转换工具
+  - `text.ts`: 文本测量工具
+  - `collision.ts`: 碰撞检测工具
+  - `misc.ts`: 其他工具函数
+  - `index.ts`: 工具函数统一导出
+- 主入口: `mini-ui-packages/mini-charts/src/index.ts`(需要更新导出)
+- 测试文件: `mini-ui-packages/mini-charts/tests/`(可选,本故事不强制要求)
+
+### 技术约束
+
+1. **搬迁原则**: 保持代码逻辑完全不变,只改变文件组织方式
+2. **类型注解**: 只在搬迁过程中添加 TypeScript 类型注解,不修改代码的实现逻辑
+3. **文件大小**: 每个文件控制在 500 行以内
+4. **类型安全**: 避免使用 `any` 类型(除非必要)
+5. **向后兼容**: 保持与原始 u-charts.ts 的功能兼容性
+
+### 验证标准
+
+完成本故事后,应该满足:
+1. ✅ `src/lib/config.ts` 文件存在,包含 config、assign、util 并有完整类型注解
+2. ✅ `src/lib/utils/` 目录下所有文件存在,每个函数都有类型注解
+3. ✅ 运行 `pnpm typecheck` 无类型错误
+4. ✅ 每个文件控制在 500 行以内
+5. ✅ 代码逻辑与原始 u-charts.ts 完全一致
+6. ✅ 所有新模块正确导出,可以被主入口文件引用
+
+### 不包含在本故事中的工作
+
+**来源**: [epic-016 故事列表](../../prd/epic-016-mini-charts-package.md#故事列表)
+
+以下工作**不在**本故事范围内:
+- ❌ 数据处理函数模块化(故事 016.003)
+- ❌ 图表数据点计算函数模块化(故事 016.004)
+- ❌ 绘制函数模块化(故事 016.005)
+- ❌ 核心类模块化(故事 016.006)
+- ❌ React 组件封装(故事 016.007)
+
+### Testing
+
+**测试框架**: Jest(不是 Vitest)
+
+**测试位置**: `tests/` 目录(与源码并列)
+
+**测试要求**:
+1. 创建基础测试验证工具函数的正确导入和导出
+2. 测试工具函数的基本功能(可选,本故事不强制要求)
+3. Mock Canvas 上下文以支持依赖 Canvas 的函数测试(如需要)
+4. 测试覆盖率目标: 基础测试即可,不要求高覆盖率
+
+**测试命令**:
+```bash
+# 运行所有测试
+pnpm test
+
+# 运行特定测试
+pnpm test --testNamePattern "配置和工具函数测试"
+```
+
+**参考**: [testing-strategy.md](../../architecture/testing-strategy.md)
+
+## Change Log
+
+| Date | Version | Description | Author |
+|------|---------|-------------|--------|
+| 2025-12-24 | 1.0 | 创建故事文档 | Bob (Scrum Master) |
+
+## Dev Agent Record
+
+*此部分由开发代理在实施过程中填写*
+
+### Agent Model Used
+
+### Debug Log References
+
+### Completion Notes List
+
+### File List
+
+## QA Results
+
+*此部分由 QA 代理在审查完成后填写*