Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6776a89003 |
+79
-83
@@ -265,11 +265,12 @@ const visibleSet = useMemo(() => new Set<string>(visiblePages), [visiblePages]);
|
|||||||
2. 第三方库(图标、UI 库等)
|
2. 第三方库(图标、UI 库等)
|
||||||
3. 业务 Provider / Context
|
3. 业务 Provider / Context
|
||||||
4. 配置 / 存储
|
4. 配置 / 存储
|
||||||
5. 本地页面组件
|
5. i18n
|
||||||
6. UI 组件
|
6. 本地页面组件
|
||||||
7. 工具函数 / Hook
|
7. UI 组件
|
||||||
8. 类型
|
8. 工具函数 / Hook
|
||||||
9. 常量
|
9. 类型
|
||||||
|
10. 常量
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// 1. React 核心
|
// 1. React 核心
|
||||||
@@ -283,16 +284,19 @@ import { useThemeMode } from '@/providers/ThemeModeProvider';
|
|||||||
// 4. 配置 / 存储
|
// 4. 配置 / 存储
|
||||||
import { FeatureConfig, FEATURES } from '@/config/features';
|
import { FeatureConfig, FEATURES } from '@/config/features';
|
||||||
import { storageUtil } from '@/utils/chromeStorage';
|
import { storageUtil } from '@/utils/chromeStorage';
|
||||||
// 5. 本地组件
|
// 5. i18n
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { normalizeLanguage, SUPPORTED_LANGUAGES } from '@/i18n';
|
||||||
|
// 6. 本地组件
|
||||||
import TextMode from './TextMode';
|
import TextMode from './TextMode';
|
||||||
import { ZONES } from './constants';
|
import { ZONES } from './constants';
|
||||||
// 6. UI 组件
|
// 7. UI 组件
|
||||||
import SwitchButtonGroup from '@/components/SwitchButtonGroup';
|
import SwitchButtonGroup from '@/components/SwitchButtonGroup';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
// 7. 工具函数 / Hook
|
// 8. 工具函数 / Hook
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useStorageState } from '@/utils/useStorageState';
|
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
||||||
// 8. 类型
|
// 9. 类型
|
||||||
import type { PageType, StorageSchema } from '@/types/storage';
|
import type { PageType, StorageSchema } from '@/types/storage';
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -397,22 +401,22 @@ className={cn(
|
|||||||
|
|
||||||
## 5. 错误处理
|
## 5. 错误处理
|
||||||
|
|
||||||
### 5.1 工具函数:可恢复错误返回可判断结果
|
### 5.1 工具函数:结果对象模式
|
||||||
|
|
||||||
可恢复的解析/校验错误应返回可判断的结果,避免工具层直接弹 Toast。确需保留底层异常的函数
|
工具函数**不抛异常**,返回包含 `hasError` 和 `error` 字段的结果对象:
|
||||||
(如 `formatJson` / `minifyJson`)必须在页面 Hook 或 UI 层捕获并转换为用户提示:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 可恢复校验返回错误消息,调用方据此展示 UI
|
// ✅ 结果对象模式
|
||||||
export function validateJson(text: string): string | null {
|
export function markdownToHtml(markdown: string): MarkdownToHtmlResult {
|
||||||
if (!text.trim()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
JSON.parse(text.trim());
|
...
|
||||||
return null;
|
return { html, originalLength, htmlLength, hasError: false };
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
return e instanceof SyntaxError ? e.message : 'Invalid JSON';
|
return {
|
||||||
|
html: '', ...
|
||||||
|
hasError: true,
|
||||||
|
error: error instanceof Error ? error.message : 'Markdown 解析失败',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -556,7 +560,7 @@ describe('SwitchButtonGroup 组件', () => {
|
|||||||
- 使用 `vi.mock()` 进行模块级 Mock
|
- 使用 `vi.mock()` 进行模块级 Mock
|
||||||
- 使用 `vi.fn()` 进行函数级 Mock
|
- 使用 `vi.fn()` 进行函数级 Mock
|
||||||
- 使用 `vi.useFakeTimers()` 控制时间
|
- 使用 `vi.useFakeTimers()` 控制时间
|
||||||
- **避免重复 mock `vitest.setup.ts` 中已有的内容**(chrome API、matchMedia 等)
|
- **避免重复 mock `vitest.setup.ts` 中已有的内容**(chrome API、i18n、matchMedia 等)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 模块级 Mock
|
// ✅ 模块级 Mock
|
||||||
@@ -654,7 +658,7 @@ export function useTimestampConverter(): UseTimestampConverterReturn { ... }
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/utils/useStorageState.ts — Chrome Storage 状态持久化
|
src/utils/useStorageState.ts — Chrome Storage 状态持久化
|
||||||
src/utils/syncSnapshot.ts — localStorage 快照读取
|
src/utils/useLazyTranslation.ts — i18n 懒加载
|
||||||
src/utils/useContextMenuData.ts — 右键菜单数据
|
src/utils/useContextMenuData.ts — 右键菜单数据
|
||||||
src/utils/useDebounce.ts — 防抖
|
src/utils/useDebounce.ts — 防抖
|
||||||
src/pages/Timestamp/useTimestampConverter.ts — 页面级 Hook
|
src/pages/Timestamp/useTimestampConverter.ts — 页面级 Hook
|
||||||
@@ -663,50 +667,47 @@ src/pages/StorageCleaner/useStorageCleaner.ts — 页面级 Hook
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. UI 文案规范
|
## 9. 国际化规范
|
||||||
|
|
||||||
项目已移除 `chrome.i18n`,所有 UI 文案直接在代码中使用中文。
|
### 9.1 翻译键格式
|
||||||
|
|
||||||
### 9.1 功能元数据
|
- 命名空间:`common`(默认)、`features`
|
||||||
|
- 翻译键格式:`namespace:key`(如 `features:timestamp.title`)
|
||||||
|
- 语言:`zh`(默认)、`en`
|
||||||
|
|
||||||
功能名称与描述在 `config/features.tsx` 的 `FEATURES` 数组中定义:
|
### 9.2 翻译文件结构
|
||||||
|
|
||||||
```typescript
|
```
|
||||||
{
|
i18n/locales/{zh,en}/common.json — 全局通用翻译
|
||||||
key: 'timestamp',
|
i18n/locales/{zh,en}/features.json — 功能模块标题和描述
|
||||||
label: '时间戳转换',
|
i18n/locales/{zh,en}/{功能名}.json — 各功能独立翻译
|
||||||
description: '日期与时间戳互转',
|
|
||||||
defaultVisible: true,
|
|
||||||
components: { popup: TimestampPage, sidepanel: TimestampPage, tab: TimestampPage },
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Dashboard 卡片、TopBar 搜索等功能从此处读取 `label` / `description`。
|
### 9.3 使用方式
|
||||||
|
|
||||||
### 9.2 页面与组件文案
|
|
||||||
|
|
||||||
- 页面标题、按钮、提示信息等直接在 JSX 或 `constants.ts` 中写中文
|
|
||||||
- 错误消息可在 Hook 中定义,或使用常量映射
|
|
||||||
- Toast 通知使用 `sonner` 的 `toast()`,文案写在调用处或常量中
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 页面组件 — 直接写中文
|
// ✅ 页面组件 — 使用 useLazyTranslation
|
||||||
|
import { useLazyTranslation } from '@/utils/useLazyTranslation';
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
return <h1 className="font-bold text-sm">时间戳转换</h1>;
|
const { t } = useLazyTranslation('timestamp');
|
||||||
|
return <h1>{t('timestamp:title')}</h1>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 常量文件 — 可复用文案
|
// ✅ 全局组件 — 使用 useTranslation
|
||||||
export const ERROR_MESSAGES = {
|
import { useTranslation } from 'react-i18next';
|
||||||
invalidInput: '输入格式无效',
|
|
||||||
conversionFailed: '转换失败',
|
export function TopBar() {
|
||||||
} as const;
|
const { t } = useTranslation(['common', 'features']);
|
||||||
|
return <span>{t('common:settings')}</span>;
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 9.3 添加新功能文案
|
### 9.4 添加新翻译
|
||||||
|
|
||||||
1. 在 `config/features.tsx` 填写 `label` 和 `description`
|
1. 在 `i18n/locales/{zh,en}/features.json` 添加功能标题和描述
|
||||||
2. 在页面组件、`constants.ts` 或 Hook 中编写 UI 文案
|
2. 创建 `i18n/locales/{zh,en}/{功能名}.json` 添加功能专属翻译
|
||||||
3. 扩展名称与描述在 `wxt.config.ts` 的 `manifest` 中维护
|
3. 在 `utils/useLazyTranslation.ts` 的 `localeModules` 中注册新命名空间
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -759,18 +760,6 @@ const [themeMode, setThemeMode, isInitialized] = useStorageState(
|
|||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### 10.4 首屏快照与初始化防覆盖
|
|
||||||
|
|
||||||
Chrome Storage 读取是异步的。项目通过 `localStorage` 快照(键名 `snapshot/{storageKey}`)提供同步初始值,消除首屏闪烁。
|
|
||||||
|
|
||||||
| 模块 | 快照工具 | 防覆盖机制 |
|
|
||||||
| ---- | -------- | ---------- |
|
|
||||||
| `RouterProvider` | `syncSnapshot.ts` | `canPersistRef`(加载成功后才写入)、`hasUserNavigatedRef`(用户导航后不被 storage 覆盖) |
|
|
||||||
| `useStorageState` | `syncSnapshot.ts` | `loadSucceededRef` 或 `userModifiedRef` 为 true 时才写入 |
|
|
||||||
| `ThemeModeProvider` | `themeSnapshot.ts` | `hasUserSetMode`(用户切换主题后不被 storage 覆盖) |
|
|
||||||
|
|
||||||
新增持久化状态时,应遵循相同模式:同步快照作初始 state → 异步加载 storage → 加载成功或用户修改后才允许写入。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. 页面开发规范
|
## 11. 页面开发规范
|
||||||
@@ -853,21 +842,24 @@ export default function DashboardPage() { ... }
|
|||||||
|
|
||||||
#### 组件职责
|
#### 组件职责
|
||||||
|
|
||||||
`index.tsx` 只负责两件事:
|
`index.tsx` 只负责三件事:
|
||||||
|
|
||||||
1. **调用业务 Hook** 获取状态和操作方法
|
1. **获取翻译函数**(`useI18n`)
|
||||||
2. **渲染 UI 布局**(纯展示,无业务逻辑)
|
2. **调用业务 Hook** 获取状态和操作方法
|
||||||
|
3. **渲染 UI 布局**(纯展示,无业务逻辑)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// ✅ 标准页面入口模板
|
// ✅ 标准页面入口模板
|
||||||
|
import { useI18n } from '@/utils/chromeI18n';
|
||||||
import { useFeatureName } from './useFeatureName';
|
import { useFeatureName } from './useFeatureName';
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
const { t } = useI18n('featureName');
|
||||||
const { state, actions } = useFeatureName();
|
const { state, actions } = useFeatureName();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||||
{/* 纯 UI 渲染,文案直接写中文 */}
|
{/* 纯 UI 渲染 */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -911,22 +903,25 @@ export function useTimestampConverter(): UseTimestampConverterReturn {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export function useFeatureName(): UseFeatureNameReturn {
|
export function useFeatureName(): UseFeatureNameReturn {
|
||||||
// 1. 基础 state(useState)
|
// 1. i18n
|
||||||
|
const { t } = useI18n('featureName');
|
||||||
|
|
||||||
|
// 2. 基础 state(useState)
|
||||||
const [mode, setMode] = useState<Mode>('default');
|
const [mode, setMode] = useState<Mode>('default');
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
|
|
||||||
// 2. 持久化 state(useStorageState)
|
// 3. 持久化 state(useStorageState)
|
||||||
const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'default', isValidMode);
|
const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'default', isValidMode);
|
||||||
|
|
||||||
// 3. 衍生数据(useMemo)— 响应式计算管线
|
// 4. 衍生数据(useMemo)— 响应式计算管线
|
||||||
const result = useMemo(() => {
|
const result = useMemo(() => {
|
||||||
// 自动计算,无需手动点击"转换"按钮
|
// 自动计算,无需手动点击"转换"按钮
|
||||||
}, [input, mode]);
|
}, [input, mode]);
|
||||||
|
|
||||||
// 4. 事件处理(useCallback)
|
// 5. 事件处理(useCallback)
|
||||||
const handleAction = useCallback(() => { ... }, [deps]);
|
const handleAction = useCallback(() => { ... }, [deps]);
|
||||||
|
|
||||||
// 5. 副作用(useEffect)— 防抖、初始化、清理
|
// 6. 副作用(useEffect)— 防抖、初始化、清理
|
||||||
useEffect(() => { ... }, [deps]);
|
useEffect(() => { ... }, [deps]);
|
||||||
|
|
||||||
// 7. 右键菜单数据(页面需要时)
|
// 7. 右键菜单数据(页面需要时)
|
||||||
@@ -996,7 +991,7 @@ interface ResultViewProps extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
// ✅ 使用 React.memo + displayName
|
// ✅ 使用 React.memo + displayName
|
||||||
const ResultView = React.memo(
|
const ResultView = React.memo(
|
||||||
({ result, mode, showEmptyPlaceholder = false, className, ...props }: ResultViewProps) => {
|
({ result, mode, showEmptyPlaceholder = false, className, ...props }: ResultViewProps) => {
|
||||||
// 文案直接写中文或使用 constants
|
const { t } = useI18n('featureName');
|
||||||
// ...
|
// ...
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -1006,7 +1001,7 @@ export default ResultView;
|
|||||||
|
|
||||||
#### 子组件内可以使用 Hook
|
#### 子组件内可以使用 Hook
|
||||||
|
|
||||||
子组件可以独立调用 `useRouter`、`useThemeMode`、`toast` 等全局 Hook/API,**不需要**通过 props 从父组件传递。
|
子组件可以独立调用 `useI18n`、`useSnackbar` 等全局 Hook,**不需要**通过 props 从父组件传递翻译函数或 toast 方法。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1043,6 +1038,7 @@ const isValidMode = (val: unknown): val is PageMode =>
|
|||||||
typeof val === 'string' && (VALID_MODES as readonly string[]).includes(val);
|
typeof val === 'string' && (VALID_MODES as readonly string[]).includes(val);
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
const { t } = useI18n('featureName');
|
||||||
const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'modeA', isValidMode);
|
const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'modeA', isValidMode);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -1050,8 +1046,8 @@ export default function Index() {
|
|||||||
<SwitchButtonGroup
|
<SwitchButtonGroup
|
||||||
value={pageMode}
|
value={pageMode}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'modeA', label: '模式 A' },
|
{ value: 'modeA', label: t('feature:modeA') },
|
||||||
{ value: 'modeB', label: '模式 B' },
|
{ value: 'modeB', label: t('feature:modeB') },
|
||||||
]}
|
]}
|
||||||
onChange={(v: PageMode) => setPageMode(v)}
|
onChange={(v: PageMode) => setPageMode(v)}
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1082,12 +1078,12 @@ export default function Index() {
|
|||||||
新增功能页面时,逐项确认:
|
新增功能页面时,逐项确认:
|
||||||
|
|
||||||
1. ✅ 在 `types/storage.d.ts` 添加 `PageType` 联合类型
|
1. ✅ 在 `types/storage.d.ts` 添加 `PageType` 联合类型
|
||||||
2. ✅ 在 `config/features.tsx` 注册 `FEATURES` 配置(key、label、description、icon、三种渲染模式组件)
|
2. ✅ 在 `config/features.tsx` 注册 `FEATURES` 配置(key、labelKey、icon、三种渲染模式组件)
|
||||||
3. ✅ 创建页面目录,使用 `Index` 作为组件名
|
3. ✅ 创建页面目录,使用 `Index` 作为组件名
|
||||||
4. ✅ 业务逻辑提取到 `useXxx.ts` Hook(index.tsx 不超过 150 行)
|
4. ✅ 业务逻辑提取到 `useXxx.ts` Hook(index.tsx 不超过 150 行)
|
||||||
5. ✅ 需要持久化的 UI 状态使用 `useStorageState`
|
5. ✅ 需要持久化的 UI 状态使用 `useStorageState`
|
||||||
6. ✅ 常量 ≥3 个时提取到 `constants.ts`
|
6. ✅ 常量 ≥3 个时提取到 `constants.ts`
|
||||||
7. ✅ 在页面组件或 `constants.ts` 中编写 UI 文案
|
7. ✅ 在 `i18n/locales/{zh,en}/` 添加翻译
|
||||||
8. ✅ 创建 `__tests__/index.test.tsx` 测试文件
|
8. ✅ 创建 `__tests__/index.test.tsx` 测试文件
|
||||||
9. ✅ 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions`
|
9. ✅ 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions`
|
||||||
10. ✅ 运行 `npm run lint && npm run typecheck && npm run test` 全部通过
|
10. ✅ 运行 `npm run lint && npm run typecheck && npm run test` 全部通过
|
||||||
@@ -1107,8 +1103,8 @@ export default function Index() {
|
|||||||
| `src/hooks/` | 自定义 React Hooks |
|
| `src/hooks/` | 自定义 React Hooks |
|
||||||
| `src/utils/` | 工具函数与服务抽象 |
|
| `src/utils/` | 工具函数与服务抽象 |
|
||||||
| `src/types/` | TypeScript 类型声明 |
|
| `src/types/` | TypeScript 类型声明 |
|
||||||
| `src/lib/` | 通用工具函数与生成器库(cn、utils、generators) |
|
| `src/lib/` | 通用工具函数(cn、utils) |
|
||||||
| `public/` | 静态资源(图标等) |
|
| `public/` | 静态资源 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
WXT 浏览器扩展项目 (React 19 + TypeScript)。提供时间戳转换、存储清理、JWT 解析、JSON 工具、二维码、Base64、测试数据生成器等测试效率工具。
|
WXT 浏览器扩展项目 (React 19 + TypeScript)。为开发者和测试人员提供浏览器内效率工具集。
|
||||||
|
|
||||||
|
## 功能列表
|
||||||
|
|
||||||
|
功能定义见 `src/config/features.tsx`,`PageType` 见 `src/types/storage.d.ts`。
|
||||||
|
|
||||||
|
| key | 页面目录 | 说明 |
|
||||||
|
| -------------------- | -------------------------- | ------------------------------------------- |
|
||||||
|
| `dashboard` | `pages/Dashboard` | 仪表盘首页,工具导航与最近使用 |
|
||||||
|
| `timestamp` | `pages/Timestamp` | Unix 时间戳转换与格式化 |
|
||||||
|
| `storageCleaner` | `pages/StorageCleaner` | 清理 Cookies、本地存储、IndexedDB、Cache 等 |
|
||||||
|
| `qrCode` | `pages/QrCode` | 二维码生成与解析 |
|
||||||
|
| `textStatistics` | `pages/TextStatistics` | 文本字符、单词、行数、字节统计 |
|
||||||
|
| `jwt` | `pages/Jwt` | JWT 解码与查看 |
|
||||||
|
| `jsonTools` | `pages/JsonTools` | JSON 差异比较、格式化、YAML/TOML 转换与压缩 |
|
||||||
|
| `base64Converter` | `pages/Base64Converter` | 文本、文件、图像 Base64 编解码 |
|
||||||
|
| `rightClickRestorer` | `pages/RightClickRestorer` | 恢复被网站禁用的右键菜单 |
|
||||||
|
| `testDataGenerator` | `pages/TestDataGenerator` | 自定义规则批量生成测试数据 |
|
||||||
|
|
||||||
## 核心命令
|
## 核心命令
|
||||||
|
|
||||||
@@ -61,7 +78,7 @@ src/ # 源代码根目录
|
|||||||
lib/ # 通用工具函数(cn、utils)及数据生成器定义
|
lib/ # 通用工具函数(cn、utils)及数据生成器定义
|
||||||
workers/ # Web Worker(数据生成等耗时任务)
|
workers/ # Web Worker(数据生成等耗时任务)
|
||||||
spec/ # 功能规格、修复方案与验收标准(见 spec/README.md)
|
spec/ # 功能规格、修复方案与验收标准(见 spec/README.md)
|
||||||
public/ # 静态资源(图标等)
|
public/ # 静态资源(图标、_locales 等)
|
||||||
.wxt/ # wxt prepare 自动生成,含类型声明与扩展 tsconfig(勿手动编辑)
|
.wxt/ # wxt prepare 自动生成,含类型声明与扩展 tsconfig(勿手动编辑)
|
||||||
.output/ # 生产构建输出目录
|
.output/ # 生产构建输出目录
|
||||||
```
|
```
|
||||||
@@ -93,38 +110,6 @@ src/pages/FeatureName/
|
|||||||
- 当 `index.tsx` 超过 150 行时,必须拆分为 UI + Hook 模式
|
- 当 `index.tsx` 超过 150 行时,必须拆分为 UI + Hook 模式
|
||||||
- 复杂页面可增加 `contexts/`、`hooks/`、`components/` 子目录
|
- 复杂页面可增加 `contexts/`、`hooks/`、`components/` 子目录
|
||||||
|
|
||||||
### 测试数据生成器模块
|
|
||||||
|
|
||||||
```
|
|
||||||
src/pages/TestDataGenerator/
|
|
||||||
├── index.tsx # 主页面(字段配置 + 标签页切换)
|
|
||||||
├── hooks/useGenerator.ts # Web Worker 管理 Hook(创建、复用、通信、销毁)
|
|
||||||
└── components/
|
|
||||||
├── FieldList.tsx # 字段列表(虚拟滚动 + @dnd-kit 拖拽排序 + 规则保存)
|
|
||||||
├── FieldItem.tsx # 字段卡片展示
|
|
||||||
├── FieldEditor.tsx # 字段编辑器(名称校验、生成器选择、参数配置)
|
|
||||||
├── GeneratorSelector.tsx # 生成器选择器(分类 + 搜索)
|
|
||||||
├── GeneratorConfig.tsx # 生成器参数表单(动态渲染 string/number/boolean/select/array)
|
|
||||||
├── GenerateOptions.tsx # 生成选项(数量、格式)
|
|
||||||
├── GenerateButton.tsx # 生成按钮 + 进度条
|
|
||||||
├── DataPreview.tsx # 示例数据预览(JSON 语法高亮)
|
|
||||||
├── ResultPanel.tsx # 生成结果状态面板
|
|
||||||
├── ExportPanel.tsx # 导出面板(复制/下载 JSON/CSV)
|
|
||||||
└── RuleManager.tsx # 规则管理(CRUD、搜索、导入/导出)
|
|
||||||
|
|
||||||
src/utils/
|
|
||||||
├── ruleStorage.ts # 规则持久化存储(localStorage)
|
|
||||||
└── dataExporter.ts # 数据导出工具(JSON/CSV 转换、下载、剪贴板)
|
|
||||||
|
|
||||||
src/lib/generators/ # 内置生成器定义(个人信息、企业、技术、基础类型)
|
|
||||||
|
|
||||||
src/workers/
|
|
||||||
└── generator.worker.ts # 数据生成 Web Worker
|
|
||||||
|
|
||||||
src/types/
|
|
||||||
└── testDataGenerator.ts # 类型定义(FieldConfig, DataRule, GeneratorDefinition 等)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 关键架构决策
|
## 关键架构决策
|
||||||
|
|
||||||
**路由**: 不使用 React Router。通过 `src/config/features.tsx` 的 `FEATURES` 数组管理,`RouterProvider` 根据 `PageType`
|
**路由**: 不使用 React Router。通过 `src/config/features.tsx` 的 `FEATURES` 数组管理,`RouterProvider` 根据 `PageType`
|
||||||
@@ -132,8 +117,7 @@ src/types/
|
|||||||
每种模式有独立的路由和可见页面配置(`app/popupRoute`、`app/sidepanelRoute`、`app/tabRoute` 等)。
|
每种模式有独立的路由和可见页面配置(`app/popupRoute`、`app/sidepanelRoute`、`app/tabRoute` 等)。
|
||||||
|
|
||||||
**存储**: 所有 Chrome Storage 键必须在 `src/types/storage.d.ts` 的 `StorageSchema` 中定义,键名使用 kebab-case 格式(如 `app/currentRoute`)。
|
**存储**: 所有 Chrome Storage 键必须在 `src/types/storage.d.ts` 的 `StorageSchema` 中定义,键名使用 kebab-case 格式(如 `app/currentRoute`)。
|
||||||
使用 `src/utils/chromeStorage.ts` 及其 Hook。Router 同时使用 `chrome.storage.local` 和 `localStorage` 快照(`snapshot/{key}`)消除首屏闪烁。
|
使用 `src/utils/chromeStorage.ts` 及其 Hook。Router 同时使用 `chrome.storage.local` 和 `localStorage` 做快照以消除首屏闪烁。
|
||||||
异步加载完成前禁止写入 storage(`RouterProvider` 的 `canPersistRef`、`useStorageState` 的 `loadSucceededRef`),避免默认值覆盖已有数据。
|
|
||||||
|
|
||||||
**通信**: 使用 `@webext-core/messaging`,协议定义在 `src/utils/messages.ts`。
|
**通信**: 使用 `@webext-core/messaging`,协议定义在 `src/utils/messages.ts`。
|
||||||
|
|
||||||
@@ -152,26 +136,6 @@ src/types/
|
|||||||
- Mock 模式: 使用 `vi.mock()` 进行模块级 mock,避免在测试文件中重复 mock 代码
|
- Mock 模式: 使用 `vi.mock()` 进行模块级 mock,避免在测试文件中重复 mock 代码
|
||||||
- 测试工具: `@testing-library/react` + `@testing-library/user-event` 进行组件测试
|
- 测试工具: `@testing-library/react` + `@testing-library/user-event` 进行组件测试
|
||||||
|
|
||||||
## UI 文案
|
|
||||||
|
|
||||||
项目已移除 `chrome.i18n`,UI 文案直接在代码中使用中文。
|
|
||||||
|
|
||||||
- **功能元数据**: `src/config/features.tsx` 的 `FEATURES` 数组定义 `label`、`description`(用于 Dashboard 卡片与搜索)
|
|
||||||
- **页面文案**: 在组件 JSX、`constants.ts` 或 Hook 中直接写中文
|
|
||||||
- **Manifest 文案**: 扩展名称与描述在 `wxt.config.ts` 的 `manifest` 中维护
|
|
||||||
- **Toast / 错误提示**: 在 Hook 或 `constants.ts` 中定义,使用 `sonner` 的 `toast()` 展示
|
|
||||||
|
|
||||||
## 新功能开发清单
|
|
||||||
|
|
||||||
1. 在 `src/types/storage.d.ts` 添加 `PageType` 联合类型
|
|
||||||
2. 在 `src/config/features.tsx` 的 `FEATURES` 数组添加配置(指定 key、label、description、图标、三种渲染模式的组件)
|
|
||||||
3. 在 `src/pages/` 创建页面组件 (懒加载):
|
|
||||||
- `index.tsx` — UI 组件(纯展示)
|
|
||||||
- `useFeatureName.ts` — 业务逻辑 Hook
|
|
||||||
- `constants.ts` — 常量定义(可选,≥3 个常量时创建)
|
|
||||||
4. 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions`
|
|
||||||
5. 添加对应的单元测试
|
|
||||||
|
|
||||||
## 代码规范
|
## 代码规范
|
||||||
|
|
||||||
- 禁止使用 `any` (测试文件除外)
|
- 禁止使用 `any` (测试文件除外)
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
- **UI 组件**: shadcn/ui (基于 Radix UI 的无头组件库)
|
- **UI 组件**: shadcn/ui (基于 Radix UI 的无头组件库)
|
||||||
- **样式**: Tailwind CSS + class-variance-authority + cn() 工具函数
|
- **样式**: Tailwind CSS + class-variance-authority + cn() 工具函数
|
||||||
- **日期处理**: dayjs (集成 UTC 和 Timezone 插件)
|
- **日期处理**: dayjs (集成 UTC 和 Timezone 插件)
|
||||||
- **UI 文案**: 组件内直接使用中文字符串;功能名称与描述定义在 `src/config/features.tsx`
|
- **国际化**: i18next + react-i18next
|
||||||
- **通信**: @webext-core/messaging
|
- **通信**: @webext-core/messaging
|
||||||
- **存储**: Chrome Storage API (类型安全封装)
|
- **存储**: Chrome Storage API (类型安全封装)
|
||||||
- **解析引擎**: qr-scanner (二维码解析), qrious (二维码生成)
|
- **解析引擎**: qr-scanner (二维码解析), qrious (二维码生成)
|
||||||
@@ -107,8 +107,8 @@
|
|||||||
│ ├── hooks/ # 自定义 React Hooks
|
│ ├── hooks/ # 自定义 React Hooks
|
||||||
│ ├── utils/ # 工具函数与服务抽象
|
│ ├── utils/ # 工具函数与服务抽象
|
||||||
│ ├── types/ # TypeScript 类型声明
|
│ ├── types/ # TypeScript 类型声明
|
||||||
│ └── lib/ # 通用工具函数与生成器库 (cn, utils, generators 等)
|
│ └── lib/ # 通用工具函数 (cn, utils 等)
|
||||||
├── public/ # 静态资源 (图标等)
|
├── public/ # 静态资源 (图标、manifest 资源等)
|
||||||
├── wxt.config.ts # WXT 框架核心配置
|
├── wxt.config.ts # WXT 框架核心配置
|
||||||
└── package.json # 项目元数据与依赖管理
|
└── package.json # 项目元数据与依赖管理
|
||||||
```
|
```
|
||||||
@@ -155,6 +155,7 @@
|
|||||||
- `sidePanel`: 支持在浏览器侧边栏中运行.
|
- `sidePanel`: 支持在浏览器侧边栏中运行.
|
||||||
- `clipboardWrite`: 提供一键复制功能.
|
- `clipboardWrite`: 提供一键复制功能.
|
||||||
- `contextMenus`: 注册右键菜单,支持快捷操作.
|
- `contextMenus`: 注册右键菜单,支持快捷操作.
|
||||||
|
- `alarms`: 管理后台定时任务(如清理后自动刷新).
|
||||||
|
|
||||||
## 浏览器支持
|
## 浏览器支持
|
||||||
|
|
||||||
|
|||||||
@@ -724,14 +724,17 @@ src/
|
|||||||
```tsx
|
```tsx
|
||||||
// src/pages/NewTool/index.tsx
|
// src/pages/NewTool/index.tsx
|
||||||
import SwitchButtonGroup from '@/components/SwitchButtonGroup';
|
import SwitchButtonGroup from '@/components/SwitchButtonGroup';
|
||||||
|
import { useI18n } from '@/utils/chromeI18n';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
|
const { t } = useI18n('newTool');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
<div className="p-4 w-full flex flex-col space-y-4 min-h-[500px] select-none">
|
||||||
{/* 页面内容 */}
|
{/* 页面内容 */}
|
||||||
<div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm">
|
<div className="p-5 rounded-xl border border-border bg-card text-card-foreground shadow-sm">
|
||||||
<h4 className="font-bold text-sm tracking-tight">新工具</h4>
|
<h4 className="font-bold text-sm tracking-tight">{t('newTool:title')}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
> 版本: v1.0
|
> 版本: v1.0
|
||||||
> 创建时间: 2024-01-20
|
> 创建时间: 2024-01-20
|
||||||
> 状态: 已实现(见 `src/pages/TestDataGenerator/`、`src/lib/generators/`、`src/workers/generator.worker.ts`)
|
> 状态: 待 Review
|
||||||
|
|
||||||
## 产品定位
|
## 产品定位
|
||||||
|
|
||||||
@@ -19,13 +19,3 @@
|
|||||||
- [规则管理](./rule-management.md)
|
- [规则管理](./rule-management.md)
|
||||||
- [界面设计](./ui-design.md)
|
- [界面设计](./ui-design.md)
|
||||||
- [技术实现](./technical-implementation.md)
|
- [技术实现](./technical-implementation.md)
|
||||||
|
|
||||||
## 当前实现入口
|
|
||||||
|
|
||||||
- 页面入口:`src/pages/TestDataGenerator/index.tsx`
|
|
||||||
- Worker:`src/workers/generator.worker.ts`
|
|
||||||
- 生成器库:`src/lib/generators/`
|
|
||||||
- 规则存储:`src/utils/ruleStorage.ts`
|
|
||||||
- 导出工具:`src/utils/dataExporter.ts`
|
|
||||||
|
|
||||||
实现约束与任务完成状态记录在 [TASKS.md](./TASKS.md)。
|
|
||||||
|
|||||||
@@ -17,4 +17,3 @@
|
|||||||
|
|
||||||
- 修改图标后需同步更新 `wxt.config.ts` 中的 manifest 配置
|
- 修改图标后需同步更新 `wxt.config.ts` 中的 manifest 配置
|
||||||
- 图标格式推荐使用 PNG,确保透明背景
|
- 图标格式推荐使用 PNG,确保透明背景
|
||||||
- UI 文案不在此目录维护,见 `src/config/features.tsx` 与各页面组件
|
|
||||||
|
|||||||
+2
-32
@@ -4,10 +4,9 @@
|
|||||||
|
|
||||||
## 文件说明
|
## 文件说明
|
||||||
|
|
||||||
| 文件/目录 | 用途 |
|
| 文件 | 用途 |
|
||||||
| ------------- | -------------------------------------- |
|
| ---------- | ------------------------------------ |
|
||||||
| `utils.ts` | `cn()` 函数 — shadcn/ui 标准工具函数 |
|
| `utils.ts` | `cn()` 函数 — shadcn/ui 标准工具函数 |
|
||||||
| `generators/` | 测试数据生成器内置生成器库和分类注册表 |
|
|
||||||
|
|
||||||
## cn()
|
## cn()
|
||||||
|
|
||||||
@@ -25,32 +24,3 @@ import { cn } from '@/lib/utils';
|
|||||||
```
|
```
|
||||||
|
|
||||||
所有需要动态合并 Tailwind 类名的场景都应使用 `cn()`,而非手动拼接字符串。
|
所有需要动态合并 Tailwind 类名的场景都应使用 `cn()`,而非手动拼接字符串。
|
||||||
|
|
||||||
## generators/
|
|
||||||
|
|
||||||
测试数据生成器的核心生成器库,供 `src/workers/generator.worker.ts` 在后台线程中按字段配置生成数据。
|
|
||||||
|
|
||||||
| 文件 | 用途 |
|
|
||||||
| -------------- | ---------------------------------------------- |
|
|
||||||
| `index.ts` | 聚合所有生成器,导出分类、查找和搜索函数 |
|
|
||||||
| `personal.ts` | 个人信息生成器(姓名、邮箱、手机号、身份证等) |
|
|
||||||
| `business.ts` | 业务数据生成器(订单号、价格、日期、状态等) |
|
|
||||||
| `technical.ts` | 技术数据生成器(UUID、IPv4、URL) |
|
|
||||||
| `basic.ts` | 基础类型生成器(整数、浮点数、布尔值、字符串) |
|
|
||||||
| `types.ts` | 生成器库内部类型与通用选项 |
|
|
||||||
|
|
||||||
使用示例:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { getGeneratorById, searchGenerators } from '@/lib/generators';
|
|
||||||
|
|
||||||
const generator = getGeneratorById('chineseName');
|
|
||||||
const value = generator?.generate({});
|
|
||||||
const matched = searchGenerators('uuid');
|
|
||||||
```
|
|
||||||
|
|
||||||
约束:
|
|
||||||
|
|
||||||
- 新增生成器必须实现 `GeneratorDefinition`,并加入所属分类文件的导出列表。
|
|
||||||
- 需要唯一值或大批量稳定输出时,优先实现 `generateAtIndex(params, index)`。
|
|
||||||
- 新增分类时同步更新 `generatorCategories`,并确保 `GeneratorSelector` 能展示该分类。
|
|
||||||
|
|||||||
+5
-15
@@ -28,46 +28,36 @@ React.StrictMode
|
|||||||
- **当前页面**:`currentPage`(`PageType`)
|
- **当前页面**:`currentPage`(`PageType`)
|
||||||
- **可见页面列表**:`visiblePages`
|
- **可见页面列表**:`visiblePages`
|
||||||
- **页面排序**:`pageOrder`
|
- **页面排序**:`pageOrder`
|
||||||
- **最近使用工具**:`recentlyUsedTools`(最多 3 项,供 TopBar 搜索历史使用)
|
|
||||||
- **加载状态**:`isLoaded`
|
- **加载状态**:`isLoaded`
|
||||||
|
|
||||||
核心特性:
|
核心特性:
|
||||||
|
|
||||||
- 通过 `chrome.storage` 持久化路由状态
|
- 通过 `chrome.storage` 持久化路由状态
|
||||||
- 使用 `localStorage` 快照(`snapshot/{key}`)实现首屏 0 闪烁
|
- 使用 `localStorage` 快照实现首屏 0 闪烁
|
||||||
- 支持 popup/sidepanel/tab 三种入口的独立路由同步(通过 `syncKey`、`visiblePagesKey`、`pageOrderKey` 配置)
|
- 支持 popup/sidepanel/tab 三种入口的独立路由同步(通过 `syncKey`、`visiblePagesKey`、`pageOrderKey` 配置)
|
||||||
- 处理右键菜单待处理数据的路由跳转
|
- 处理右键菜单待处理数据的路由跳转
|
||||||
- 监听 `chrome.storage.onChanged` 实现跨端同步
|
- 监听 `chrome.storage.onChanged` 实现跨端同步
|
||||||
|
|
||||||
### 初始化与防覆盖
|
|
||||||
|
|
||||||
异步加载 storage 期间,快照值会作为首屏初始 state。加载完成后:
|
|
||||||
|
|
||||||
1. **`canPersistRef`**:仅在 `loadInitialData` 成功后才设为 `true`,在此之前不会向 storage 写入,避免默认值覆盖已有路由
|
|
||||||
2. **`hasUserNavigatedRef`**:用户调用 `navigateTo` / `goHome` 后设为 `true`,异步加载结果不会覆盖用户已选页面
|
|
||||||
3. **`mergeWithDefaults`**:将已保存的页面列表与默认列表合并,新增功能会自动出现在列表末尾
|
|
||||||
|
|
||||||
导出:
|
导出:
|
||||||
|
|
||||||
- `RouterProvider` 组件
|
- `RouterProvider` 组件
|
||||||
- `useRouter()` Hook — 获取 `currentPage`、`visiblePages`、`pageOrder`、`recentlyUsedTools`、`navigateTo`、`goHome` 等
|
- `useRouter()` Hook — 获取 `currentPage`、`visiblePages`、`pageOrder`、`navigateTo`、`goHome` 等
|
||||||
|
|
||||||
## ThemeModeProvider.tsx
|
## ThemeModeProvider.tsx
|
||||||
|
|
||||||
主题模式 Provider,管理:
|
主题模式 Provider,管理:
|
||||||
|
|
||||||
- **主题模式**:`light` / `dark` / `system`
|
- **主题模式**:`light` / `dark` / `system`
|
||||||
- **解析后的主题**:`resolvedMode`(`light` / `dark`)
|
- **解析后的主题**:`resolvedTheme`(`light` / `dark`)
|
||||||
|
|
||||||
核心特性:
|
核心特性:
|
||||||
|
|
||||||
- 使用 `themeSnapshot.ts` 读写 `localStorage` 快照,实现首屏 0 闪烁
|
- 使用 `localStorage` 快照实现首屏 0 闪烁
|
||||||
- 监听系统级暗色模式变化(`matchMedia`)
|
- 监听系统级暗色模式变化(`matchMedia`)
|
||||||
- 通过 `chrome.storage` 跨端同步主题偏好
|
- 通过 `chrome.storage` 跨端同步主题偏好
|
||||||
- 自动在 `document.documentElement` 上切换 `dark` class
|
- 自动在 `document.documentElement` 上切换 `dark` class
|
||||||
- **`hasUserSetMode`**:用户主动切换主题后,异步 storage 加载不会覆盖用户选择
|
|
||||||
|
|
||||||
导出:
|
导出:
|
||||||
|
|
||||||
- `ThemeModeProvider` 组件
|
- `ThemeModeProvider` 组件
|
||||||
- `useThemeMode()` Hook — 获取 `mode`、`resolvedMode`、`setMode`
|
- `useThemeMode()` Hook — 获取 `themeMode`、`resolvedTheme`、`setThemeMode`
|
||||||
|
|||||||
+3
-11
@@ -10,10 +10,12 @@
|
|||||||
|
|
||||||
**页面类型:**
|
**页面类型:**
|
||||||
|
|
||||||
- `PageType` — 所有页面类型的联合类型(含 `dashboard`、`timestamp`、`storageCleaner`、`testDataGenerator` 等)
|
- `PageType` — 所有页面类型的联合类型(`dashboard` | `timestamp` | `storageCleaner` | ...)
|
||||||
- `JsonToolsPageMode` — JSON 工具子模式(`diff` | `format` | `yaml` | `toml` | `minify`)
|
- `JsonToolsPageMode` — JSON 工具子模式(`diff` | `format` | `yaml` | `toml` | `minify`)
|
||||||
- `Base64ConverterPageMode` — Base64 子模式(`text` | `file` | `image`)
|
- `Base64ConverterPageMode` — Base64 子模式(`text` | `file` | `image`)
|
||||||
- `Base64ConvertDirection` — 编解码方向(`encode` | `decode`)
|
- `Base64ConvertDirection` — 编解码方向(`encode` | `decode`)
|
||||||
|
- `MarkdownToHtmlPreviewMode` — Markdown 预览模式(`split` | `preview` | `html`)
|
||||||
|
- `HtmlToMarkdownPreviewMode` — HTML 预览模式(`split` | `preview` | `markdown`)
|
||||||
|
|
||||||
**存储 Schema:**
|
**存储 Schema:**
|
||||||
|
|
||||||
@@ -32,16 +34,6 @@
|
|||||||
|
|
||||||
`qrious` 库的类型声明,定义 QR 码生成选项和 `QRious` 类。
|
`qrious` 库的类型声明,定义 QR 码生成选项和 `QRious` 类。
|
||||||
|
|
||||||
### testDataGenerator.ts
|
|
||||||
|
|
||||||
测试数据生成器共享类型,包含:
|
|
||||||
|
|
||||||
- `FieldConfig` — 字段配置(字段名、生成器、参数、必填、空值率、唯一性)
|
|
||||||
- `DataRule` — 可保存/导入/导出的字段规则
|
|
||||||
- `GeneratorDefinition` / `GeneratorParam` — 内置生成器定义和参数 Schema
|
|
||||||
- `GenerateResult` / `GenerateProgress` / `WorkerMessage` — Worker 生成结果、进度和消息协议
|
|
||||||
- `ExportFile` — JSON/CSV 导出文件描述
|
|
||||||
|
|
||||||
## 修改 StorageSchema 的注意事项
|
## 修改 StorageSchema 的注意事项
|
||||||
|
|
||||||
修改 `StorageSchema` 时,必须:
|
修改 `StorageSchema` 时,必须:
|
||||||
|
|||||||
+6
-19
@@ -5,11 +5,8 @@
|
|||||||
## 工具函数
|
## 工具函数
|
||||||
|
|
||||||
| 文件 | 用途 |
|
| 文件 | 用途 |
|
||||||
| ------------------------ | --------------------------------------------------------------------------------------------------- |
|
| -------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
| `chromeStorage.ts` | Chrome Storage API 封装:类型安全的 `StorageUtils` 类,提供 `get/set/remove` 方法 |
|
| `chromeStorage.ts` | Chrome Storage API 封装:类型安全的 `StorageUtils` 类,提供 `get/set/remove` 方法 |
|
||||||
| `syncSnapshot.ts` | 通用 `localStorage` 快照读取(`snapshot/{key}`),用于 Router 与 `useStorageState` 首屏防闪烁 |
|
|
||||||
| `themeSnapshot.ts` | 主题专用快照读写与 `document.documentElement` class 切换 |
|
|
||||||
| `restrictedUrls.ts` | 受限 URL 检测(`chrome://`、`about:` 等),供 Storage Cleaner 等模块复用 |
|
|
||||||
| `chromeTabs.ts` | Chrome Tabs API 封装:获取活动标签页、获取域名、在新标签页打开扩展页面 |
|
| `chromeTabs.ts` | Chrome Tabs API 封装:获取活动标签页、获取域名、在新标签页打开扩展页面 |
|
||||||
| `clipboard.ts` | 剪贴板操作:`copyTextToClipboard`(文本)、`copyImageToClipboard`(图片) |
|
| `clipboard.ts` | 剪贴板操作:`copyTextToClipboard`(文本)、`copyImageToClipboard`(图片) |
|
||||||
| `messages.ts` | 扩展消息通信:基于 `@webext-core/messaging`,定义 `MessageAction` 枚举和 `ProtocolMap` 类型安全映射 |
|
| `messages.ts` | 扩展消息通信:基于 `@webext-core/messaging`,定义 `MessageAction` 枚举和 `ProtocolMap` 类型安全映射 |
|
||||||
@@ -19,37 +16,27 @@
|
|||||||
| `jsonFormatter.ts` | JSON 格式化/压缩:支持缩进、按键排序、minify |
|
| `jsonFormatter.ts` | JSON 格式化/压缩:支持缩进、按键排序、minify |
|
||||||
| `jsonToYaml.ts` | JSON→YAML 转换 |
|
| `jsonToYaml.ts` | JSON→YAML 转换 |
|
||||||
| `jsonToToml.ts` | JSON→TOML 转换 |
|
| `jsonToToml.ts` | JSON→TOML 转换 |
|
||||||
|
| `markdownToHtml.ts` | Markdown→HTML 转换:基于 `marked` 库,支持 GFM 和换行转换 |
|
||||||
|
| `htmlToMarkdown.ts` | HTML→Markdown 转换:基于 DOMParser 解析 |
|
||||||
| `qrCodeParser.ts` | 二维码解析:基于 `qr-scanner` 库从文件中解析二维码 |
|
| `qrCodeParser.ts` | 二维码解析:基于 `qr-scanner` 库从文件中解析二维码 |
|
||||||
| `storageCleaner.ts` | 存储清理:获取当前标签页、计算 Cookie/Storage 大小、清理操作 |
|
| `storageCleaner.ts` | 存储清理:获取当前标签页、检测受限 URL、计算 Cookie/Storage 大小、清理操作 |
|
||||||
| `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、搜索、导入/导出和数量限制 |
|
|
||||||
| `dataExporter.ts` | 测试数据导出:JSON/CSV 转换、文件下载和复制到剪贴板 |
|
|
||||||
| `rightClickInjection.ts` | 右键恢复注入脚本:在页面上下文恢复 contextmenu/copy/paste 等事件默认行为 |
|
|
||||||
|
|
||||||
## 自定义 Hooks
|
## 自定义 Hooks
|
||||||
|
|
||||||
| 文件 | 用途 |
|
| 文件 | 用途 |
|
||||||
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
| ----------------------- | -------------------------------------------------------------------------------------------------------------- |
|
||||||
| `useStorageState.ts` | Chrome Storage 状态 Hook:类似 `useState`,值自动同步到 `chrome.storage`,使用 `localStorage` 快照消除首屏闪烁 |
|
| `useStorageState.ts` | Chrome Storage 状态 Hook:类似 `useState`,值自动同步到 `chrome.storage`,使用 `localStorage` 快照消除首屏闪烁 |
|
||||||
|
| `useLazyTranslation.ts` | 懒加载翻译 Hook:按需动态导入 i18n 命名空间,支持预加载和缓存 |
|
||||||
| `useContextMenuData.ts` | 右键菜单数据 Hook:从 storage 读取待处理数据,匹配 featureKey 后消费并触发回调 |
|
| `useContextMenuData.ts` | 右键菜单数据 Hook:从 storage 读取待处理数据,匹配 featureKey 后消费并触发回调 |
|
||||||
| `useDebounce.ts` | 防抖 Hook:对值进行延迟更新,避免频繁触发 |
|
| `useDebounce.ts` | 防抖 Hook:对值进行延迟更新,避免频繁触发 |
|
||||||
|
|
||||||
### useStorageState 初始化防覆盖
|
|
||||||
|
|
||||||
`useStorageState` 在挂载时从 storage 异步加载。写入 storage 需满足以下任一条件:
|
|
||||||
|
|
||||||
- **`loadSucceededRef`**:storage 读取成功
|
|
||||||
- **`userModifiedRef`**:用户通过 setter 主动修改过值
|
|
||||||
|
|
||||||
若 storage 读取失败且用户未修改,不会将默认值写回 storage,避免静默覆盖已有数据。
|
|
||||||
|
|
||||||
## 使用约定
|
## 使用约定
|
||||||
|
|
||||||
- 工具函数使用**命名导出**(`export function xxx()`)
|
- 工具函数使用**命名导出**(`export function xxx()`)
|
||||||
- 工具层不直接展示 Toast;可恢复错误返回可判断结果,需要抛出的解析/转换错误由页面 Hook 或 UI 层捕获
|
- 工具函数**不抛异常**,返回包含 `hasError` 和 `error` 字段的结果对象
|
||||||
- Hook 使用 `use` 前缀命名,定义返回值接口类型
|
- Hook 使用 `use` 前缀命名,定义返回值接口类型
|
||||||
- 存储操作使用 `chromeStorage.ts` 的 `storageUtil` 封装,不要直接调用 `chrome.storage`
|
- 存储操作使用 `chromeStorage.ts` 的 `storageUtil` 封装,不要直接调用 `chrome.storage`
|
||||||
- 消息通信使用 `messages.ts` 的 `sendMessage`/`onMessage`,不要使用原生 `chrome.runtime.sendMessage`
|
- 消息通信使用 `messages.ts` 的 `sendMessage`/`onMessage`,不要使用原生 `chrome.runtime.sendMessage`
|
||||||
- 需要首屏快照的新持久化状态,优先复用 `syncSnapshot.ts` 或参考 `themeSnapshot.ts` 模式
|
|
||||||
|
|||||||
Reference in New Issue
Block a user