diff --git a/.gitignore b/.gitignore index c5eb473..d19ef9c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,11 @@ stats-*.json .wxt .vitest +# Environment variables +.env +.env.local +.env.*.local + # Editor directories and files .vscode/* !.vscode/extensions.json @@ -30,5 +35,16 @@ stats-*.json .qoder/* dev/* -docs/* -!docs/VISUAL_STYLE_GUIDE.md +# Build outputs +dist/ +build/ + +# Test coverage +coverage/ + +# IDE files +*.code-workspace + +# Other +*.tsbuildinfo +*.tsxbuildinfo diff --git a/docs/test-data-generator/README.md b/docs/test-data-generator/README.md new file mode 100644 index 0000000..8681be1 --- /dev/null +++ b/docs/test-data-generator/README.md @@ -0,0 +1,21 @@ +# 测试数据生成器 - 功能设计文档 + +> 版本: v1.0 +> 创建时间: 2024-01-20 +> 状态: 待 Review + +## 产品定位 + +**轻量级可视化测试数据生成器** + +- 纯前端工具,无需后端 +- 可视化配置,无需编码 +- 本地生成,数据安全 + +## 目录 + +- [核心功能](./core-features.md) +- [生成器库](./generators.md) +- [规则管理](./rule-management.md) +- [界面设计](./ui-design.md) +- [技术实现](./technical-implementation.md) diff --git a/docs/test-data-generator/core-features.md b/docs/test-data-generator/core-features.md new file mode 100644 index 0000000..50f7979 --- /dev/null +++ b/docs/test-data-generator/core-features.md @@ -0,0 +1,122 @@ +# 核心功能 + +## 功能概览 + +| 功能模块 | 功能点 | 说明 | 优先级 | +| -------------- | --------------- | ------------------ | ------ | +| **字段配置** | 添加字段 | 可视化添加数据字段 | P0 | +| | 删除字段 | 删除不需要的字段 | P0 | +| | 字段排序 | 拖拽调整字段顺序 | P1 | +| | 字段命名 | 自定义字段名称 | P0 | +| **生成器系统** | 生成器选择 | 从生成器库中选择 | P0 | +| | 参数配置 | 配置生成器参数 | P0 | +| | 实时预览 | 配置时查看生成结果 | P1 | +| **数据生成** | 批量生成 | 支持 10000+ 条数据 | P0 | +| | 唯一性约束 | 保证字段值唯一 | P1 | +| | 数据预览 | 生成前预览数据 | P1 | +| **数据导出** | JSON 导出 | 导出为 JSON 格式 | P0 | +| | CSV 导出 | 导出为 CSV 格式 | P0 | +| | SQL 导出 | 导出为 INSERT 语句 | P1 | +| | TypeScript 导出 | 导出为 TS 类型定义 | P2 | + +## 功能详情 + +### 1. 字段配置 + +#### 添加字段 + +- 用户点击"添加字段"按钮 +- 弹出生成器选择器 +- 选择生成器后,自动创建新字段 +- 字段默认名称为生成器名称 + +#### 删除字段 + +- 每个字段右侧有删除按钮 +- 点击后弹出确认对话框 +- 确认后删除该字段 + +#### 字段排序 + +- 支持拖拽排序 +- 支持上移/下移按钮 +- 排序后预览数据同步更新 + +#### 字段命名 + +- 字段名称可自定义 +- 支持中英文命名 +- 同一规则内字段名不可重复 + +--- + +### 2. 生成器系统 + +#### 生成器选择 + +- 以分类方式展示生成器 +- 支持搜索生成器 +- 点击选择后添加到字段配置 + +#### 参数配置 + +- 每个生成器有对应的参数配置界面 +- 必填参数标记星号 +- 修改参数后实时预览更新 + +#### 实时预览 + +- 配置参数时,右侧预览区实时更新 +- 显示 5-10 条示例数据 +- 帮助用户确认配置是否正确 + +--- + +### 3. 数据生成 + +#### 批量生成 + +- 支持配置生成数量 +- 使用 Web Worker 后台生成 +- 支持 10000+ 条数据 +- 生成过程中显示进度条 + +#### 唯一性约束 + +- 每个字段可单独设置唯一性约束 +- 启用后,该字段生成的值不会重复 +- 如果无法生成唯一值,使用重试机制 + +#### 数据预览 + +- 生成前可预览数据 +- 显示前 10 条数据 +- 支持分页查看更多数据 + +--- + +### 4. 数据导出 + +#### JSON 导出 + +- 导出为标准 JSON 格式 +- 支持压缩和格式化两种模式 +- 包含字段名和数据 + +#### CSV 导出 + +- 导出为 CSV 格式 +- 自动处理逗号转义 +- 支持中文编码 (UTF-8 with BOM) + +#### SQL 导出 + +- 导出为 INSERT 语句 +- 支持自定义表名 +- 自动处理特殊字符转义 + +#### TypeScript 导出 + +- 导出为 TypeScript 类型定义 +- 根据字段类型自动推断 +- 可选是否包含示例数据 diff --git a/docs/test-data-generator/generators.md b/docs/test-data-generator/generators.md new file mode 100644 index 0000000..ad4abf7 --- /dev/null +++ b/docs/test-data-generator/generators.md @@ -0,0 +1,496 @@ +# 生成器库 + +## 分类概览 + +| 分类 | 生成器数量 | 说明 | +| ----------- | ---------- | ------------------------ | +| 👤 个人信息 | 5 个 | 中文姓名、邮箱、手机号等 | +| 💼 业务数据 | 4 个 | 订单号、价格、日期等 | +| 🔧 技术数据 | 3 个 | UUID、IP地址、URL | +| 📝 基础类型 | 4 个 | 整数、浮点数、字符串等 | +| **总计** | **16 个** | | + +--- + +## 👤 个人信息生成器 + +### 1. 中文姓名 (chineseName) + +**说明**: 生成中文姓名,如张三、李四 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| --------------- | ---------------------------- | ---- | ------------------ | ------------ | +| surnamePool | string \| string[] | 否 | 百家姓前100个 | 姓氏池 | +| givenNameLength | { min: number, max: number } | 否 | { min: 1, max: 2 } | 名字长度范围 | + +**示例**: + +```json +{ + "name": "name", + "generator": "chineseName", + "params": { + "surnamePool": ["张", "王", "李", "赵"], + "givenNameLength": { "min": 1, "max": 2 } + } +} +``` + +**生成结果**: 张伟, 李娜, 王强, 赵敏 + +--- + +### 2. 邮箱地址 (email) + +**说明**: 生成邮箱地址 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| --------- | -------------------- | ---- | ------------------------------------------------------------ | ---------- | +| domains | string[] | 否 | ['qq.com', '163.com', '126.com', 'gmail.com', 'outlook.com'] | 域名列表 | +| nameStyle | 'pinyin' \| 'random' | 否 | 'pinyin' | 用户名风格 | + +**示例**: + +```json +{ + "name": "email", + "generator": "email", + "params": { + "domains": ["qq.com", "163.com"], + "nameStyle": "pinyin" + } +} +``` + +**生成结果**: zhangwei@qq.com, lina@163.com + +--- + +### 3. 中国手机号 (chinesePhone) + +**说明**: 生成 11 位中国手机号 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------ | -------- | ---- | -------------- | ---------- | +| prefix | string[] | 否 | 常见手机号前缀 | 手机号前缀 | + +**示例**: + +```json +{ + "name": "phone", + "generator": "chinesePhone", + "params": { + "prefix": ["138", "139", "150", "151"] + } +} +``` + +**生成结果**: 13812345678, 15087654321 + +--- + +### 4. 身份证号 (idCard) + +**说明**: 生成 18 位中国身份证号 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------ | ------ | ---- | ------ | ------ | +| region | string | 否 | 随机 | 地区码 | + +**示例**: + +```json +{ + "name": "idCard", + "generator": "idCard", + "params": { + "region": "110101" + } +} +``` + +**生成结果**: 110101199001011234 + +--- + +### 5. 中文地址 (chineseAddress) + +**说明**: 生成中国地址 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------ | ------------------------------ | ---- | ------ | -------- | +| level | 'full' \| 'province' \| 'city' | 否 | 'full' | 详细程度 | + +**示例**: + +```json +{ + "name": "address", + "generator": "chineseAddress", + "params": { + "level": "full" + } +} +``` + +**生成结果**: 北京市朝阳区建国路88号 + +--- + +## 💼 业务数据生成器 + +### 1. 订单号 (orderId) + +**说明**: 生成订单号 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------------ | ------- | ---- | ------ | ------------ | +| prefix | string | 否 | 'ORD' | 前缀 | +| dateIncluded | boolean | 否 | true | 是否包含日期 | + +**示例**: + +```json +{ + "name": "orderId", + "generator": "orderId", + "params": { + "prefix": "ORD", + "dateIncluded": true + } +} +``` + +**生成结果**: ORD20240115001, ORD20240115002 + +--- + +### 2. 价格 (price) + +**说明**: 生成价格,保留 2 位小数 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ----------------------- | ---- | ------- | -------- | +| min | number | 否 | 0.01 | 最小值 | +| max | number | 否 | 9999.99 | 最大值 | +| currency | 'CNY' \| 'USD' \| 'EUR' | 否 | 'CNY' | 货币类型 | + +**示例**: + +```json +{ + "name": "amount", + "generator": "price", + "params": { + "min": 10, + "max": 5000, + "currency": "CNY" + } +} +``` + +**生成结果**: 299.00, 1599.50, 0.01 + +--- + +### 3. 日期时间 (date) + +**说明**: 生成日期时间 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------ | ------ | ---- | --------------------- | -------- | +| format | string | 否 | 'YYYY-MM-DD HH:mm:ss' | 日期格式 | +| min | string | 否 | '2020-01-01' | 最早日期 | +| max | string | 否 | 当前日期 | 最晚日期 | + +**示例**: + +```json +{ + "name": "createdAt", + "generator": "date", + "params": { + "format": "YYYY-MM-DD HH:mm:ss", + "min": "2024-01-01" + } +} +``` + +**生成结果**: 2024-01-15 14:30:22 + +--- + +### 4. 状态 (status) + +**说明**: 从选项中随机选择 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------- | -------- | ---- | -------- | ------------ | +| options | string[] | 是 | - | 状态选项列表 | +| weights | number[] | 否 | 均匀分布 | 各选项权重 | + +**示例**: + +```json +{ + "name": "status", + "generator": "status", + "params": { + "options": ["pending", "paid", "shipped", "completed"], + "weights": [30, 40, 20, 10] + } +} +``` + +**生成结果**: pending, paid, shipped, completed + +--- + +## 🔧 技术数据生成器 + +### 1. UUID (uuid) + +**说明**: 生成 UUID + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------- | ------------ | ---- | ------ | --------- | +| version | 'v4' \| 'v1' | 否 | 'v4' | UUID 版本 | + +**示例**: + +```json +{ + "name": "id", + "generator": "uuid", + "params": { + "version": "v4" + } +} +``` + +**生成结果**: 550e8400-e29b-41d4-a716-446655440000 + +--- + +### 2. IPv4 地址 (ipv4) + +**说明**: 生成 IPv4 地址 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------- | ------- | ---- | ------ | ------------ | +| private | boolean | 否 | false | 是否私有地址 | + +**示例**: + +```json +{ + "name": "ip", + "generator": "ipv4", + "params": { + "private": true + } +} +``` + +**生成结果**: 192.168.1.1, 10.0.0.1 + +--- + +### 3. URL (url) + +**说明**: 生成 URL + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ----------------- | ---- | ------------- | ---- | +| protocol | 'http' \| 'https' | 否 | 'https' | 协议 | +| domain | string | 否 | 'example.com' | 域名 | + +**示例**: + +```json +{ + "name": "website", + "generator": "url", + "params": { + "protocol": "https", + "domain": "shop.com" + } +} +``` + +**生成结果**: https://shop.com/product/123 + +--- + +## 📝 基础类型生成器 + +### 1. 随机整数 (randomInt) + +**说明**: 生成指定范围的随机整数 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------ | ------ | ---- | ------ | ------ | +| min | number | 是 | - | 最小值 | +| max | number | 是 | - | 最大值 | + +**示例**: + +```json +{ + "name": "age", + "generator": "randomInt", + "params": { + "min": 18, + "max": 60 + } +} +``` + +**生成结果**: 25, 42, 18, 60 + +--- + +### 2. 随机浮点数 (randomFloat) + +**说明**: 生成指定范围的随机浮点数 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| -------- | ------ | ---- | ------ | -------- | +| min | number | 是 | - | 最小值 | +| max | number | 是 | - | 最大值 | +| decimals | number | 否 | 2 | 小数位数 | + +**示例**: + +```json +{ + "name": "score", + "generator": "randomFloat", + "params": { + "min": 0, + "max": 100, + "decimals": 1 + } +} +``` + +**生成结果**: 85.3, 42.7, 99.9 + +--- + +### 3. 随机字符串 (randomString) + +**说明**: 生成指定长度的随机字符串 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------- | ------ | ---- | -------------- | ---------- | +| length | number | 是 | - | 字符串长度 | +| charset | string | 否 | 'alphanumeric' | 字符集 | + +**示例**: + +```json +{ + "name": "code", + "generator": "randomString", + "params": { + "length": 8, + "charset": "alphanumeric" + } +} +``` + +**生成结果**: aB3kL9mN, xY7zW2pQ + +--- + +### 4. 从列表选择 (fromList) + +**说明**: 从选项列表中随机选择 + +**参数**: + +| 参数名 | 类型 | 必填 | 默认值 | 说明 | +| ------- | -------- | ---- | -------- | -------- | +| options | any[] | 是 | - | 选项列表 | +| weights | number[] | 否 | 均匀分布 | 权重 | + +**示例**: + +```json +{ + "name": "category", + "generator": "fromList", + "params": { + "options": ["电子产品", "服装", "食品", "图书"], + "weights": [40, 30, 20, 10] + } +} +``` + +**生成结果**: 电子产品, 服装, 食品 + +--- + +## 生成器选择界面 + +### 界面设计 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 🔧 选择生成器 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 🔍 搜索: [搜索生成器... ] │ +│ │ +│ 📁 分类筛选 │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 全部 │ │ 个人信息 │ │ 业务数据 │ │ 技术数据 │ │ 基础类型 │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +│ 📋 生成器列表 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 👤 个人信息 │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ 👤 中文姓名 生成中文姓名,如张三 │ │ │ +│ │ │ 📧 邮箱地址 生成邮箱地址 │ │ │ +│ │ │ 📱 中国手机号 生成11位手机号 │ │ │ +│ │ │ 🆔 身份证号 生成18位身份证号 │ │ │ +│ │ │ 🏠 中文地址 生成中国地址 │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` diff --git a/docs/test-data-generator/rule-management.md b/docs/test-data-generator/rule-management.md new file mode 100644 index 0000000..e2f13f6 --- /dev/null +++ b/docs/test-data-generator/rule-management.md @@ -0,0 +1,493 @@ +# 规则管理 + +## 功能概览 + +| 功能 | 说明 | 优先级 | +| -------- | -------------------- | ------ | +| 保存规则 | 将当前配置保存为模板 | P0 | +| 规则列表 | 查看所有已保存的规则 | P0 | +| 规则搜索 | 按关键词搜索规则 | P1 | +| 标签筛选 | 按标签分类筛选规则 | P1 | +| 规则编辑 | 修改已保存的规则 | P0 | +| 规则删除 | 删除不需要的规则 | P0 | +| 规则加载 | 一键加载规则配置 | P0 | +| 规则复制 | 复制规则配置 | P1 | +| 规则导出 | 导出为 JSON 文件 | P1 | +| 规则导入 | 从 JSON 文件导入 | P1 | + +--- + +## 数据结构 + +### 规则模板 + +```typescript +interface RuleTemplate { + id: string; // 唯一标识 + name: string; // 规则名称 + description?: string; // 规则描述 + tags: string[]; // 标签列表 + fields: FieldConfig[]; // 字段配置 + options: { + total: number; // 生成数量 + format: 'json' | 'csv' | 'sql' | 'typescript'; + }; + metadata: { + createdAt: number; // 创建时间 + updatedAt: number; // 更新时间 + lastUsedAt?: number; // 最后使用时间 + useCount: number; // 使用次数 + }; +} +``` + +### 字段配置 + +```typescript +interface FieldConfig { + id: string; // 字段唯一标识 + name: string; // 字段名 + generator: string; // 生成器名称 + params: Record; // 生成器参数 + unique: boolean; // 唯一性约束 +} +``` + +--- + +## 功能详情 + +### 1. 保存规则 + +**触发方式**: 用户点击"保存规则"按钮 + +**界面设计**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 💾 保存规则 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📝 规则信息 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 规则名称: │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ [电商用户数据 - 测试用 ]│ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ 规则描述 (可选): │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ [用于测试用户注册功能,包含姓名、邮箱、手机号 ]│ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ 标签 (可选): │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ [电商] [用户] [测试数据] [+ 添加标签] │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📊 规则预览 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 字段数量: 4 个 │ │ +│ │ • name (中文姓名) │ │ +│ │ • email (邮箱地址) │ │ +│ │ • phone (手机号) │ │ +│ │ • age (年龄) │ │ +│ │ │ │ +│ │ 生成数量: 100 条 │ │ +│ │ 数据格式: JSON │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [💾 保存] [❌ 取消] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**保存逻辑**: + +1. 验证规则名称不为空 +2. 生成唯一 ID +3. 设置创建时间和更新时间 +4. 初始化使用次数为 0 +5. 保存到 localStorage + +--- + +### 2. 规则列表 + +**界面设计**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📚 规则管理 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 🔍 搜索: [搜索规则名称或标签... ] │ +│ │ +│ 📁 标签筛选 │ +│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ +│ │ 全部 │ │ 电商 │ │ 用户 │ │ 测试数据 │ │ 最近使用 │ │ +│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ +│ │ +│ 📋 规则列表 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────────────────┐ │ │ +│ │ │ 📦 电商用户数据 - 测试用 │ │ │ +│ │ │ ─────────────────────────────────────────────────────── │ │ │ +│ │ │ 📝 用于测试用户注册功能,包含姓名、邮箱、手机号 │ │ │ +│ │ │ 🏷️ 电商 用户 测试数据 │ │ │ +│ │ │ 📊 4 个字段 | 100 条 | JSON │ │ │ +│ │ │ 📅 2024-01-15 14:30 创建 | 2024-01-20 10:15 最后使用 │ │ │ +│ │ │ │ │ │ +│ │ │ [📥 加载] [📋 复制] [📤 导出] [✏️ 编辑] [🗑️ 删除] │ │ │ +│ │ └─────────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [📥 导入规则] [📤 导出全部] [🗑️ 清空全部] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**列表项信息**: + +- 规则名称 +- 规则描述 +- 标签 +- 字段数量、生成数量、数据格式 +- 创建时间、最后使用时间 +- 操作按钮 + +--- + +### 3. 规则搜索 + +**搜索逻辑**: + +- 支持按规则名称搜索 +- 支持按规则描述搜索 +- 支持按标签搜索 +- 搜索为模糊匹配,不区分大小写 + +**实现方式**: + +```typescript +search(keyword: string): RuleTemplate[] { + const rules = this.getAll(); + const lowerKeyword = keyword.toLowerCase(); + + return rules.filter(r => + r.name.toLowerCase().includes(lowerKeyword) || + r.description?.toLowerCase().includes(lowerKeyword) || + r.tags.some(t => t.toLowerCase().includes(lowerKeyword)) + ); +} +``` + +--- + +### 4. 标签筛选 + +**标签来源**: + +- 从所有已保存的规则中提取 +- 自动去重 +- 按使用频率排序 + +**筛选逻辑**: + +- 点击标签,筛选包含该标签的规则 +- 支持多标签筛选(交集) +- "全部"标签显示所有规则 + +--- + +### 5. 规则编辑 + +**界面设计**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ ✏️ 编辑规则 - 电商用户数据 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📝 基本信息 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 规则名称: [电商用户数据 - 测试用 ]│ │ +│ │ 规则描述: [用于测试用户注册功能,包含姓名、邮箱、手机号 ]│ │ +│ │ 标签: [电商] [用户] [测试数据] [+ 添加标签] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📋 字段配置 (可直接编辑) │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ 1. name [中文姓名 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 2. email [邮箱地址 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 3. phone [手机号 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 4. age [年龄 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ │ │ +│ │ [+ 添加字段] │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ ⚙️ 生成选项 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 数量: [100] 格式: [JSON ▼] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [💾 保存修改] [❌ 取消] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**编辑逻辑**: + +1. 加载原规则配置到编辑器 +2. 用户修改配置 +3. 点击保存时更新规则 +4. 更新 metadata.updatedAt +5. 保存到 localStorage + +--- + +### 6. 规则删除 + +**删除流程**: + +1. 点击删除按钮 +2. 弹出确认对话框 +3. 确认后删除规则 +4. 刷新规则列表 + +**确认对话框**: + +``` +┌─────────────────────────────────────────┐ +│ ⚠️ 确认删除 │ +├─────────────────────────────────────────┤ +│ │ +│ 确定要删除规则 "电商用户数据" 吗? │ +│ │ +│ 此操作不可撤销。 │ +│ │ +│ [❌ 取消] [🗑️ 删除] │ +│ │ +└─────────────────────────────────────────┘ +``` + +--- + +### 7. 规则加载 + +**加载流程**: + +1. 点击"加载"按钮 +2. 将规则配置应用到当前编辑器 +3. 更新预览数据 +4. 记录使用时间和次数 + +**实现方式**: + +```typescript +loadRule(ruleId: string): void { + const rule = this.storage.getById(ruleId); + + // 应用规则配置 + this.setFields(rule.fields); + this.setOptions(rule.options); + + // 记录使用 + this.storage.recordUse(ruleId); + + // 更新预览 + this.updatePreview(); +} +``` + +--- + +### 8. 规则复制 + +**复制逻辑**: + +1. 点击"复制"按钮 +2. 创建规则的副本 +3. 名称添加"(副本)"后缀 +4. 生成新的 ID +5. 保存为新规则 + +--- + +### 9. 规则导出 + +**导出界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📤 导出规则 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📋 选择要导出的规则: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ ☑️ 电商用户数据 - 测试用 (4个字段, 100条) │ │ +│ │ ☑️ 电商订单数据 - 测试用 (6个字段, 500条) │ │ +│ │ ☐ 用户登录数据 - 测试用 (3个字段, 50条) │ │ +│ │ │ │ +│ │ [全选] [全不选] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📤 导出格式: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ ○ 导出为单个 JSON 文件 (所有规则合并) │ │ +│ │ ● 导出为多个 JSON 文件 (每个规则一个文件) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [📤 导出] [❌ 取消] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**导出格式**: + +```json +{ + "version": "1.0", + "exportedAt": "2024-01-20T10:15:45.000Z", + "rules": [ + { + "id": "rule_123456", + "name": "电商用户数据 - 测试用", + "description": "用于测试用户注册功能", + "tags": ["电商", "用户", "测试数据"], + "fields": [...], + "options": {...}, + "metadata": {...} + } + ] +} +``` + +--- + +### 10. 规则导入 + +**导入界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📥 导入规则 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📤 选择导入方式: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ │ │ +│ │ ○ 从文件导入 │ │ +│ │ ┌─────────────────────────────────────────────────────┐ │ │ +│ │ │ 📁 拖拽文件到这里,或 [点击选择文件] │ │ │ +│ │ │ 支持格式: .json │ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ ○ 从剪贴板粘贴 │ │ +│ │ ┌─────────────────────────────────────────────────────┐ │ │ +│ │ │ 粘贴 JSON 规则内容... │ │ │ +│ │ └─────────────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📊 预览导入内容: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ ✅ 格式验证通过 │ │ +│ │ 规则数量: 2 个 │ │ +│ │ • 电商用户数据 (4个字段) │ │ +│ │ • 电商订单数据 (6个字段) │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [📥 导入] [❌ 取消] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**导入逻辑**: + +1. 解析 JSON 文件 +2. 验证格式是否正确 +3. 预览导入内容 +4. 确认后逐条导入 +5. 生成新 ID 避免冲突 +6. 处理导入结果(成功/失败数量) + +--- + +## 存储机制 + +### 本地存储 + +使用 localStorage 存储规则数据: + +```typescript +class RuleStorage { + private readonly STORAGE_KEY = 'testDataGenerator_rules'; + + // 获取所有规则 + getAll(): RuleTemplate[] { + const data = localStorage.getItem(this.STORAGE_KEY); + return data ? JSON.parse(data) : []; + } + + // 保存规则 + save(rule: RuleTemplate): void { + const rules = this.getAll(); + rules.push(rule); + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(rules)); + } + + // 更新规则 + update(id: string, updates: Partial): void { + const rules = this.getAll(); + const index = rules.findIndex((r) => r.id === id); + if (index !== -1) { + rules[index] = { ...rules[index], ...updates }; + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(rules)); + } + } + + // 删除规则 + delete(id: string): void { + const rules = this.getAll(); + const filtered = rules.filter((r) => r.id !== id); + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(filtered)); + } + + // 记录使用 + recordUse(id: string): void { + const rules = this.getAll(); + const rule = rules.find((r) => r.id === id); + if (rule) { + rule.metadata.lastUsedAt = Date.now(); + rule.metadata.useCount++; + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(rules)); + } + } +} +``` + +### 存储限制 + +- localStorage 容量: 5-10MB(浏览器限制) +- 单个规则约 1-5KB +- 预计可存储 1000-5000 个规则 + +--- + +## 功能亮点 + +| 亮点 | 说明 | +| -------- | ------------------------------ | +| 本地存储 | 数据保存在浏览器本地,无需后端 | +| 快速复用 | 一键加载已保存的规则 | +| 标签管理 | 按标签分类管理规则 | +| 导入导出 | 支持 JSON 文件导入导出 | +| 使用统计 | 记录使用次数和时间 | +| 搜索筛选 | 支持关键词搜索和标签筛选 | diff --git a/docs/test-data-generator/technical-implementation.md b/docs/test-data-generator/technical-implementation.md new file mode 100644 index 0000000..68e4455 --- /dev/null +++ b/docs/test-data-generator/technical-implementation.md @@ -0,0 +1,818 @@ +# 技术实现 + +## 技术栈 + +| 技术 | 用途 | 版本 | +| ------------ | -------- | ---- | +| React | UI 框架 | 19.x | +| TypeScript | 类型系统 | 5.x | +| Tailwind CSS | 样式 | 3.x | +| Web Worker | 后台生成 | - | +| localStorage | 本地存储 | - | + +--- + +## 项目结构 + +``` +src/pages/TestDataGenerator/ +├── index.tsx # 主页面 +├── components/ +│ ├── FieldList.tsx # 字段列表 +│ ├── FieldItem.tsx # 单个字段项 +│ ├── FieldEditor.tsx # 字段编辑器 +│ ├── GeneratorSelector.tsx # 生成器选择器 +│ ├── GeneratorConfig.tsx # 参数配置 +│ ├── DataPreview.tsx # 数据预览 +│ ├── GenerateOptions.tsx # 生成选项 +│ ├── GenerateButton.tsx # 生成按钮 +│ ├── ExportPanel.tsx # 导出面板 +│ └── RuleManager.tsx # 规则管理 +├── generators/ +│ ├── index.ts # 生成器入口 +│ ├── personal.ts # 个人信息生成器 +│ ├── business.ts # 业务数据生成器 +│ ├── technical.ts # 技术数据生成器 +│ └── basic.ts # 基础类型生成器 +├── storage/ +│ ├── ruleStorage.ts # 规则存储 +│ └── index.ts # 存储入口 +├── worker/ +│ └── generator.worker.ts # Web Worker +├── types/ +│ └── index.ts # 类型定义 +└── utils/ + ├── validator.ts # 规则校验 + ├── exporter.ts # 数据导出 + └── helpers.ts # 工具函数 +``` + +--- + +## 类型定义 + +### 核心类型 + +```typescript +// 字段配置 +export interface FieldConfig { + id: string; + name: string; + generator: string; + params: Record; + unique: boolean; +} + +// 规则配置 +export interface DataRule { + id: string; + name: string; + description?: string; + tags: string[]; + fields: FieldConfig[]; + options: { + total: number; + format: 'json' | 'csv' | 'sql' | 'typescript'; + }; + metadata: { + createdAt: number; + updatedAt: number; + lastUsedAt?: number; + useCount: number; + }; +} + +// 生成器定义 +export interface GeneratorDefinition { + name: string; + label: string; + description: string; + category: 'personal' | 'business' | 'technical' | 'basic'; + icon: string; + params: GeneratorParam[]; +} + +// 生成器参数 +export interface GeneratorParam { + name: string; + label: string; + type: 'string' | 'number' | 'boolean' | 'select' | 'array'; + required: boolean; + default?: any; + options?: { label: string; value: any }[]; +} + +// 生成结果 +export interface GenerateResult { + success: boolean; + data: any[]; + errors?: string[]; + stats: { + total: number; + generated: number; + failed: number; + duration: number; + }; +} +``` + +--- + +## 核心模块 + +### 1. 生成器系统 + +```typescript +// generators/index.ts +import { personalGenerators } from './personal'; +import { businessGenerators } from './business'; +import { technicalGenerators } from './technical'; +import { basicGenerators } from './basic'; + +export const generators = { + ...personalGenerators, + ...businessGenerators, + ...technicalGenerators, + ...basicGenerators, +}; + +export const generatorCategories = [ + { + key: 'personal', + label: '个人信息', + generators: ['chineseName', 'email', 'chinesePhone', 'idCard', 'chineseAddress'], + }, + { + key: 'business', + label: '业务数据', + generators: ['orderId', 'price', 'date', 'status'], + }, + { + key: 'technical', + label: '技术数据', + generators: ['uuid', 'ipv4', 'url'], + }, + { + key: 'basic', + label: '基础类型', + generators: ['randomInt', 'randomFloat', 'randomString', 'fromList'], + }, +]; +``` + +### 2. 生成器实现 + +```typescript +// generators/personal.ts +export const personalGenerators = { + chineseName: { + name: 'chineseName', + label: '中文姓名', + description: '生成中文姓名,如张三、李四', + icon: '👤', + params: [ + { + name: 'surnamePool', + label: '姓氏池', + type: 'string', + required: false, + default: '百家姓前100', + }, + { + name: 'givenNameLength', + label: '名字长度', + type: 'object', + required: false, + default: { min: 1, max: 2 }, + }, + ], + generate: (params: any) => { + const surnames = + '王李张刘陈杨赵黄周吴徐孙胡朱高林何郭马罗梁宋郑谢韩唐冯于董萧程曹袁邓许傅沈曾彭吕苏卢蒋蔡贾丁魏薛叶阎余潘杜戴夏钟汪田任姜范方石姚谭廖邹熊金陆郝孔白崔康毛邱秦江史顾侯邵孟龙万段雷钱汤尹黎易常武乔贺赖龚文'; + const givenNames = + '伟芳娜秀英敏静丽强磊洋勇艳杰娟涛超明华雪飞平刚慧建华玲桂英旭峰辉志强建平婷欣怡梦琪雅琴晓红'; + + const surname = surnames[Math.floor(Math.random() * surnames.length)]; + const { min, max } = params.givenNameLength || { min: 1, max: 2 }; + const length = Math.floor(Math.random() * (max - min + 1)) + min; + + let givenName = ''; + for (let i = 0; i < length; i++) { + givenName += givenNames[Math.floor(Math.random() * givenNames.length)]; + } + + return surname + givenName; + }, + }, + + email: { + name: 'email', + label: '邮箱地址', + description: '生成邮箱地址', + icon: '📧', + params: [ + { + name: 'domains', + label: '域名列表', + type: 'array', + required: false, + default: ['qq.com', '163.com', '126.com', 'gmail.com', 'outlook.com'], + }, + ], + generate: (params: any) => { + const domains = params.domains || ['qq.com', '163.com']; + const domain = domains[Math.floor(Math.random() * domains.length)]; + + const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let localPart = ''; + const length = 6 + Math.floor(Math.random() * 6); + + for (let i = 0; i < length; i++) { + localPart += chars[Math.floor(Math.random() * chars.length)]; + } + + return `${localPart}@${domain}`; + }, + }, + + chinesePhone: { + name: 'chinesePhone', + label: '中国手机号', + description: '生成11位中国手机号', + icon: '📱', + params: [ + { + name: 'prefix', + label: '手机号前缀', + type: 'array', + required: false, + default: [ + '130', + '131', + '132', + '133', + '134', + '135', + '136', + '137', + '138', + '139', + '150', + '151', + '152', + '153', + '155', + '156', + '157', + '158', + '159', + '180', + '181', + '182', + '183', + '184', + '185', + '186', + '187', + '188', + '189', + ], + }, + ], + generate: (params: any) => { + const prefixes = params.prefix || ['138', '139', '150', '151']; + const prefix = prefixes[Math.floor(Math.random() * prefixes.length)]; + + let suffix = ''; + for (let i = 0; i < 8; i++) { + suffix += Math.floor(Math.random() * 10); + } + + return prefix + suffix; + }, + }, +}; +``` + +### 3. 数据生成引擎 + +```typescript +// worker/generator.worker.ts +import { generators } from '../generators'; + +self.onmessage = function (e) { + const { rule, batchSize } = e.data; + const results = []; + + for (let i = 0; i < rule.fields.length; i++) { + const field = rule.fields[i]; + const generator = generators[field.generator]; + + if (!generator) { + self.postMessage({ + type: 'error', + field: field.name, + error: `生成器 ${field.generator} 不存在`, + }); + continue; + } + } + + // 生成数据 + const data = []; + for (let i = 0; i < rule.options.total; i++) { + const record: Record = {}; + const usedValues: Record> = {}; + + for (const field of rule.fields) { + const generator = generators[field.generator]; + + let value; + let retryCount = 0; + const maxRetry = 100; + + do { + value = generator.generate(field.params); + + if (field.unique && usedValues[field.name]?.has(value)) { + retryCount++; + value = undefined; + } + } while (value === undefined && retryCount < maxRetry); + + if (value !== undefined) { + record[field.name] = value; + + if (field.unique) { + if (!usedValues[field.name]) { + usedValues[field.name] = new Set(); + } + usedValues[field.name].add(value); + } + } + } + + data.push(record); + + // 发送进度 + if (i % batchSize === 0) { + self.postMessage({ + type: 'progress', + current: i + 1, + total: rule.options.total, + }); + } + } + + self.postMessage({ + type: 'complete', + data, + }); +}; +``` + +### 4. 规则存储 + +```typescript +// storage/ruleStorage.ts +import type { DataRule } from '../types'; + +const STORAGE_KEY = 'testDataGenerator_rules'; + +export class RuleStorage { + // 获取所有规则 + getAll(): DataRule[] { + const data = localStorage.getItem(STORAGE_KEY); + return data ? JSON.parse(data) : []; + } + + // 获取单个规则 + getById(id: string): DataRule | null { + const rules = this.getAll(); + return rules.find((r) => r.id === id) || null; + } + + // 保存规则 + save(rule: Omit): DataRule { + const rules = this.getAll(); + + const newRule: DataRule = { + ...rule, + id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + metadata: { + createdAt: Date.now(), + updatedAt: Date.now(), + useCount: 0, + }, + }; + + rules.push(newRule); + localStorage.setItem(STORAGE_KEY, JSON.stringify(rules)); + + return newRule; + } + + // 更新规则 + update(id: string, updates: Partial): DataRule | null { + const rules = this.getAll(); + const index = rules.findIndex((r) => r.id === id); + + if (index === -1) return null; + + rules[index] = { + ...rules[index], + ...updates, + metadata: { + ...rules[index].metadata, + updatedAt: Date.now(), + }, + }; + + localStorage.setItem(STORAGE_KEY, JSON.stringify(rules)); + return rules[index]; + } + + // 删除规则 + delete(id: string): boolean { + const rules = this.getAll(); + const filtered = rules.filter((r) => r.id !== id); + + if (filtered.length === rules.length) return false; + + localStorage.setItem(STORAGE_KEY, JSON.stringify(filtered)); + return true; + } + + // 记录使用 + recordUse(id: string): void { + const rules = this.getAll(); + const rule = rules.find((r) => r.id === id); + + if (rule) { + rule.metadata.lastUsedAt = Date.now(); + rule.metadata.useCount++; + localStorage.setItem(STORAGE_KEY, JSON.stringify(rules)); + } + } + + // 搜索规则 + search(keyword: string): DataRule[] { + const rules = this.getAll(); + const lowerKeyword = keyword.toLowerCase(); + + return rules.filter( + (r) => + r.name.toLowerCase().includes(lowerKeyword) || + r.description?.toLowerCase().includes(lowerKeyword) || + r.tags.some((t) => t.toLowerCase().includes(lowerKeyword)), + ); + } + + // 按标签筛选 + filterByTag(tag: string): DataRule[] { + const rules = this.getAll(); + return rules.filter((r) => r.tags.includes(tag)); + } + + // 获取最近使用 + getRecent(limit: number = 10): DataRule[] { + const rules = this.getAll(); + return rules + .filter((r) => r.metadata.lastUsedAt) + .sort((a, b) => b.metadata.lastUsedAt! - a.metadata.lastUsedAt!) + .slice(0, limit); + } + + // 导出规则 + export(ids: string[]): string { + const rules = this.getAll(); + const selected = rules.filter((r) => ids.includes(r.id)); + + return JSON.stringify( + { + version: '1.0', + exportedAt: new Date().toISOString(), + rules: selected, + }, + null, + 2, + ); + } + + // 导入规则 + import(jsonString: string): { success: number; failed: number; errors: string[] } { + try { + const data = JSON.parse(jsonString); + + if (!data.rules || !Array.isArray(data.rules)) { + return { success: 0, failed: 1, errors: ['无效的规则格式'] }; + } + + let success = 0; + let failed = 0; + const errors: string[] = []; + + for (const rule of data.rules) { + try { + if (!this.validateRule(rule)) { + throw new Error('规则格式不完整'); + } + + const newRule = { + ...rule, + id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + metadata: { + ...rule.metadata, + createdAt: Date.now(), + updatedAt: Date.now(), + }, + }; + + const rules = this.getAll(); + rules.push(newRule); + localStorage.setItem(STORAGE_KEY, JSON.stringify(rules)); + + success++; + } catch (e: any) { + failed++; + errors.push(`导入失败: ${e.message}`); + } + } + + return { success, failed, errors }; + } catch (e) { + return { success: 0, failed: 1, errors: ['JSON 解析失败'] }; + } + } + + // 验证规则格式 + private validateRule(rule: any): boolean { + return ( + rule.name && + Array.isArray(rule.fields) && + rule.fields.every((f: any) => f.name && f.generator) && + rule.options && + typeof rule.options.total === 'number' + ); + } +} + +export const ruleStorage = new RuleStorage(); +``` + +### 5. 数据导出 + +```typescript +// utils/exporter.ts +import type { DataRule } from '../types'; + +export class DataExporter { + // 导出为 JSON + static toJSON(data: any[], pretty: boolean = true): string { + return pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data); + } + + // 导出为 CSV + static toCSV(data: any[]): string { + if (data.length === 0) return ''; + + const headers = Object.keys(data[0]); + const rows = data.map((row) => headers.map((h) => this.escapeCSV(row[h])).join(',')); + + return [headers.join(','), ...rows].join('\n'); + } + + // CSV 转义 + private static escapeCSV(value: any): string { + const str = String(value ?? ''); + if (str.includes(',') || str.includes('"') || str.includes('\n')) { + return `"${str.replace(/"/g, '""')}"`; + } + return str; + } + + // 导出为 SQL + static toSQL(data: any[], tableName: string = 'data'): string { + if (data.length === 0) return ''; + + const columns = Object.keys(data[0]); + const rows = data.map((row) => { + const values = columns.map((col) => { + const value = row[col]; + if (typeof value === 'string') { + return `'${value.replace(/'/g, "\\'")}'`; + } + if (value === null || value === undefined) { + return 'NULL'; + } + return String(value); + }); + return `(${values.join(', ')})`; + }); + + return `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES\n${rows.join(',\n')};`; + } + + // 导出为 TypeScript 类型 + static toTypeScript(data: any[], interfaceName: string = 'Data'): string { + if (data.length === 0) return ''; + + const sample = data[0]; + const fields = Object.entries(sample).map(([key, value]) => { + let type = 'any'; + if (typeof value === 'string') type = 'string'; + else if (typeof value === 'number') type = 'number'; + else if (typeof value === 'boolean') type = 'boolean'; + else if (Array.isArray(value)) type = 'any[]'; + + return ` ${key}: ${type};`; + }); + + return `interface ${interfaceName} {\n${fields.join('\n')}\n}\n\n// 示例数据\nconst data: ${interfaceName}[] = ${JSON.stringify(data, null, 2)};`; + } + + // 下载文件 + static download(content: string, filename: string, mimeType: string = 'text/plain'): void { + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + } +} +``` + +--- + +## Web Worker 使用 + +### 创建 Worker + +```typescript +// hooks/useGenerator.ts +import { useRef, useState, useCallback } from 'react'; +import type { DataRule, GenerateResult } from '../types'; + +export function useGenerator() { + const workerRef = useRef(null); + const [progress, setProgress] = useState(0); + const [isGenerating, setIsGenerating] = useState(false); + const [result, setResult] = useState(null); + + const generate = useCallback(async (rule: DataRule): Promise => { + return new Promise((resolve, reject) => { + setIsGenerating(true); + setProgress(0); + + // 创建 Worker + workerRef.current = new Worker(new URL('../worker/generator.worker.ts', import.meta.url), { + type: 'module', + }); + + // 监听消息 + workerRef.current.onmessage = (e) => { + const { type, current, total, data, error } = e.data; + + if (type === 'progress') { + setProgress(Math.round((current / total) * 100)); + } + + if (type === 'complete') { + setIsGenerating(false); + setProgress(100); + + const result: GenerateResult = { + success: true, + data, + stats: { + total: rule.options.total, + generated: data.length, + failed: 0, + duration: 0, + }, + }; + + setResult(result); + resolve(result); + } + + if (type === 'error') { + setIsGenerating(false); + reject(new Error(error)); + } + }; + + // 发送任务 + workerRef.current.postMessage({ + rule, + batchSize: 1000, + }); + }); + }, []); + + const terminate = useCallback(() => { + if (workerRef.current) { + workerRef.current.terminate(); + workerRef.current = null; + setIsGenerating(false); + } + }, []); + + return { + generate, + terminate, + progress, + isGenerating, + result, + }; +} +``` + +--- + +## 性能优化 + +### 1. Web Worker + +- 将数据生成放到后台线程 +- 不阻塞主线程 UI +- 支持取消和中断 + +### 2. 分批生成 + +- 每批生成 1000 条数据 +- 实时发送进度 +- 避免内存峰值 + +### 3. 唯一性处理 + +- 使用 Set 存储已生成的值 +- 自动生成重试 +- 避免无限循环 + +### 4. 虚拟列表 + +- 大数据预览使用虚拟列表 +- 只渲染可见区域 +- 减少 DOM 渲染 + +--- + +## 测试策略 + +### 单元测试 + +```typescript +// generators/__tests__/personal.test.ts +import { personalGenerators } from '../personal'; + +describe('chineseName', () => { + it('should generate valid chinese name', () => { + const name = personalGenerators.chineseName.generate({}); + expect(name).toMatch(/^[一-龥]{2,3}$/); + }); + + it('should respect givenNameLength', () => { + const name = personalGenerators.chineseName.generate({ + givenNameLength: { min: 1, max: 1 }, + }); + expect(name.length).toBe(2); + }); +}); + +describe('email', () => { + it('should generate valid email', () => { + const email = personalGenerators.email.generate({}); + expect(email).toMatch(/^[a-z0-9]+@[a-z0-9]+\.[a-z]+$/); + }); +}); +``` + +### 集成测试 + +```typescript +// __tests__/generator.integration.test.ts +import { useGenerator } from '../hooks/useGenerator'; +import { renderHook, act } from '@testing-library/react'; + +describe('useGenerator', () => { + it('should generate data', async () => { + const { result } = renderHook(() => useGenerator()); + + const rule = { + fields: [{ id: '1', name: 'name', generator: 'chineseName', params: {}, unique: false }], + options: { total: 10, format: 'json' as const }, + }; + + await act(async () => { + await result.current.generate(rule); + }); + + expect(result.current.result?.data).toHaveLength(10); + }); +}); +``` diff --git a/docs/test-data-generator/ui-design.md b/docs/test-data-generator/ui-design.md new file mode 100644 index 0000000..6747cb1 --- /dev/null +++ b/docs/test-data-generator/ui-design.md @@ -0,0 +1,347 @@ +# 界面设计 + +## 设计原则 + +1. **简洁直观** - 一目了然的布局 +2. **操作便捷** - 减少点击次数 +3. **实时反馈** - 配置即预览 +4. **响应式** - 支持多种屏幕尺寸 + +--- + +## 页面结构 + +### 主页面布局 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 🧪 测试数据生成器 [帮助] │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────────────────────────┐ ┌───────────────────────┐ │ +│ │ 📋 字段配置 │ │ 📊 数据预览 │ │ +│ │ │ │ │ │ +│ │ [+ 添加字段] │ │ [实时预览数据] │ │ +│ │ │ │ │ │ +│ │ 字段列表... │ │ │ │ +│ │ │ │ │ │ +│ └─────────────────────────────────────┘ └───────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ ⚙️ 生成选项 │ │ +│ │ 数量、格式、约束... │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 📤 操作栏 │ │ +│ │ [🚀 生成] [💾 保存规则] [📚 规则管理] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 组件设计 + +### 1. 字段列表组件 + +**功能**: 展示和管理所有字段 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📋 字段配置 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ [+ 添加字段] │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 1. name [中文姓名 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 2. email [邮箱地址 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 3. phone [手机号 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ │ 4. age [年龄 ▼] [⚙️] [❌] [⬆️][⬇️] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [+ 添加字段] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**交互**: + +- 点击字段名可编辑 +- 点击生成器下拉框可切换生成器 +- 点击 ⚙️ 展开参数配置 +- 点击 ❌ 删除字段 +- 点击 ⬆️⬇️ 调整顺序 + +--- + +### 2. 字段配置组件 + +**功能**: 配置单个字段的详细参数 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 字段配置 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📝 基础配置 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 字段名称: [name ] │ │ +│ │ 字段描述: [用户姓名 ] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 🔧 生成器选择 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 分类: [👤 个人信息 ▼] │ │ +│ │ 生成器: [中文姓名 ▼] │ │ +│ │ 📖 说明: 生成中文姓名,如张三、李四 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ ⚙️ 参数配置 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 姓氏池: [百家姓前100 ▼] │ │ +│ │ 名字长度: [1] ~ [2] │ │ +│ │ ☑ 唯一性约束 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 🎲 预览示例 │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 张伟, 李娜, 王强, 赵敏, 刘洋 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [✅ 保存] [❌ 取消] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 3. 数据预览组件 + +**功能**: 实时预览生成的数据 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📊 数据预览 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 📋 预览: 前 10 条 │ +│ │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ { │ │ +│ │ "name": "张伟", │ │ +│ │ "email": "zhangwei@qq.com", │ │ +│ │ "phone": "13812345678", │ │ +│ │ "age": 28 │ │ +│ │ }, │ │ +│ │ { │ │ +│ │ "name": "李娜", │ │ +│ │ "email": "lina@163.com", │ │ +│ │ "phone": "13987654321", │ │ +│ │ "age": 35 │ │ +│ │ }, │ │ +│ │ ... │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📊 格式切换: │ +│ [JSON] [CSV] [SQL] [TypeScript] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +**功能**: + +- 实时更新预览 +- 支持格式切换 +- 分页显示更多数据 +- 一键复制 + +--- + +### 4. 生成选项组件 + +**功能**: 配置生成参数 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ ⚙️ 生成选项 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌───────────────────────┐ │ +│ │ 📊 生成数量 │ │ 📄 数据格式 │ │ 🔒 约束条件 │ │ +│ │ │ │ │ │ │ │ +│ │ [100 ] │ │ ○ JSON │ │ ☑ name 唯一 │ │ +│ │ [10 ▼] │ │ ○ CSV │ │ ☑ email 唯一 │ │ +│ │ [100 ▼] │ │ ○ SQL │ │ ☑ phone 唯一 │ │ +│ │ [自定义 ] │ │ ○ TypeScript │ │ ☐ age 唯一 │ │ +│ │ │ │ │ │ │ │ +│ └─────────────────┘ └─────────────────┘ └───────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 5. 操作栏组件 + +**功能**: 顶部操作按钮 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📤 操作栏 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ [🚀 生成数据] [💾 保存规则] [📚 规则管理] [📥 导入规则] [📤 导出]│ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 6. 生成结果组件 + +**功能**: 展示生成结果 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📊 生成结果 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ ✅ 成功生成 100 条数据 │ +│ │ +│ 📊 数据统计: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ • 总条数: 100 │ │ +│ │ • 文件大小: 12.5 KB │ │ +│ │ • 唯一性检查: ✅ 全部通过 │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📋 数据预览: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ [JSON 格式预览...] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ 📤 导出选项: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ [📋 复制全部] [📥 下载 JSON] [📥 下载 CSV] [📥 下载 SQL] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [❌ 关闭] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +### 7. 规则管理组件 + +**功能**: 管理已保存的规则 + +**界面**: + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 📚 规则管理 │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 🔍 搜索: [搜索规则名称或标签... ] │ +│ │ +│ 📁 标签筛选: [全部] [电商] [用户] [测试数据] [最近使用] │ +│ │ +│ 📋 规则列表: │ +│ ┌───────────────────────────────────────────────────────────────┐ │ +│ │ 📦 电商用户数据 - 测试用 │ │ +│ │ 📝 用于测试用户注册功能 │ │ +│ │ 🏷️ 电商 用户 测试数据 │ │ +│ │ 📊 4 个字段 | 100 条 | JSON │ │ +│ │ 📅 2024-01-15 创建 | 使用 12 次 │ │ +│ │ [📥 加载] [✏️ 编辑] [📤 导出] [🗑️ 删除] │ │ +│ │ │ │ +│ │ 📦 电商订单数据 - 测试用 │ │ +│ │ 📝 用于测试订单功能 │ │ +│ │ 🏷️ 电商 订单 测试数据 │ │ +│ │ 📊 6 个字段 | 500 条 | JSON │ │ +│ │ 📅 2024-01-16 创建 | 使用 8 次 │ │ +│ │ [📥 加载] [✏️ 编辑] [📤 导出] [🗑️ 删除] │ │ +│ └───────────────────────────────────────────────────────────────┘ │ +│ │ +│ [📥 导入规则] [📤 导出全部] │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 响应式设计 + +### 桌面端 (>1024px) + +- 左右分栏布局 +- 字段配置区占 60% +- 数据预览区占 40% + +### 平板端 (768px-1024px) + +- 左右分栏布局 +- 字段配置区占 50% +- 数据预览区占 50% + +### 移动端 (<768px) + +- 单栏布局 +- 字段配置和数据预览上下排列 +- 预览区默认折叠 + +--- + +## 交互细节 + +### 拖拽排序 + +- 字段支持拖拽排序 +- 拖拽时显示占位符 +- 释放后立即生效 + +### 实时预览 + +- 配置参数时实时更新预览 +- 延迟 300ms 防抖 +- 加载状态显示 + +### 快捷操作 + +- Ctrl+C 复制选中数据 +- Ctrl+V 粘贴规则配置 +- Ctrl+S 保存当前配置 + +--- + +## 主题支持 + +### 浅色主题 + +- 背景色: #ffffff +- 文字色: #333333 +- 边框色: #e0e0e0 +- 主题色: #3b82f6 + +### 深色主题 + +- 背景色: #1a1a1a +- 文字色: #ffffff +- 边框色: #404040 +- 主题色: #60a5fa