Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 36b6bfd20d |
@@ -29,20 +29,3 @@
|
|||||||
- 导出工具:`src/utils/dataExporter.ts`
|
- 导出工具:`src/utils/dataExporter.ts`
|
||||||
|
|
||||||
实现约束与任务完成状态记录在 [TASKS.md](./TASKS.md)。
|
实现约束与任务完成状态记录在 [TASKS.md](./TASKS.md)。
|
||||||
|
|
||||||
## 开发者注意事项(与源码同步)
|
|
||||||
|
|
||||||
以下行为以 `src/` 源码为准;设计文档中的旧版 class API、`metadata`/`options` 嵌套结构已废弃。
|
|
||||||
|
|
||||||
### Worker 任务 ID(`generationId`)
|
|
||||||
|
|
||||||
`useGenerator` 每次调用 `generate()` 递增 `generationIdRef`,经 `start` 消息传入 Worker。Worker 所有响应(`progress` / `complete` / `error`)均携带同一 `generationId`。
|
|
||||||
|
|
||||||
- **取消**:`cancel()` 先递增 ID 再发送 `cancel`,使进行中的 Worker 响应被主线程忽略;Worker 每生成 100 行让出事件循环以处理 cancel。
|
|
||||||
- **快速重试**:新任务 ID 大于旧响应时,旧消息被丢弃,避免 UI 状态错乱。
|
|
||||||
|
|
||||||
类型见 `WorkerRequestMessage` / `WorkerResponseMessage`(`src/types/testDataGenerator.ts`)。
|
|
||||||
|
|
||||||
### 规则存储写入失败
|
|
||||||
|
|
||||||
`ruleStorage.save()` / `update()` 在 `localStorage.setItem` 失败时返回 `null`(不部分提交)。页面层(如 `FieldList.tsx`)仅在返回值非空时 Toast 成功。详见 [rule-management.md § 存储机制](./rule-management.md#存储机制) 与 `src/utils/README.md`。
|
|
||||||
|
|||||||
@@ -24,20 +24,25 @@
|
|||||||
|
|
||||||
## 数据结构
|
## 数据结构
|
||||||
|
|
||||||
> **与源码对齐**:类型定义见 `src/types/testDataGenerator.ts`。规则**不**持久化生成数量与导出格式(由页面 `GenerateOptions` 状态管理)。
|
|
||||||
|
|
||||||
### 规则模板
|
### 规则模板
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface DataRule {
|
interface DataRule {
|
||||||
id: string;
|
id: string; // 唯一标识
|
||||||
name: string;
|
name: string; // 规则名称
|
||||||
description?: string;
|
description?: string; // 规则描述
|
||||||
fields: FieldConfig[];
|
fields: FieldConfig[]; // 字段配置
|
||||||
createdAt: number;
|
options: {
|
||||||
updatedAt: number;
|
total: number; // 生成数量
|
||||||
lastUsedAt?: number;
|
format: 'json' | 'csv';
|
||||||
useCount: number;
|
defaultEmptyRate: number; // 默认空值率(0-100)
|
||||||
|
};
|
||||||
|
metadata: {
|
||||||
|
createdAt: number; // 创建时间
|
||||||
|
updatedAt: number; // 更新时间
|
||||||
|
lastUsedAt?: number; // 最后使用时间
|
||||||
|
useCount: number; // 使用次数
|
||||||
|
};
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -45,14 +50,13 @@ interface DataRule {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface FieldConfig {
|
interface FieldConfig {
|
||||||
id: string;
|
id: string; // 字段唯一标识
|
||||||
name: string;
|
name: string; // 字段名
|
||||||
description?: string;
|
generator: string; // 生成器名称
|
||||||
generatorId: string; // 生成器 ID,对应 lib/generators 中的 id
|
params: Record<string, any>; // 生成器参数
|
||||||
params: Record<string, unknown>;
|
unique: boolean; // 唯一性约束
|
||||||
required: boolean;
|
required: boolean; // 是否必填
|
||||||
nullRate: number; // 空值率 0-100,仅 required=false 时生效
|
emptyRate?: number; // 选填字段的空值概率(0-100)
|
||||||
unique: boolean;
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,9 +110,11 @@ interface FieldConfig {
|
|||||||
|
|
||||||
1. 验证规则名称不为空
|
1. 验证规则名称不为空
|
||||||
2. 检查规则数量是否达到上限(20 条)
|
2. 检查规则数量是否达到上限(20 条)
|
||||||
3. 若达上限,`save()` 返回 `null`,UI 应阻止或提示
|
3. 如果达到上限,显示提示"已达到最大规则数量"
|
||||||
4. 生成唯一 ID,写入 `createdAt`/`updatedAt`,`useCount` 初始为 0
|
4. 生成唯一 ID
|
||||||
5. 调用 `ruleStorage.save()`;仅当返回值非 `null` 时视为成功(`localStorage` 写入失败同样返回 `null`)
|
5. 设置创建时间和更新时间
|
||||||
|
6. 初始化使用次数为 0
|
||||||
|
7. 保存到 localStorage
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -161,13 +167,17 @@ interface FieldConfig {
|
|||||||
- 支持按规则描述搜索
|
- 支持按规则描述搜索
|
||||||
- 搜索为模糊匹配,不区分大小写
|
- 搜索为模糊匹配,不区分大小写
|
||||||
|
|
||||||
**实现方式**(`src/utils/ruleStorage.ts`):
|
**实现方式**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import * as ruleStorage from '@/utils/ruleStorage';
|
search(keyword: string): DataRule[] {
|
||||||
|
const rules = this.getAll();
|
||||||
|
const lowerKeyword = keyword.toLowerCase();
|
||||||
|
|
||||||
function searchRules(query: string): DataRule[] {
|
return rules.filter(r =>
|
||||||
return ruleStorage.search(query);
|
r.name.toLowerCase().includes(lowerKeyword) ||
|
||||||
|
r.description?.toLowerCase().includes(lowerKeyword)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -214,7 +224,9 @@ function searchRules(query: string): DataRule[] {
|
|||||||
|
|
||||||
1. 加载原规则配置到编辑器
|
1. 加载原规则配置到编辑器
|
||||||
2. 用户修改配置
|
2. 用户修改配置
|
||||||
3. 点击保存时调用 `ruleStorage.update()`;返回非 `null` 才更新 `updatedAt` 并提示成功
|
3. 点击保存时更新规则
|
||||||
|
4. 更新 metadata.updatedAt
|
||||||
|
5. 保存到 localStorage
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -257,12 +269,18 @@ function searchRules(query: string): DataRule[] {
|
|||||||
**实现方式**:
|
**实现方式**:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
function loadRule(ruleId: string): void {
|
loadRule(ruleId: string): void {
|
||||||
const rule = ruleStorage.getById(ruleId);
|
const rule = this.storage.getById(ruleId);
|
||||||
if (!rule) return;
|
|
||||||
|
|
||||||
setFields(rule.fields);
|
// 应用规则配置
|
||||||
ruleStorage.recordUse(ruleId);
|
this.setFields(rule.fields);
|
||||||
|
this.setOptions(rule.options);
|
||||||
|
|
||||||
|
// 记录使用
|
||||||
|
this.storage.recordUse(ruleId);
|
||||||
|
|
||||||
|
// 更新预览
|
||||||
|
this.updatePreview();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -274,7 +292,7 @@ function loadRule(ruleId: string): void {
|
|||||||
|
|
||||||
1. 点击"复制"按钮
|
1. 点击"复制"按钮
|
||||||
2. 创建规则的副本
|
2. 创建规则的副本
|
||||||
3. 名称添加「(副本)」后缀(默认 `duplicate(id, '(副本)')`)
|
3. 名称添加"(副本)"后缀
|
||||||
4. 生成新的 ID
|
4. 生成新的 ID
|
||||||
5. 保存为新规则
|
5. 保存为新规则
|
||||||
|
|
||||||
@@ -309,20 +327,23 @@ function loadRule(ruleId: string): void {
|
|||||||
└─────────────────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
**导出格式**(`exportRules()` 返回 `DataRule[]` 的 JSON 字符串,无外层包装):
|
**导出格式**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
[
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"exportedAt": "2024-01-20T10:15:45.000Z",
|
||||||
|
"rules": [
|
||||||
{
|
{
|
||||||
"id": "rule_123456",
|
"id": "rule_123456",
|
||||||
"name": "电商用户数据 - 测试用",
|
"name": "电商用户数据 - 测试用",
|
||||||
"description": "用于测试用户注册功能",
|
"description": "用于测试用户注册功能",
|
||||||
"fields": [],
|
"fields": [...],
|
||||||
"createdAt": 1704067200000,
|
"options": {...},
|
||||||
"updatedAt": 1704067200000,
|
"metadata": {...}
|
||||||
"useCount": 0
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -380,25 +401,63 @@ function loadRule(ruleId: string): void {
|
|||||||
|
|
||||||
### 本地存储
|
### 本地存储
|
||||||
|
|
||||||
使用 `localStorage`,键名 `testDataGenerator_rules`。API 为**命名导出函数**(见 `src/utils/ruleStorage.ts`):
|
使用 localStorage 存储规则数据:
|
||||||
|
|
||||||
| 函数 | 说明 |
|
|
||||||
| ---- | ---- |
|
|
||||||
| `getAll()` / `getById()` / `getByName()` | 读取 |
|
|
||||||
| `save()` / `update()` / `deleteRule()` / `duplicate()` | 写入;失败时返回 `null` 或 `false` |
|
|
||||||
| `recordUse()` | 递增 `useCount`、更新 `lastUsedAt` |
|
|
||||||
| `search()` / `getRecent()` | 搜索与最近使用 |
|
|
||||||
| `exportRules()` / `importRules()` | 导入导出 JSON 数组 |
|
|
||||||
| `clear()` | 清空全部规则 |
|
|
||||||
|
|
||||||
写入失败(如 `QuotaExceededError`)时,内部 `setAll()` 返回 `false`,`save`/`update` 返回 `null`,`deleteRule` 返回 `false`,并在控制台输出 `[ruleStorage] 保存规则失败`。调用方须检查返回值,避免误报成功。
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import * as ruleStorage from '@/utils/ruleStorage';
|
class RuleStorage {
|
||||||
|
private readonly STORAGE_KEY = 'testDataGenerator_rules';
|
||||||
|
|
||||||
const saved = ruleStorage.save({ name: '示例', fields });
|
// 获取所有规则
|
||||||
if (!saved) {
|
getAll(): DataRule[] {
|
||||||
// 达上限或 localStorage 不可用
|
const data = localStorage.getItem(this.STORAGE_KEY);
|
||||||
|
return data ? JSON.parse(data) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存规则
|
||||||
|
save(rule: DataRule): { success: boolean; message?: string } {
|
||||||
|
const rules = this.getAll();
|
||||||
|
|
||||||
|
// 规则数量限制:最多 20 条
|
||||||
|
const MAX_RULES = 20;
|
||||||
|
if (rules.length >= MAX_RULES) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: `已达到最大规则数量(${MAX_RULES}条),请删除一些规则后再保存`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rules.push(rule);
|
||||||
|
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(rules));
|
||||||
|
return { success: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新规则
|
||||||
|
update(id: string, updates: Partial<DataRule>): 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# 技术实现
|
# 技术实现
|
||||||
|
|
||||||
> **文档同步说明**:本文档含早期设计稿代码示例,部分类型/API 已与实现偏离。开发时请以 `src/types/testDataGenerator.ts`、`src/workers/generator.worker.ts`、`src/pages/TestDataGenerator/hooks/useGenerator.ts`、`src/utils/ruleStorage.ts` 为准。近期变更摘要见 [README § 开发者注意事项](./README.md#开发者注意事项与源码同步)。
|
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
| 技术 | 用途 | 版本 |
|
| 技术 | 用途 | 版本 |
|
||||||
@@ -1500,69 +1498,6 @@ export class DataExporter {
|
|||||||
|
|
||||||
## Web Worker 使用
|
## Web Worker 使用
|
||||||
|
|
||||||
### 消息协议
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/types/testDataGenerator.ts
|
|
||||||
|
|
||||||
type WorkerRequestMessage =
|
|
||||||
| { type: 'start'; payload: WorkerStartPayload }
|
|
||||||
| { type: 'cancel' };
|
|
||||||
|
|
||||||
type WorkerResponseMessage =
|
|
||||||
| { type: 'progress'; generationId: number; payload: GenerateProgress }
|
|
||||||
| { type: 'complete'; generationId: number; payload: GenerateResult }
|
|
||||||
| { type: 'error'; generationId: number; payload: { error: string } };
|
|
||||||
|
|
||||||
interface WorkerStartPayload {
|
|
||||||
generationId: number; // 任务 ID,用于忽略过期响应
|
|
||||||
fields: FieldConfig[];
|
|
||||||
count: number;
|
|
||||||
csvMode: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### useGenerator Hook
|
|
||||||
|
|
||||||
`src/pages/TestDataGenerator/hooks/useGenerator.ts` 负责 Worker 生命周期与 `generationId` 管理:
|
|
||||||
|
|
||||||
- Worker **复用**:同一 Hook 实例内只创建一次,出错后 `terminate` 并在下次重建
|
|
||||||
- **开始生成**:`generate(fields, count, csvMode?)` 递增 `generationId` 并 post `start`
|
|
||||||
- **取消**:`cancel()` 递增 ID(使旧响应失效)并 post `cancel`;取消完成的 `complete` 不写入 `error` 状态
|
|
||||||
- **响应过滤**:`onmessage` 中若 `data.generationId !== generationIdRef.current` 则忽略
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const generate = (fields: FieldConfig[], count: number, csvMode = false) => {
|
|
||||||
if (isGenerating) return;
|
|
||||||
const generationId = ++generationIdRef.current;
|
|
||||||
worker.postMessage({ type: 'start', payload: { generationId, fields, count, csvMode } });
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancel = () => {
|
|
||||||
if (workerRef.current && isGenerating) {
|
|
||||||
++generationIdRef.current;
|
|
||||||
worker.postMessage({ type: 'cancel' });
|
|
||||||
setIsGenerating(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Worker 实现要点
|
|
||||||
|
|
||||||
`src/workers/generator.worker.ts`:
|
|
||||||
|
|
||||||
- 按 `field.generatorId` 查找生成器;选填字段按 `nullRate` 随机置 `null`
|
|
||||||
- 唯一性:≤1000 条随机+重试;>1000 条优先 `generateAtIndex`
|
|
||||||
- 每 `YIELD_EVERY`(100)行 `await setTimeout(0)`,以便处理 `cancel`
|
|
||||||
- 进度:每 1000 条或最后一行 post `progress`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Web Worker 使用(历史设计稿,仅供参考)
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>展开查看旧版设计示例(与当前实现不一致)</summary>
|
|
||||||
|
|
||||||
### 创建 Worker
|
### 创建 Worker
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
@@ -1658,7 +1593,7 @@ export function useGenerator() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 错误处理(设计稿,`useErrorHandler.ts` 未实现)
|
### 错误处理
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/pages/TestDataGenerator/hooks/useErrorHandler.ts
|
// src/pages/TestDataGenerator/hooks/useErrorHandler.ts
|
||||||
@@ -1701,12 +1636,8 @@ export function useErrorHandler(options: ErrorHandlerOptions = {}) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## 错误提示机制
|
## 错误提示机制
|
||||||
|
|
||||||
> 当前实现:`TestDataGenerator/index.tsx` 直接使用 `useGenerator` 的 `error`/`result` 与 `ResultPanel` 展示警告;下方示例引用未实现的 `useErrorHandler`,仅供对照。
|
|
||||||
|
|
||||||
### 错误类型分类
|
### 错误类型分类
|
||||||
|
|
||||||
| 错误类型 | 严重程度 | 触发场景 | 提示方式 |
|
| 错误类型 | 严重程度 | 触发场景 | 提示方式 |
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
| `SwitchButtonGroup.tsx` | 通用切换按钮组,支持 `small/medium/large` 三种尺寸,用于页面子模式切换 |
|
| `SwitchButtonGroup.tsx` | 通用切换按钮组,支持 `small/medium/large` 三种尺寸,用于页面子模式切换 |
|
||||||
| `EmptyPlaceholder.tsx` | 虚线边框空状态占位,统一工具页「暂无结果」提示样式 |
|
| `EmptyPlaceholder.tsx` | 虚线边框空状态占位,统一工具页「暂无结果」提示样式 |
|
||||||
| `TextInputArea.tsx` | 增强文本输入区域,支持校验规则、工具栏操作、字符计数、清空 |
|
| `TextInputArea.tsx` | 增强文本输入区域,支持校验规则、工具栏操作、字符计数、清空 |
|
||||||
| `CopyButton.tsx` | 一键复制按钮:复制成功后 1.5s 内切换为 Check 图标并应用 `text-emerald-500`;空内容/失败时 `toast` 提示 |
|
| `CopyButton.tsx` | 一键复制按钮,支持复制成功状态动画,封装 `copyTextToClipboard` 和 `toast` 反馈 |
|
||||||
| `ImageUploader.tsx` | 图片上传组件,支持拖拽上传、文件选择和预览 |
|
| `ImageUploader.tsx` | 图片上传组件,支持拖拽上传、文件选择和预览 |
|
||||||
| `QrCodePreview.tsx` | 二维码预览组件,展示生成的二维码图片,提供复制和下载操作 |
|
| `QrCodePreview.tsx` | 二维码预览组件,展示生成的二维码图片,提供复制和下载操作 |
|
||||||
| `DecodeResultPaper.tsx` | Base64 解码结果展示面板,显示 MIME 类型、文件大小、文件名输入和下载按钮 |
|
| `DecodeResultPaper.tsx` | Base64 解码结果展示面板,显示 MIME 类型、文件大小、文件名输入和下载按钮 |
|
||||||
|
|||||||
+1
-3
@@ -39,9 +39,7 @@
|
|||||||
- `FieldConfig` — 字段配置(字段名、生成器、参数、必填、空值率、唯一性)
|
- `FieldConfig` — 字段配置(字段名、生成器、参数、必填、空值率、唯一性)
|
||||||
- `DataRule` — 可保存/导入/导出的字段规则
|
- `DataRule` — 可保存/导入/导出的字段规则
|
||||||
- `GeneratorDefinition` / `GeneratorParam` — 内置生成器定义和参数 Schema
|
- `GeneratorDefinition` / `GeneratorParam` — 内置生成器定义和参数 Schema
|
||||||
- `GenerateResult` / `GenerateProgress` — Worker 生成结果与进度
|
- `GenerateResult` / `GenerateProgress` / `WorkerMessage` — Worker 生成结果、进度和消息协议
|
||||||
- `WorkerRequestMessage` / `WorkerResponseMessage` — Worker 消息协议;每条响应携带 `generationId`,用于忽略过期任务(取消或快速重试时)
|
|
||||||
- `WorkerMessage` — 已废弃,请使用上述两种消息类型
|
|
||||||
- `ExportFile` — JSON/CSV 导出文件描述
|
- `ExportFile` — JSON/CSV 导出文件描述
|
||||||
|
|
||||||
## 修改 StorageSchema 的注意事项
|
## 修改 StorageSchema 的注意事项
|
||||||
|
|||||||
+1
-18
@@ -24,7 +24,7 @@
|
|||||||
| `textStatistics.ts` | 文本统计:使用 `Intl.Segmenter` 计算字符数/单词数/行数/字节大小 |
|
| `textStatistics.ts` | 文本统计:使用 `Intl.Segmenter` 计算字符数/单词数/行数/字节大小 |
|
||||||
| `format.ts` | 通用格式化:`formatBytes` 将字节转为可读字符串(B/KB/MB/GB/TB) |
|
| `format.ts` | 通用格式化:`formatBytes` 将字节转为可读字符串(B/KB/MB/GB/TB) |
|
||||||
| `dayjs.ts` | Day.js 初始化:扩展 UTC、Timezone、RelativeTime 插件,加载中文本地化 |
|
| `dayjs.ts` | Day.js 初始化:扩展 UTC、Timezone、RelativeTime 插件,加载中文本地化 |
|
||||||
| `ruleStorage.ts` | 测试数据生成器规则存储:基于 `localStorage` 的 CRUD、搜索、导入/导出和数量限制(见下方说明) |
|
| `ruleStorage.ts` | 测试数据生成器规则存储:基于 `localStorage` 的 CRUD、搜索、导入/导出和数量限制 |
|
||||||
| `dataExporter.ts` | 测试数据导出:JSON/CSV 转换、文件下载和复制到剪贴板 |
|
| `dataExporter.ts` | 测试数据导出:JSON/CSV 转换、文件下载和复制到剪贴板 |
|
||||||
| `rightClickInjection.ts` | 右键恢复注入脚本:在页面上下文恢复 contextmenu/copy/paste 等事件默认行为 |
|
| `rightClickInjection.ts` | 右键恢复注入脚本:在页面上下文恢复 contextmenu/copy/paste 等事件默认行为 |
|
||||||
|
|
||||||
@@ -36,23 +36,6 @@
|
|||||||
| `useContextMenuData.ts` | 右键菜单数据 Hook:从 storage 读取待处理数据,匹配 featureKey 后消费并触发回调 |
|
| `useContextMenuData.ts` | 右键菜单数据 Hook:从 storage 读取待处理数据,匹配 featureKey 后消费并触发回调 |
|
||||||
| `useDebounce.ts` | 防抖 Hook:对值进行延迟更新,避免频繁触发 |
|
| `useDebounce.ts` | 防抖 Hook:对值进行延迟更新,避免频繁触发 |
|
||||||
|
|
||||||
### ruleStorage 写入失败处理
|
|
||||||
|
|
||||||
`ruleStorage.ts` 使用函数式导出(非 class),存储键为 `testDataGenerator_rules`,最多 `MAX_RULES = 20` 条。
|
|
||||||
|
|
||||||
写入经内部 `setAll()` 完成;`localStorage.setItem` 抛错(如配额超限)时返回 `false`,并 `console.error`,**不会部分提交**:
|
|
||||||
|
|
||||||
| 方法 | 写入失败返回值 | 常见失败原因 |
|
|
||||||
| ---- | -------------- | ------------ |
|
|
||||||
| `save()` | `null` | 达上限、规则不存在(更新时)、`setItem` 异常 |
|
|
||||||
| `update()` | `null` | 规则不存在、`setItem` 异常 |
|
|
||||||
| `deleteRule()` | `false` | 规则不存在、`setItem` 异常 |
|
|
||||||
| `duplicate()` | 仍可能返回对象但数据未持久化 | 见源码:`duplicate` 未检查 `setAll` 返回值(已知缺口) |
|
|
||||||
|
|
||||||
调用方应检查返回值后再展示成功 Toast。页面层参考 `FieldList.tsx`:`save`/`update` 返回非空才提示「规则已保存/已更新」。
|
|
||||||
|
|
||||||
规则仅持久化 `name`、`description`、`fields` 及时间戳/使用统计;生成数量与导出格式由页面状态管理,不写入规则。
|
|
||||||
|
|
||||||
### useStorageState 初始化防覆盖
|
### useStorageState 初始化防覆盖
|
||||||
|
|
||||||
`useStorageState` 在挂载时从 storage 异步加载。写入 storage 需满足以下任一条件:
|
`useStorageState` 在挂载时从 storage 异步加载。写入 storage 需满足以下任一条件:
|
||||||
|
|||||||
Reference in New Issue
Block a user