docs: 统一测试数据生成器文档,移除 SQL/TypeScript 导出,修复接口命名不一致
- 移除所有文档中的 SQL 和 TypeScript 导出格式引用 - 统一数据导出格式为 JSON 和 CSV - 移除 GeneratorDefinition 中的 icon 字段 - 更新 generatorCategories 配置,包含所有 21 个生成器 - 统一 rule-management.md 和 technical-implementation.md 中的接口名称为 DataRule - 更新 ui-design.md 中的界面设计,移除 SQL/TypeScript 相关元素
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
## 功能概览
|
||||
|
||||
| 功能模块 | 功能点 | 说明 | 优先级 |
|
||||
| -------------- | --------------- | ------------------ | ------ |
|
||||
| -------------- | ------------- | ------------------ | ------ |
|
||||
| **字段配置** | 添加字段 | 可视化添加数据字段 | P0 |
|
||||
| | 删除字段 | 删除不需要的字段 | P0 |
|
||||
| | 字段排序 | 拖拽调整字段顺序 | P1 |
|
||||
@@ -19,8 +19,6 @@
|
||||
| | 数据预览 | 生成前预览数据 | P1 |
|
||||
| **数据导出** | JSON 导出 | 导出为 JSON 格式 | P0 |
|
||||
| | CSV 导出 | 导出为 CSV 格式 | P0 |
|
||||
| | SQL 导出 | 导出为 INSERT 语句 | P1 |
|
||||
| | TypeScript 导出 | 导出为 TS 类型定义 | P2 |
|
||||
|
||||
## 功能详情
|
||||
|
||||
@@ -238,15 +236,3 @@
|
||||
- 导出为 CSV 格式
|
||||
- 自动处理逗号转义
|
||||
- 支持中文编码 (UTF-8 with BOM)
|
||||
|
||||
#### SQL 导出
|
||||
|
||||
- 导出为 INSERT 语句
|
||||
- 支持自定义表名
|
||||
- 自动处理特殊字符转义
|
||||
|
||||
#### TypeScript 导出
|
||||
|
||||
- 导出为 TypeScript 类型定义
|
||||
- 根据字段类型自动推断
|
||||
- 可选是否包含示例数据
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
## 分类概览
|
||||
|
||||
| 分类 | 生成器数量 | 说明 |
|
||||
| ----------- | ---------- | ------------------------------------------------ |
|
||||
| 👤 个人信息 | 6 个 | 中文姓名、邮箱、手机号、年龄等 |
|
||||
| 💼 业务数据 | 8 个 | 订单号、价格、日期、状态、数量、评分、折扣、库存 |
|
||||
| -------- | ---------- | ------------------------------------------------ |
|
||||
| 个人信息 | 6 个 | 中文姓名、邮箱、手机号、年龄等 |
|
||||
| 业务数据 | 8 个 | 订单号、价格、日期、状态、数量、评分、折扣、库存 |
|
||||
| 技术数据 | 3 个 | UUID、IP地址、URL |
|
||||
| 基础类型 | 4 个 | 整数、浮点数、字符串等 |
|
||||
| **总计** | **21 个** | |
|
||||
|
||||
---
|
||||
|
||||
## 👤 个人信息生成器
|
||||
## 个人信息生成器
|
||||
|
||||
### 1. 中文姓名 (chineseName)
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
|
||||
---
|
||||
|
||||
## 💼 业务数据生成器
|
||||
## 业务数据生成器
|
||||
|
||||
### 1. 订单号 (orderId)
|
||||
|
||||
@@ -687,13 +687,13 @@
|
||||
│ │
|
||||
│ 生成器列表 │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ 👤 个人信息 │ │
|
||||
│ │ 个人信息 │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ 👤 中文姓名 生成中文姓名,如张三 │ │ │
|
||||
│ │ │ 📧 邮箱地址 生成邮箱地址 │ │ │
|
||||
│ │ │ 📱 中国手机号 生成11位手机号 │ │ │
|
||||
│ │ │ 🆔 身份证号 生成18位身份证号 │ │ │
|
||||
│ │ │ 🏠 中文地址 生成中国地址 │ │ │
|
||||
│ │ │ 中文姓名 生成中文姓名,如张三 │ │ │
|
||||
│ │ │ 邮箱地址 生成邮箱地址 │ │ │
|
||||
│ │ │ 中国手机号 生成11位手机号 │ │ │
|
||||
│ │ │ 身份证号 生成18位身份证号 │ │ │
|
||||
│ │ │ 中文地址 生成中国地址 │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────┘ │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
|
||||
@@ -27,14 +27,14 @@
|
||||
### 规则模板
|
||||
|
||||
```typescript
|
||||
interface RuleTemplate {
|
||||
interface DataRule {
|
||||
id: string; // 唯一标识
|
||||
name: string; // 规则名称
|
||||
description?: string; // 规则描述
|
||||
fields: FieldConfig[]; // 字段配置
|
||||
options: {
|
||||
total: number; // 生成数量
|
||||
format: 'json' | 'csv' | 'sql' | 'typescript';
|
||||
format: 'json' | 'csv';
|
||||
defaultEmptyRate: number; // 默认空值率(0-100)
|
||||
};
|
||||
metadata: {
|
||||
@@ -170,7 +170,7 @@ interface FieldConfig {
|
||||
**实现方式**:
|
||||
|
||||
```typescript
|
||||
search(keyword: string): RuleTemplate[] {
|
||||
search(keyword: string): DataRule[] {
|
||||
const rules = this.getAll();
|
||||
const lowerKeyword = keyword.toLowerCase();
|
||||
|
||||
@@ -408,13 +408,13 @@ class RuleStorage {
|
||||
private readonly STORAGE_KEY = 'testDataGenerator_rules';
|
||||
|
||||
// 获取所有规则
|
||||
getAll(): RuleTemplate[] {
|
||||
getAll(): DataRule[] {
|
||||
const data = localStorage.getItem(this.STORAGE_KEY);
|
||||
return data ? JSON.parse(data) : [];
|
||||
}
|
||||
|
||||
// 保存规则
|
||||
save(rule: RuleTemplate): { success: boolean; message?: string } {
|
||||
save(rule: DataRule): { success: boolean; message?: string } {
|
||||
const rules = this.getAll();
|
||||
|
||||
// 规则数量限制:最多 20 条
|
||||
@@ -432,7 +432,7 @@ class RuleStorage {
|
||||
}
|
||||
|
||||
// 更新规则
|
||||
update(id: string, updates: Partial<RuleTemplate>): void {
|
||||
update(id: string, updates: Partial<DataRule>): void {
|
||||
const rules = this.getAll();
|
||||
const index = rules.findIndex((r) => r.id === id);
|
||||
if (index !== -1) {
|
||||
|
||||
@@ -15,38 +15,55 @@
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
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/
|
||||
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 # 规则管理
|
||||
│ ├── hooks/ # 页面专属钩子
|
||||
│ │ └── useGenerator.ts # 数据生成钩子
|
||||
│ └── __tests__/ # 测试文件
|
||||
│ └── TestDataGenerator.test.tsx
|
||||
├── lib/
|
||||
│ └── generators/ # 生成器库(独立模块)
|
||||
│ ├── index.ts # 生成器入口
|
||||
│ ├── types.ts # 生成器类型定义
|
||||
│ ├── personal.ts # 个人信息生成器
|
||||
│ ├── business.ts # 业务数据生成器
|
||||
│ ├── technical.ts # 技术数据生成器
|
||||
│ └── basic.ts # 基础类型生成器
|
||||
├── storage/
|
||||
│ ├── ruleStorage.ts # 规则存储
|
||||
│ └── index.ts # 存储入口
|
||||
├── worker/
|
||||
├── workers/
|
||||
│ └── generator.worker.ts # Web Worker
|
||||
├── types/
|
||||
│ └── index.ts # 类型定义
|
||||
│ └── testDataGenerator.ts # 类型定义
|
||||
└── utils/
|
||||
├── validator.ts # 规则校验
|
||||
├── exporter.ts # 数据导出
|
||||
└── helpers.ts # 工具函数
|
||||
├── ruleStorage.ts # 规则存储
|
||||
├── dataExporter.ts # 数据导出
|
||||
└── validator.ts # 规则校验
|
||||
```
|
||||
|
||||
### 目录职责说明
|
||||
|
||||
| 目录 | 职责 | 说明 |
|
||||
| ------------------------------------- | ---------- | -------------------------- |
|
||||
| `pages/TestDataGenerator/` | 页面模块 | 包含页面组件和页面专属逻辑 |
|
||||
| `pages/TestDataGenerator/components/` | 页面组件 | 仅属于该页面的 UI 组件 |
|
||||
| `pages/TestDataGenerator/hooks/` | 页面钩子 | 仅属于该页面的自定义钩子 |
|
||||
| `lib/generators/` | 生成器库 | 独立的生成器模块,可复用 |
|
||||
| `workers/` | Web Worker | 后台任务处理 |
|
||||
| `types/` | 类型定义 | 全局共享的类型定义 |
|
||||
| `utils/` | 工具函数 | 全局共享的工具函数 |
|
||||
|
||||
---
|
||||
|
||||
## 类型定义
|
||||
@@ -54,6 +71,8 @@ src/pages/TestDataGenerator/
|
||||
### 核心类型
|
||||
|
||||
```typescript
|
||||
// src/types/testDataGenerator.ts
|
||||
|
||||
// 字段配置
|
||||
export interface FieldConfig {
|
||||
id: string;
|
||||
@@ -73,7 +92,7 @@ export interface DataRule {
|
||||
fields: FieldConfig[];
|
||||
options: {
|
||||
total: number;
|
||||
format: 'json' | 'csv' | 'sql' | 'typescript';
|
||||
format: 'json' | 'csv';
|
||||
defaultEmptyRate: number; // 默认空值率(0-100),用于未设置 emptyRate 的选填字段
|
||||
};
|
||||
metadata: {
|
||||
@@ -90,7 +109,6 @@ export interface GeneratorDefinition {
|
||||
label: string;
|
||||
description: string;
|
||||
category: 'personal' | 'business' | 'technical' | 'basic';
|
||||
icon: string;
|
||||
params: GeneratorParam[];
|
||||
// 普通生成方法
|
||||
generate: (params: Record<string, any>) => any;
|
||||
@@ -117,6 +135,7 @@ export interface GenerateResult {
|
||||
total: number;
|
||||
generated: number;
|
||||
failed: number;
|
||||
failedFields?: string[]; // 生成失败的字段名列表
|
||||
duration: number;
|
||||
};
|
||||
}
|
||||
@@ -129,7 +148,7 @@ export interface GenerateResult {
|
||||
### 1. 生成器系统
|
||||
|
||||
```typescript
|
||||
// generators/index.ts
|
||||
// src/lib/generators/index.ts
|
||||
import { personalGenerators } from './personal';
|
||||
import { businessGenerators } from './business';
|
||||
import { technicalGenerators } from './technical';
|
||||
@@ -146,12 +165,12 @@ export const generatorCategories = [
|
||||
{
|
||||
key: 'personal',
|
||||
label: '个人信息',
|
||||
generators: ['chineseName', 'email', 'chinesePhone', 'idCard', 'chineseAddress'],
|
||||
generators: ['chineseName', 'email', 'chinesePhone', 'idCard', 'chineseAddress', 'age'],
|
||||
},
|
||||
{
|
||||
key: 'business',
|
||||
label: '业务数据',
|
||||
generators: ['orderId', 'price', 'date', 'status'],
|
||||
generators: ['orderId', 'price', 'date', 'status', 'quantity', 'rating', 'discount', 'stock'],
|
||||
},
|
||||
{
|
||||
key: 'technical',
|
||||
@@ -169,13 +188,12 @@ export const generatorCategories = [
|
||||
### 2. 生成器实现
|
||||
|
||||
```typescript
|
||||
// generators/personal.ts
|
||||
// src/lib/generators/personal.ts
|
||||
export const personalGenerators = {
|
||||
chineseName: {
|
||||
name: 'chineseName',
|
||||
label: '中文姓名',
|
||||
description: '生成中文姓名,如张三、李四',
|
||||
icon: '👤',
|
||||
params: [
|
||||
{
|
||||
name: 'surnamePool',
|
||||
@@ -227,7 +245,6 @@ export const personalGenerators = {
|
||||
name: 'email',
|
||||
label: '邮箱地址',
|
||||
description: '生成邮箱地址',
|
||||
icon: '📧',
|
||||
params: [
|
||||
{
|
||||
name: 'domains',
|
||||
@@ -267,7 +284,6 @@ export const personalGenerators = {
|
||||
name: 'chinesePhone',
|
||||
label: '中国手机号',
|
||||
description: '生成11位中国手机号',
|
||||
icon: '📱',
|
||||
params: [
|
||||
{
|
||||
name: 'prefix',
|
||||
@@ -335,7 +351,6 @@ export const personalGenerators = {
|
||||
name: 'age',
|
||||
label: '年龄',
|
||||
description: '生成真实分布的年龄',
|
||||
icon: '🎂',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -394,13 +409,12 @@ export const personalGenerators = {
|
||||
```
|
||||
|
||||
```typescript
|
||||
// generators/business.ts
|
||||
// src/lib/generators/business.ts
|
||||
export const businessGenerators = {
|
||||
price: {
|
||||
name: 'price',
|
||||
label: '价格',
|
||||
description: '生成真实的价格数据',
|
||||
icon: '💰',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -482,7 +496,6 @@ export const businessGenerators = {
|
||||
name: 'orderId',
|
||||
label: '订单号',
|
||||
description: '生成订单号',
|
||||
icon: '📋',
|
||||
params: [
|
||||
{
|
||||
name: 'prefix',
|
||||
@@ -520,7 +533,6 @@ export const businessGenerators = {
|
||||
name: 'date',
|
||||
label: '日期',
|
||||
description: '生成随机日期',
|
||||
icon: '📅',
|
||||
params: [
|
||||
{
|
||||
name: 'start',
|
||||
@@ -569,7 +581,6 @@ export const businessGenerators = {
|
||||
name: 'status',
|
||||
label: '状态',
|
||||
description: '生成随机状态',
|
||||
icon: '🔄',
|
||||
params: [
|
||||
{
|
||||
name: 'options',
|
||||
@@ -593,7 +604,6 @@ export const businessGenerators = {
|
||||
name: 'quantity',
|
||||
label: '数量',
|
||||
description: '生成真实分布的数量',
|
||||
icon: '📦',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -653,7 +663,6 @@ export const businessGenerators = {
|
||||
name: 'rating',
|
||||
label: '评分',
|
||||
description: '生成真实分布的评分',
|
||||
icon: '⭐',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -720,7 +729,6 @@ export const businessGenerators = {
|
||||
name: 'discount',
|
||||
label: '折扣',
|
||||
description: '生成真实分布的折扣',
|
||||
icon: '🏷️',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -780,7 +788,6 @@ export const businessGenerators = {
|
||||
name: 'stock',
|
||||
label: '库存',
|
||||
description: '生成真实分布的库存',
|
||||
icon: '🏭',
|
||||
params: [
|
||||
{
|
||||
name: 'min',
|
||||
@@ -985,51 +992,53 @@ function generatePsychologicalDiscount(min: number, max: number): number {
|
||||
### 3. 数据生成引擎
|
||||
|
||||
```typescript
|
||||
// worker/generator.worker.ts
|
||||
import { generators } from '../generators';
|
||||
// src/workers/generator.worker.ts
|
||||
import { generators } from '@/lib/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) {
|
||||
// 1. 验证所有字段的生成器是否存在
|
||||
const invalidField = rule.fields.find((f) => !generators[f.generator]);
|
||||
if (invalidField) {
|
||||
self.postMessage({
|
||||
type: 'error',
|
||||
field: field.name,
|
||||
error: `生成器 ${field.generator} 不存在`,
|
||||
field: invalidField.name,
|
||||
error: `生成器 ${invalidField.generator} 不存在`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成数据
|
||||
const data = [];
|
||||
// 2. 生成数据
|
||||
const data: Record<string, any>[] = [];
|
||||
const usedValues: Record<string, Set<any>> = {};
|
||||
|
||||
// 根据总数量决定策略
|
||||
const isLargeBatch = rule.options.total > 1000;
|
||||
|
||||
// 统计信息
|
||||
let failedCount = 0;
|
||||
const failedFields: string[] = [];
|
||||
|
||||
for (let i = 0; i < rule.options.total; i++) {
|
||||
const record: Record<string, any> = {};
|
||||
|
||||
for (const field of rule.fields) {
|
||||
const generator = generators[field.generator];
|
||||
|
||||
// 判断是否需要生成数据
|
||||
const shouldGenerate =
|
||||
field.required || Math.random() * 100 >= (field.emptyRate ?? rule.options.defaultEmptyRate);
|
||||
// 3. 判断是否需要生成数据
|
||||
const emptyRate = field.emptyRate ?? rule.options.defaultEmptyRate;
|
||||
const shouldGenerate = field.required || Math.random() * 100 >= emptyRate;
|
||||
|
||||
if (!shouldGenerate) {
|
||||
// 选填字段且触发空值
|
||||
record[field.name] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
let value;
|
||||
|
||||
// 4. 根据条件选择生成策略
|
||||
if (field.unique && isLargeBatch) {
|
||||
// 策略1:大数据量 + 唯一性 → 索引生成(高性能)
|
||||
value = generator.generateAtIndex?.(field.params, i) ?? generator.generate(field.params);
|
||||
@@ -1046,14 +1055,25 @@ self.onmessage = function (e) {
|
||||
value = undefined;
|
||||
}
|
||||
} while (value === undefined && retryCount < maxRetry);
|
||||
|
||||
// 5. 处理重试失败
|
||||
if (retryCount >= maxRetry) {
|
||||
failedCount++;
|
||||
if (!failedFields.includes(field.name)) {
|
||||
failedFields.push(field.name);
|
||||
}
|
||||
console.warn(`字段 "${field.name}" 重试 ${maxRetry} 次后仍无法生成唯一值`);
|
||||
}
|
||||
} else {
|
||||
// 策略3:非唯一性 → 普通生成
|
||||
value = generator.generate(field.params);
|
||||
}
|
||||
|
||||
// 6. 记录生成的值
|
||||
if (value !== undefined) {
|
||||
record[field.name] = value;
|
||||
|
||||
// 记录唯一性字段的值
|
||||
if (field.unique) {
|
||||
if (!usedValues[field.name]) {
|
||||
usedValues[field.name] = new Set();
|
||||
@@ -1061,13 +1081,14 @@ self.onmessage = function (e) {
|
||||
usedValues[field.name].add(value);
|
||||
}
|
||||
} else {
|
||||
// 生成失败,设置为空值
|
||||
record[field.name] = null;
|
||||
}
|
||||
}
|
||||
|
||||
data.push(record);
|
||||
|
||||
// 发送进度
|
||||
// 7. 发送进度(每 batchSize 条发送一次)
|
||||
if (i % batchSize === 0) {
|
||||
self.postMessage({
|
||||
type: 'progress',
|
||||
@@ -1077,9 +1098,16 @@ self.onmessage = function (e) {
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 发送完成结果
|
||||
self.postMessage({
|
||||
type: 'complete',
|
||||
data,
|
||||
stats: {
|
||||
total: rule.options.total,
|
||||
generated: data.length,
|
||||
failed: failedCount,
|
||||
failedFields,
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
@@ -1087,10 +1115,11 @@ self.onmessage = function (e) {
|
||||
### 4. 规则存储
|
||||
|
||||
```typescript
|
||||
// storage/ruleStorage.ts
|
||||
import type { DataRule } from '../types';
|
||||
// src/utils/ruleStorage.ts
|
||||
import type { DataRule } from '@/types/testDataGenerator';
|
||||
|
||||
const STORAGE_KEY = 'testDataGenerator_rules';
|
||||
const MAX_RULES = 20; // 最大规则数量限制
|
||||
|
||||
export class RuleStorage {
|
||||
// 获取所有规则
|
||||
@@ -1105,10 +1134,32 @@ export class RuleStorage {
|
||||
return rules.find((r) => r.id === id) || null;
|
||||
}
|
||||
|
||||
// 获取当前规则数量
|
||||
getCount(): number {
|
||||
return this.getAll().length;
|
||||
}
|
||||
|
||||
// 检查是否达到最大数量限制
|
||||
isMaxReached(): boolean {
|
||||
return this.getCount() >= MAX_RULES;
|
||||
}
|
||||
|
||||
// 保存规则
|
||||
save(rule: Omit<DataRule, 'id' | 'metadata'>): DataRule {
|
||||
save(rule: Omit<DataRule, 'id' | 'metadata'>): {
|
||||
success: boolean;
|
||||
rule?: DataRule;
|
||||
error?: string;
|
||||
} {
|
||||
const rules = this.getAll();
|
||||
|
||||
// 检查数量限制
|
||||
if (rules.length >= MAX_RULES) {
|
||||
return {
|
||||
success: false,
|
||||
error: `已达到最大规则数量(${MAX_RULES}条),请删除一些规则后再保存`,
|
||||
};
|
||||
}
|
||||
|
||||
const newRule: DataRule = {
|
||||
...rule,
|
||||
id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
@@ -1122,7 +1173,7 @@ export class RuleStorage {
|
||||
rules.push(newRule);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(rules));
|
||||
|
||||
return newRule;
|
||||
return { success: true, rule: newRule };
|
||||
}
|
||||
|
||||
// 更新规则
|
||||
@@ -1156,6 +1207,43 @@ export class RuleStorage {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 复制规则
|
||||
duplicate(id: string): { success: boolean; rule?: DataRule; error?: string } {
|
||||
const rules = this.getAll();
|
||||
|
||||
// 检查数量限制
|
||||
if (rules.length >= MAX_RULES) {
|
||||
return {
|
||||
success: false,
|
||||
error: `已达到最大规则数量(${MAX_RULES}条),请删除一些规则后再复制`,
|
||||
};
|
||||
}
|
||||
|
||||
const source = rules.find((r) => r.id === id);
|
||||
if (!source) {
|
||||
return {
|
||||
success: false,
|
||||
error: '找不到要复制的规则',
|
||||
};
|
||||
}
|
||||
|
||||
const newRule: DataRule = {
|
||||
...source,
|
||||
id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
name: `${source.name} (副本)`,
|
||||
metadata: {
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
useCount: 0,
|
||||
},
|
||||
};
|
||||
|
||||
rules.push(newRule);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(rules));
|
||||
|
||||
return { success: true, rule: newRule };
|
||||
}
|
||||
|
||||
// 记录使用
|
||||
recordUse(id: string): void {
|
||||
const rules = this.getAll();
|
||||
@@ -1214,9 +1302,23 @@ export class RuleStorage {
|
||||
return { success: 0, failed: 1, errors: ['无效的规则格式'] };
|
||||
}
|
||||
|
||||
// 检查导入后总数是否超过限制
|
||||
const currentCount = this.getCount();
|
||||
const importCount = data.rules.length;
|
||||
if (currentCount + importCount > MAX_RULES) {
|
||||
return {
|
||||
success: 0,
|
||||
failed: importCount,
|
||||
errors: [
|
||||
`导入失败:当前已有 ${currentCount} 条规则,最多只能保存 ${MAX_RULES} 条,无法导入 ${importCount} 条规则`,
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
let success = 0;
|
||||
let failed = 0;
|
||||
const errors: string[] = [];
|
||||
const rules = this.getAll();
|
||||
|
||||
for (const rule of data.rules) {
|
||||
try {
|
||||
@@ -1224,7 +1326,7 @@ export class RuleStorage {
|
||||
throw new Error('规则格式不完整');
|
||||
}
|
||||
|
||||
const newRule = {
|
||||
const newRule: DataRule = {
|
||||
...rule,
|
||||
id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||
metadata: {
|
||||
@@ -1234,10 +1336,7 @@ export class RuleStorage {
|
||||
},
|
||||
};
|
||||
|
||||
const rules = this.getAll();
|
||||
rules.push(newRule);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(rules));
|
||||
|
||||
success++;
|
||||
} catch (e: any) {
|
||||
failed++;
|
||||
@@ -1245,12 +1344,22 @@ export class RuleStorage {
|
||||
}
|
||||
}
|
||||
|
||||
// 一次性保存所有导入的规则
|
||||
if (success > 0) {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(rules));
|
||||
}
|
||||
|
||||
return { success, failed, errors };
|
||||
} catch (e) {
|
||||
return { success: 0, failed: 1, errors: ['JSON 解析失败'] };
|
||||
}
|
||||
}
|
||||
|
||||
// 清空所有规则
|
||||
clear(): void {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
// 验证规则格式
|
||||
private validateRule(rule: any): boolean {
|
||||
return (
|
||||
@@ -1269,8 +1378,14 @@ export const ruleStorage = new RuleStorage();
|
||||
### 5. 数据导出
|
||||
|
||||
```typescript
|
||||
// utils/exporter.ts
|
||||
import type { DataRule } from '../types';
|
||||
// src/utils/dataExporter.ts
|
||||
import type { DataRule } from '@/types/testDataGenerator';
|
||||
|
||||
export interface ExportFile {
|
||||
filename: string;
|
||||
content: string;
|
||||
mimeType: string;
|
||||
}
|
||||
|
||||
export class DataExporter {
|
||||
// 导出为 JSON
|
||||
@@ -1297,47 +1412,56 @@ export class DataExporter {
|
||||
return str;
|
||||
}
|
||||
|
||||
// 导出为 SQL
|
||||
static toSQL(data: any[], tableName: string = 'data'): string {
|
||||
if (data.length === 0) return '';
|
||||
// 根据格式导出数据
|
||||
static exportByFormat(data: any[], format: 'json' | 'csv'): string {
|
||||
switch (format) {
|
||||
case 'json':
|
||||
return this.toJSON(data);
|
||||
case 'csv':
|
||||
return this.toCSV(data);
|
||||
default:
|
||||
return this.toJSON(data);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// 批量导出多个规则(每个规则一个文件)
|
||||
static toMultipleFiles(rules: DataRule[], format: 'json' | 'csv'): ExportFile[] {
|
||||
return rules.map((rule) => {
|
||||
const content = this.exportByFormat([rule], format);
|
||||
|
||||
return {
|
||||
filename: `${rule.name}.${format}`,
|
||||
content,
|
||||
mimeType: this.getMimeType(format),
|
||||
};
|
||||
});
|
||||
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 '';
|
||||
// 合并导出多个规则(所有规则合并为一个文件)
|
||||
static toSingleFile(rules: DataRule[], format: 'json' | 'csv'): ExportFile {
|
||||
// 合并所有规则的字段配置
|
||||
const mergedFields = rules.flatMap((rule) => rule.fields);
|
||||
|
||||
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[]';
|
||||
// 合并导出内容
|
||||
const content = this.exportByFormat([mergedFields], format);
|
||||
|
||||
return ` ${key}: ${type};`;
|
||||
});
|
||||
|
||||
return `interface ${interfaceName} {\n${fields.join('\n')}\n}\n\n// 示例数据\nconst data: ${interfaceName}[] = ${JSON.stringify(data, null, 2)};`;
|
||||
return {
|
||||
filename: `merged_rules.${format}`,
|
||||
content,
|
||||
mimeType: this.getMimeType(format),
|
||||
};
|
||||
}
|
||||
|
||||
// 下载文件
|
||||
// 根据格式获取 MIME 类型
|
||||
private static getMimeType(format: string): string {
|
||||
const mimeTypes: Record<string, string> = {
|
||||
json: 'application/json',
|
||||
csv: 'text/csv',
|
||||
};
|
||||
return mimeTypes[format] || 'text/plain';
|
||||
}
|
||||
|
||||
// 下载单个文件
|
||||
static download(content: string, filename: string, mimeType: string = 'text/plain'): void {
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -1349,6 +1473,37 @@ export class DataExporter {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// 批量下载多个文件
|
||||
static downloadMultiple(files: ExportFile[]): void {
|
||||
files.forEach((file) => {
|
||||
this.download(file.content, file.filename, file.mimeType);
|
||||
});
|
||||
}
|
||||
|
||||
// 下载为 ZIP(需要引入 jszip 库)
|
||||
static async downloadAsZip(
|
||||
files: ExportFile[],
|
||||
zipFilename: string = 'export.zip',
|
||||
): Promise<void> {
|
||||
// 动态导入 jszip
|
||||
const JSZip = (await import('jszip')).default;
|
||||
const zip = new JSZip();
|
||||
|
||||
files.forEach((file) => {
|
||||
zip.file(file.filename, file.content);
|
||||
});
|
||||
|
||||
const content = await zip.generateAsync({ type: 'blob' });
|
||||
const url = URL.createObjectURL(content);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = zipFilename;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1359,9 +1514,9 @@ export class DataExporter {
|
||||
### 创建 Worker
|
||||
|
||||
```typescript
|
||||
// hooks/useGenerator.ts
|
||||
// src/pages/TestDataGenerator/hooks/useGenerator.ts
|
||||
import { useRef, useState, useCallback } from 'react';
|
||||
import type { DataRule, GenerateResult } from '../types';
|
||||
import type { DataRule, GenerateResult } from '@/types/testDataGenerator';
|
||||
|
||||
export function useGenerator() {
|
||||
const workerRef = useRef<Worker | null>(null);
|
||||
@@ -1373,15 +1528,18 @@ export function useGenerator() {
|
||||
return new Promise((resolve, reject) => {
|
||||
setIsGenerating(true);
|
||||
setProgress(0);
|
||||
setResult(null);
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
// 创建 Worker
|
||||
workerRef.current = new Worker(new URL('../worker/generator.worker.ts', import.meta.url), {
|
||||
workerRef.current = new Worker(new URL('@/workers/generator.worker.ts', import.meta.url), {
|
||||
type: 'module',
|
||||
});
|
||||
|
||||
// 监听消息
|
||||
workerRef.current.onmessage = (e) => {
|
||||
const { type, current, total, data, error } = e.data;
|
||||
const { type, current, total, data, error, stats } = e.data;
|
||||
|
||||
if (type === 'progress') {
|
||||
setProgress(Math.round((current / total) * 100));
|
||||
@@ -1391,14 +1549,17 @@ export function useGenerator() {
|
||||
setIsGenerating(false);
|
||||
setProgress(100);
|
||||
|
||||
const duration = Date.now() - startTime;
|
||||
|
||||
const result: GenerateResult = {
|
||||
success: true,
|
||||
data,
|
||||
stats: {
|
||||
total: rule.options.total,
|
||||
generated: data.length,
|
||||
failed: 0,
|
||||
duration: 0,
|
||||
total: stats.total,
|
||||
generated: stats.generated,
|
||||
failed: stats.failed,
|
||||
failedFields: stats.failedFields,
|
||||
duration,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1412,6 +1573,12 @@ export function useGenerator() {
|
||||
}
|
||||
};
|
||||
|
||||
// 错误处理
|
||||
workerRef.current.onerror = (e) => {
|
||||
setIsGenerating(false);
|
||||
reject(new Error(e.message || 'Worker 执行出错'));
|
||||
};
|
||||
|
||||
// 发送任务
|
||||
workerRef.current.postMessage({
|
||||
rule,
|
||||
@@ -1425,6 +1592,7 @@ export function useGenerator() {
|
||||
workerRef.current.terminate();
|
||||
workerRef.current = null;
|
||||
setIsGenerating(false);
|
||||
setProgress(0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -1438,6 +1606,236 @@ export function useGenerator() {
|
||||
}
|
||||
```
|
||||
|
||||
### 错误处理
|
||||
|
||||
```typescript
|
||||
// src/pages/TestDataGenerator/hooks/useErrorHandler.ts
|
||||
import { useCallback } from 'react';
|
||||
import type { GenerateResult } from '@/types/testDataGenerator';
|
||||
|
||||
export interface ErrorHandlerOptions {
|
||||
onSuccess?: (result: GenerateResult) => void;
|
||||
onWarning?: (message: string, failedFields: string[]) => void;
|
||||
onError?: (error: Error) => void;
|
||||
}
|
||||
|
||||
export function useErrorHandler(options: ErrorHandlerOptions = {}) {
|
||||
const handleGenerateResult = useCallback(
|
||||
(result: GenerateResult) => {
|
||||
// 1. 检查是否有失败字段
|
||||
if (result.stats.failed > 0) {
|
||||
const message = `有 ${result.stats.failed} 个字段生成失败:${result.stats.failedFields?.join('、')}`;
|
||||
options.onWarning?.(message, result.stats.failedFields || []);
|
||||
} else {
|
||||
options.onSuccess?.(result);
|
||||
}
|
||||
},
|
||||
[options],
|
||||
);
|
||||
|
||||
const handleGenerateError = useCallback(
|
||||
(error: Error) => {
|
||||
options.onError?.(error);
|
||||
},
|
||||
[options],
|
||||
);
|
||||
|
||||
return {
|
||||
handleGenerateResult,
|
||||
handleGenerateError,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误提示机制
|
||||
|
||||
### 错误类型分类
|
||||
|
||||
| 错误类型 | 严重程度 | 触发场景 | 提示方式 |
|
||||
| --------------- | -------- | ------------------------ | ------------- |
|
||||
| 生成器不存在 | 🔴 严重 | 字段配置了不存在的生成器 | toast.error |
|
||||
| Worker 执行错误 | 🔴 严重 | Web Worker 内部异常 | toast.error |
|
||||
| 重试达到上限 | 🟡 警告 | 唯一性字段无法生成唯一值 | toast.warning |
|
||||
| 部分字段失败 | 🟡 警告 | 生成完成但有字段失败 | 结果面板警告 |
|
||||
| 生成成功 | 🟢 成功 | 全部字段生成成功 | toast.success |
|
||||
|
||||
### 提示方式实现
|
||||
|
||||
项目使用 `sonner` 库作为 Toast 提示组件(已在项目中配置),导入方式:
|
||||
|
||||
```typescript
|
||||
import { toast } from 'sonner';
|
||||
```
|
||||
|
||||
```typescript
|
||||
// src/pages/TestDataGenerator/index.tsx
|
||||
import { useGenerator } from './hooks/useGenerator';
|
||||
import { useErrorHandler } from './hooks/useErrorHandler';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export default function TestDataGenerator() {
|
||||
const { generate, progress, isGenerating, result } = useGenerator();
|
||||
|
||||
const { handleGenerateResult, handleGenerateError } = useErrorHandler({
|
||||
// 成功回调
|
||||
onSuccess: (result) => {
|
||||
toast.success(`成功生成 ${result.stats.generated} 条数据`);
|
||||
},
|
||||
// 警告回调(部分字段失败)
|
||||
onWarning: (message, failedFields) => {
|
||||
toast.warning(message);
|
||||
},
|
||||
// 错误回调(严重错误)
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
|
||||
const handleGenerate = async (rule: DataRule) => {
|
||||
try {
|
||||
const result = await generate(rule);
|
||||
handleGenerateResult(result);
|
||||
} catch (error) {
|
||||
handleGenerateError(error as Error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 生成按钮 */}
|
||||
<GenerateButton
|
||||
onClick={() => handleGenerate(currentRule)}
|
||||
disabled={isGenerating}
|
||||
loading={isGenerating}
|
||||
/>
|
||||
|
||||
{/* 进度条 */}
|
||||
{isGenerating && <ProgressBar progress={progress} />}
|
||||
|
||||
{/* 结果面板(包含警告信息) */}
|
||||
{result && <ResultPanel result={result} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 结果面板(含警告)
|
||||
|
||||
```typescript
|
||||
// src/pages/TestDataGenerator/components/ResultPanel.tsx
|
||||
import type { GenerateResult } from '@/types/testDataGenerator';
|
||||
|
||||
interface ResultPanelProps {
|
||||
result: GenerateResult;
|
||||
}
|
||||
|
||||
export function ResultPanel({ result }: ResultPanelProps) {
|
||||
const { stats } = result;
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* 成功信息 */}
|
||||
<div className="p-4 bg-green-50 border border-green-200 rounded">
|
||||
<h3 className="text-green-800 font-medium">生成完成</h3>
|
||||
<p className="text-green-600">
|
||||
成功生成 {stats.generated} 条数据,耗时 {stats.duration}ms
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 警告信息(有失败字段) */}
|
||||
{stats.failed > 0 && (
|
||||
<div className="p-4 bg-yellow-50 border border-yellow-200 rounded">
|
||||
<h3 className="text-yellow-800 font-medium">部分字段生成失败</h3>
|
||||
<p className="text-yellow-600">
|
||||
有 {stats.failed} 个字段无法生成唯一值:
|
||||
</p>
|
||||
<ul className="list-disc list-inside text-yellow-600 mt-2">
|
||||
{stats.failedFields?.map((field) => (
|
||||
<li key={field}>"{field}" - 重试 100 次后仍存在重复</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-yellow-500 text-sm mt-2">
|
||||
这些字段的值已设置为空,您可以尝试减少生成数量或调整字段配置
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 数据预览 */}
|
||||
<div className="border rounded p-4">
|
||||
<h3 className="font-medium mb-2">数据预览</h3>
|
||||
<pre className="text-sm overflow-auto max-h-64">
|
||||
{JSON.stringify(result.data.slice(0, 5), null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Toast 使用示例
|
||||
|
||||
项目使用 `sonner` 库,已配置在 `src/components/ui/sonner.tsx` 中,使用方式:
|
||||
|
||||
```typescript
|
||||
import { toast } from 'sonner';
|
||||
|
||||
// 成功提示
|
||||
toast.success('操作成功');
|
||||
|
||||
// 警告提示
|
||||
toast.warning('有部分数据生成失败');
|
||||
|
||||
// 错误提示
|
||||
toast.error('生成失败:生成器不存在');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误处理流程图
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ 错误处理流程 │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 用户点击"生成" │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ Worker 执行生成 │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ 是 ┌─────────────────┐ │
|
||||
│ │ 生成器不存在? │ ───────→ │ toast.error │ │
|
||||
│ └────────┬────────┘ └─────────────────┘ │
|
||||
│ │ 否 │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ 循环生成数据 │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ 是 ┌─────────────────┐ │
|
||||
│ │ 唯一性重试失败?│ ───────→ │ 记录失败字段 │ │
|
||||
│ └────────┬────────┘ └─────────────────┘ │
|
||||
│ │ 否 │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ 发送完成结果 │ │
|
||||
│ └────────┬────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ 有 ┌─────────────────┐ │
|
||||
│ │ failedFields? │ ────────→ │ toast.warning │ │
|
||||
│ └────────┬────────┘ │ 结果面板显示 │ │
|
||||
│ │ 无 └─────────────────┘ │
|
||||
│ ↓ │
|
||||
│ ┌─────────────────┐ │
|
||||
│ │ toast.success │ │
|
||||
│ └─────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能优化
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 格式切换: │
|
||||
│ [JSON] [CSV] [SQL] [TypeScript] │
|
||||
│ [JSON] [CSV] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
@@ -196,8 +196,8 @@
|
||||
│ │ │ │ │ │ │ │
|
||||
│ │ [100 ] │ │ ○ JSON │ │ ☑ name 唯一 │ │
|
||||
│ │ [10 ▼] │ │ ○ CSV │ │ ☑ email 唯一 │ │
|
||||
│ │ [100 ▼] │ │ ○ SQL │ │ ☑ phone 唯一 │ │
|
||||
│ │ [自定义 ] │ │ ○ TypeScript │ │ ☐ age 唯一 │ │
|
||||
│ │ [100 ▼] │ │ │ │ ☑ phone 唯一 │ │
|
||||
│ │ [自定义 ] │ │ │ │ ☐ age 唯一 │ │
|
||||
│ │ │ │ │ │ │ │
|
||||
│ └─────────────────┘ └─────────────────┘ └───────────────────────┘ │
|
||||
│ │
|
||||
@@ -233,22 +233,26 @@
|
||||
|
||||
### 6. 生成结果组件
|
||||
|
||||
**功能**: 展示生成结果
|
||||
**功能**: 展示生成结果,包含成功、警告、错误三种状态
|
||||
|
||||
**界面**:
|
||||
**成功状态界面**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 生成结果 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 成功生成 100 条数据 │
|
||||
│ 生成完成 │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [成功图标] 成功生成 100 条数据,耗时 1234ms │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 数据统计: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ • 总条数: 100 │ │
|
||||
│ │ • 成功条数: 100 │ │
|
||||
│ │ • 失败条数: 0 │ │
|
||||
│ │ • 文件大小: 12.5 KB │ │
|
||||
│ │ • 唯一性检查: 全部通过 │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 数据预览: │
|
||||
@@ -258,7 +262,7 @@
|
||||
│ │
|
||||
│ 导出选项: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [复制全部] [下载 JSON] [下载 CSV] [下载 SQL] │ │
|
||||
│ │ [复制全部] [下载 JSON] [下载 CSV] │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [关闭] │
|
||||
@@ -266,6 +270,137 @@
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**警告状态界面(部分字段失败)**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 生成结果 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 部分字段生成失败 │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [警告图标] 有 2 个字段生成失败 │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 失败详情: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [警告图标] "phone" - 重试 100 次后仍存在重复 │ │
|
||||
│ │ [警告图标] "email" - 重试 100 次后仍存在重复 │ │
|
||||
│ │ │ │
|
||||
│ │ 提示: 这些字段的值已设置为空,您可以尝试减少生成数量 │ │
|
||||
│ │ 或调整字段配置 │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 数据统计: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ • 总条数: 100 │ │
|
||||
│ │ • 成功条数: 98 │ │
|
||||
│ │ • 失败条数: 2 │ │
|
||||
│ │ • 文件大小: 12.3 KB │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 数据预览: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [JSON 格式预览...] │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 导出选项: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [复制全部] [下载 JSON] [下载 CSV] │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [关闭] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**错误状态界面(生成失败)**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ 生成失败 │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ [错误图标] 生成过程中发生错误 │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 错误详情: │
|
||||
│ ┌───────────────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 错误类型: 生成器不存在 │ │
|
||||
│ │ 错误信息: 字段 "phone" 的生成器 "chinesePhone1" 不存在 │ │
|
||||
│ │ 错误位置: 字段配置第 2 行 │ │
|
||||
│ │ │ │
|
||||
│ │ 建议操作: │ │
|
||||
│ │ 1. 检查字段配置中的生成器名称是否正确 │ │
|
||||
│ │ 2. 确认该生成器已正确注册 │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ [重新配置] [关闭] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.1 Toast 提示组件
|
||||
|
||||
**功能**: 显示临时提示信息
|
||||
|
||||
**成功提示**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ [成功图标] 数据生成成功 │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**警告提示**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [警告图标] 有 2 个字段生成失败:phone、email │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**错误提示**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [错误图标] 生成失败:生成器 "xxx" 不存在 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6.2 Modal 提示组件
|
||||
|
||||
**功能**: 显示严重错误的模态框
|
||||
|
||||
**错误模态框**:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ [错误图标] 生成失败 │ │
|
||||
│ │ │ │
|
||||
│ │ 字段 "phone" 的生成器 "chinesePhone1" 不存在 │ │
|
||||
│ │ │ │
|
||||
│ │ 请检查字段配置中的生成器名称是否正确 │ │
|
||||
│ │ │ │
|
||||
│ │ ─────────────────────────────────────────────────── │ │
|
||||
│ │ │ │
|
||||
│ │ [ 确定 ] │ │
|
||||
│ │ │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 规则管理组件
|
||||
|
||||
Reference in New Issue
Block a user