diff --git a/.github/CODING_STANDARDS.md b/.github/CODING_STANDARDS.md index 3035c0f..b19d0a5 100644 --- a/.github/CODING_STANDARDS.md +++ b/.github/CODING_STANDARDS.md @@ -265,12 +265,11 @@ const visibleSet = useMemo(() => new Set(visiblePages), [visiblePages]); 2. 第三方库(图标、UI 库等) 3. 业务 Provider / Context 4. 配置 / 存储 -5. i18n -6. 本地页面组件 -7. UI 组件 -8. 工具函数 / Hook -9. 类型 -10. 常量 +5. 本地页面组件 +6. UI 组件 +7. 工具函数 / Hook +8. 类型 +9. 常量 ```typescript // 1. React 核心 @@ -284,18 +283,16 @@ import { useThemeMode } from '@/providers/ThemeModeProvider'; // 4. 配置 / 存储 import { FeatureConfig, FEATURES } from '@/config/features'; import { storageUtil } from '@/utils/chromeStorage'; -// 5. i18n -import { useI18n } from '@/utils/chromeI18n'; -// 6. 本地组件 +// 5. 本地组件 import TextMode from './TextMode'; import { ZONES } from './constants'; -// 7. UI 组件 +// 6. UI 组件 import SwitchButtonGroup from '@/components/SwitchButtonGroup'; import { Button } from '@/components/ui/button'; -// 8. 工具函数 / Hook +// 7. 工具函数 / Hook import { cn } from '@/lib/utils'; import { useStorageState } from '@/utils/useStorageState'; -// 9. 类型 +// 8. 类型 import type { PageType, StorageSchema } from '@/types/storage'; ``` @@ -559,7 +556,7 @@ describe('SwitchButtonGroup 组件', () => { - 使用 `vi.mock()` 进行模块级 Mock - 使用 `vi.fn()` 进行函数级 Mock - 使用 `vi.useFakeTimers()` 控制时间 -- **避免重复 mock `vitest.setup.ts` 中已有的内容**(chrome API、i18n、matchMedia 等) +- **避免重复 mock `vitest.setup.ts` 中已有的内容**(chrome API、matchMedia 等) ```typescript // ✅ 模块级 Mock @@ -657,7 +654,7 @@ export function useTimestampConverter(): UseTimestampConverterReturn { ... } ``` src/utils/useStorageState.ts — Chrome Storage 状态持久化 -src/utils/chromeI18n.ts — chrome.i18n wrapper 与 useI18n +src/utils/syncSnapshot.ts — localStorage 快照读取 src/utils/useContextMenuData.ts — 右键菜单数据 src/utils/useDebounce.ts — 防抖 src/pages/Timestamp/useTimestampConverter.ts — 页面级 Hook @@ -666,52 +663,50 @@ src/pages/StorageCleaner/useStorageCleaner.ts — 页面级 Hook --- -## 9. 国际化规范 +## 9. UI 文案规范 -### 9.1 翻译键格式 +项目已移除 `chrome.i18n`,所有 UI 文案直接在代码中使用中文。 -- 使用 Chrome 扩展标准的 `chrome.i18n`,通过 `src/utils/chromeI18n.ts` 暴露 `useI18n` -- 翻译 key 存放在 `public/_locales/zh_CN/messages.json` -- 直接 key:`t('dashboard_title')` → 查找 `dashboard_title` -- 命名空间兼容写法:`t('common:buttons.search')` → 查找 `common_buttons_search` -- 命名空间参数:`useI18n(['common', 'features'])` 会尝试 `common_key`、`features_key` +### 9.1 功能元数据 -### 9.2 翻译文件结构 - -``` -public/_locales/zh_CN/messages.json — Chrome 扩展默认语言包 -wxt.config.ts — manifest.default_locale = 'zh_CN' -``` - -### 9.3 使用方式 +功能名称与描述在 `config/features.tsx` 的 `FEATURES` 数组中定义: ```typescript -// ✅ 页面组件 / 子组件 — 使用 useI18n -import { useI18n } from '@/utils/chromeI18n'; - -export default function Index() { - const { t } = useI18n('timestamp'); - return

{t('timestamp_title')}

; -} - -// ✅ 带占位符 -export function NotFoundMessage() { - const { t } = useI18n('router'); - return

{t('router_notFoundDescription', { entryPointType: 'popup' })}

; +{ + key: 'timestamp', + label: '时间戳转换', + description: '日期与时间戳互转', + defaultVisible: true, + components: { popup: TimestampPage, sidepanel: TimestampPage, tab: TimestampPage }, } ``` -### 9.4 添加新翻译 +Dashboard 卡片、TopBar 搜索等功能从此处读取 `label` / `description`。 -1. 在 `public/_locales/zh_CN/messages.json` 添加 Chrome 扩展格式的消息: - ```json - { - "feature_title": { "message": "功能标题" }, - "feature_description": { "message": "功能描述" } - } - ``` -2. key 使用下划线分隔,避免点号;`useI18n` 会把 `namespace:key.path` 兼容转换为下划线 -3. `chrome.i18n` 不支持运行时动态切换语言,浏览器语言变化后需要刷新扩展页面 +### 9.2 页面与组件文案 + +- 页面标题、按钮、提示信息等直接在 JSX 或 `constants.ts` 中写中文 +- 错误消息可在 Hook 中定义,或使用常量映射 +- Toast 通知使用 `sonner` 的 `toast()`,文案写在调用处或常量中 + +```typescript +// ✅ 页面组件 — 直接写中文 +export default function Index() { + return

时间戳转换

; +} + +// ✅ 常量文件 — 可复用文案 +export const ERROR_MESSAGES = { + invalidInput: '输入格式无效', + conversionFailed: '转换失败', +} as const; +``` + +### 9.3 添加新功能文案 + +1. 在 `config/features.tsx` 填写 `label` 和 `description` +2. 在页面组件、`constants.ts` 或 Hook 中编写 UI 文案 +3. 扩展名称与描述在 `wxt.config.ts` 的 `manifest` 中维护 --- @@ -764,6 +759,18 @@ 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. 页面开发规范 @@ -846,24 +853,21 @@ export default function DashboardPage() { ... } #### 组件职责 -`index.tsx` 只负责三件事: +`index.tsx` 只负责两件事: -1. **获取翻译函数**(`useI18n`) -2. **调用业务 Hook** 获取状态和操作方法 -3. **渲染 UI 布局**(纯展示,无业务逻辑) +1. **调用业务 Hook** 获取状态和操作方法 +2. **渲染 UI 布局**(纯展示,无业务逻辑) ```typescript // ✅ 标准页面入口模板 -import { useI18n } from '@/utils/chromeI18n'; import { useFeatureName } from './useFeatureName'; export default function Index() { - const { t } = useI18n('featureName'); const { state, actions } = useFeatureName(); return (
- {/* 纯 UI 渲染 */} + {/* 纯 UI 渲染,文案直接写中文 */}
); } @@ -907,25 +911,22 @@ export function useTimestampConverter(): UseTimestampConverterReturn { ```typescript export function useFeatureName(): UseFeatureNameReturn { - // 1. i18n - const { t } = useI18n('featureName'); - - // 2. 基础 state(useState) + // 1. 基础 state(useState) const [mode, setMode] = useState('default'); const [input, setInput] = useState(''); - // 3. 持久化 state(useStorageState) + // 2. 持久化 state(useStorageState) const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'default', isValidMode); - // 4. 衍生数据(useMemo)— 响应式计算管线 + // 3. 衍生数据(useMemo)— 响应式计算管线 const result = useMemo(() => { // 自动计算,无需手动点击"转换"按钮 }, [input, mode]); - // 5. 事件处理(useCallback) + // 4. 事件处理(useCallback) const handleAction = useCallback(() => { ... }, [deps]); - // 6. 副作用(useEffect)— 防抖、初始化、清理 + // 5. 副作用(useEffect)— 防抖、初始化、清理 useEffect(() => { ... }, [deps]); // 7. 右键菜单数据(页面需要时) @@ -995,7 +996,7 @@ interface ResultViewProps extends React.HTMLAttributes { // ✅ 使用 React.memo + displayName const ResultView = React.memo( ({ result, mode, showEmptyPlaceholder = false, className, ...props }: ResultViewProps) => { - const { t } = useI18n('featureName'); + // 文案直接写中文或使用 constants // ... }, ); @@ -1005,7 +1006,7 @@ export default ResultView; #### 子组件内可以使用 Hook -子组件可以独立调用 `useI18n`、`useSnackbar` 等全局 Hook,**不需要**通过 props 从父组件传递翻译函数或 toast 方法。 +子组件可以独立调用 `useRouter`、`useThemeMode`、`toast` 等全局 Hook/API,**不需要**通过 props 从父组件传递。 --- @@ -1042,7 +1043,6 @@ const isValidMode = (val: unknown): val is PageMode => typeof val === 'string' && (VALID_MODES as readonly string[]).includes(val); export default function Index() { - const { t } = useI18n('featureName'); const [pageMode, setPageMode] = useStorageState('feature/pageMode', 'modeA', isValidMode); return ( @@ -1050,8 +1050,8 @@ export default function Index() { setPageMode(v)} size="small" @@ -1082,12 +1082,12 @@ export default function Index() { 新增功能页面时,逐项确认: 1. ✅ 在 `types/storage.d.ts` 添加 `PageType` 联合类型 -2. ✅ 在 `config/features.tsx` 注册 `FEATURES` 配置(key、labelKey、icon、三种渲染模式组件) +2. ✅ 在 `config/features.tsx` 注册 `FEATURES` 配置(key、label、description、icon、三种渲染模式组件) 3. ✅ 创建页面目录,使用 `Index` 作为组件名 4. ✅ 业务逻辑提取到 `useXxx.ts` Hook(index.tsx 不超过 150 行) 5. ✅ 需要持久化的 UI 状态使用 `useStorageState` 6. ✅ 常量 ≥3 个时提取到 `constants.ts` -7. ✅ 在 `public/_locales/zh_CN/messages.json` 添加翻译 +7. ✅ 在页面组件或 `constants.ts` 中编写 UI 文案 8. ✅ 创建 `__tests__/index.test.tsx` 测试文件 9. ✅ 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions` 10. ✅ 运行 `npm run lint && npm run typecheck && npm run test` 全部通过 @@ -1108,7 +1108,7 @@ export default function Index() { | `src/utils/` | 工具函数与服务抽象 | | `src/types/` | TypeScript 类型声明 | | `src/lib/` | 通用工具函数与生成器库(cn、utils、generators) | -| `public/` | 静态资源与 Chrome `_locales` 语言包 | +| `public/` | 静态资源(图标等) | --- diff --git a/AGENTS.md b/AGENTS.md index cbbae37..6af87e1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -61,7 +61,7 @@ src/ # 源代码根目录 lib/ # 通用工具函数(cn、utils)及数据生成器定义 workers/ # Web Worker(数据生成等耗时任务) spec/ # 功能规格、修复方案与验收标准(见 spec/README.md) -public/ # 静态资源(图标、_locales 等) +public/ # 静态资源(图标等) .wxt/ # wxt prepare 自动生成,含类型声明与扩展 tsconfig(勿手动编辑) .output/ # 生产构建输出目录 ``` @@ -132,7 +132,8 @@ src/types/ 每种模式有独立的路由和可见页面配置(`app/popupRoute`、`app/sidepanelRoute`、`app/tabRoute` 等)。 **存储**: 所有 Chrome Storage 键必须在 `src/types/storage.d.ts` 的 `StorageSchema` 中定义,键名使用 kebab-case 格式(如 `app/currentRoute`)。 -使用 `src/utils/chromeStorage.ts` 及其 Hook。Router 同时使用 `chrome.storage.local` 和 `localStorage` 做快照以消除首屏闪烁。 +使用 `src/utils/chromeStorage.ts` 及其 Hook。Router 同时使用 `chrome.storage.local` 和 `localStorage` 快照(`snapshot/{key}`)消除首屏闪烁。 +异步加载完成前禁止写入 storage(`RouterProvider` 的 `canPersistRef`、`useStorageState` 的 `loadSucceededRef`),避免默认值覆盖已有数据。 **通信**: 使用 `@webext-core/messaging`,协议定义在 `src/utils/messages.ts`。 @@ -146,39 +147,30 @@ src/types/ - 全局变量: `vitest/globals` (describe, it, expect 等无需导入) - Setup 文件: `vitest.setup.ts` 自动 mock: - `chrome.*` / `browser.*` API (storage, tabs, runtime, cookies 等) - - `@/utils/chromeI18n` (从 `public/_locales/zh_CN/messages.json` 加载真实翻译) - `window.matchMedia` - 测试文件命名: `__tests__/*.test.{ts,tsx}` 或 `*.test.{ts,tsx}` - Mock 模式: 使用 `vi.mock()` 进行模块级 mock,避免在测试文件中重复 mock 代码 - 测试工具: `@testing-library/react` + `@testing-library/user-event` 进行组件测试 -## i18n (chrome.i18n) +## UI 文案 -项目使用 Chrome 扩展标准的 `chrome.i18n` API 进行本地化,通过 `src/utils/chromeI18n.ts` 提供类型安全的 React Hook 包装。 +项目已移除 `chrome.i18n`,UI 文案直接在代码中使用中文。 -- **翻译文件**: `public/_locales/zh_CN/messages.json`(Chrome 扩展标准格式) -- **默认语言**: `zh_CN`(在 `wxt.config.ts` 的 `manifest.default_locale` 中配置) -- **使用方式**: `import { useI18n } from '@/utils/chromeI18n'` -- **翻译键格式**: - - 直接 key: `t('dashboard_title')` → 查找 `dashboard_title` - - 命名空间格式(兼容旧用法): `t('common:buttons.search')` → 查找 `common_buttons_search` - - 带命名空间参数: `useI18n(['common', 'features'])`,会自动尝试 `common_key`、`features_key` -- **占位符支持**: `t('router_notFoundDescription', { entryPointType: 'popup' })` -- **Hook 返回值**: `{ t, i18n: { language, changeLanguage }, isLoaded }` -- **回退策略**: 当翻译 key 未命中时,返回 key 本身(开发模式下在控制台记录 warning) -- **限制**: `chrome.i18n` 无法动态切换语言,语言跟随浏览器设置,切换后需刷新页面 +- **功能元数据**: `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、翻译键、图标、三种渲染模式的组件) +2. 在 `src/config/features.tsx` 的 `FEATURES` 数组添加配置(指定 key、label、description、图标、三种渲染模式的组件) 3. 在 `src/pages/` 创建页面组件 (懒加载): - - `index.tsx` — UI 组件,使用 `useI18n` 获取翻译 + - `index.tsx` — UI 组件(纯展示) - `useFeatureName.ts` — 业务逻辑 Hook - - `constants.ts` — 常量(可选) -4. 在 `public/_locales/zh_CN/messages.json` 添加翻译 -5. 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions` -6. 添加对应的单元测试 + - `constants.ts` — 常量定义(可选,≥3 个常量时创建) +4. 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions` +5. 添加对应的单元测试 ## 代码规范 diff --git a/README.md b/README.md index 43f7c39..8df269e 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ - **UI 组件**: shadcn/ui (基于 Radix UI 的无头组件库) - **样式**: Tailwind CSS + class-variance-authority + cn() 工具函数 - **日期处理**: dayjs (集成 UTC 和 Timezone 插件) -- **国际化**: Chrome `chrome.i18n` + `public/_locales/zh_CN/messages.json` +- **UI 文案**: 组件内直接使用中文字符串;功能名称与描述定义在 `src/config/features.tsx` - **通信**: @webext-core/messaging - **存储**: Chrome Storage API (类型安全封装) - **解析引擎**: qr-scanner (二维码解析), qrious (二维码生成) @@ -108,7 +108,7 @@ │ ├── utils/ # 工具函数与服务抽象 │ ├── types/ # TypeScript 类型声明 │ └── lib/ # 通用工具函数与生成器库 (cn, utils, generators 等) -├── public/ # 静态资源 (图标、_locales 本地化资源等) +├── public/ # 静态资源 (图标等) ├── wxt.config.ts # WXT 框架核心配置 └── package.json # 项目元数据与依赖管理 ``` diff --git a/docs/VISUAL_STYLE_GUIDE.md b/docs/VISUAL_STYLE_GUIDE.md index ceb8169..dc59cdd 100644 --- a/docs/VISUAL_STYLE_GUIDE.md +++ b/docs/VISUAL_STYLE_GUIDE.md @@ -724,17 +724,14 @@ src/ ```tsx // src/pages/NewTool/index.tsx import SwitchButtonGroup from '@/components/SwitchButtonGroup'; -import { useI18n } from '@/utils/chromeI18n'; import { cn } from '@/lib/utils'; export default function Index() { - const { t } = useI18n('newTool'); - return ( -
+
{/* 页面内容 */}
-

{t('newTool:title')}

+

新工具

); diff --git a/public/README.md b/public/README.md index 9be021a..e535947 100644 --- a/public/README.md +++ b/public/README.md @@ -12,24 +12,9 @@ | `icon/48.png` | 48×48 图标(扩展管理页) | | `icon/96.png` | 96×96 图标 | | `icon/128.png` | 128×128 图标(Chrome Web Store) | -| `_locales/` | Chrome 扩展本地化资源 | - -## 本地化资源 - -项目使用 Chrome 扩展标准 `chrome.i18n`,默认语言在 `wxt.config.ts` 中配置为 `zh_CN`。 - -| 文件/目录 | 用途 | -| ------------------------------ | -------------------------------------------------- | -| `_locales/zh_CN/messages.json` | 默认中文语言包,包含功能名、描述、按钮、提示等翻译 | - -添加或修改文案时: - -- 使用 Chrome 扩展消息格式:`"key": { "message": "文本" }` -- key 使用下划线分隔,例如 `testDataGenerator_title` -- 代码中通过 `useI18n` 或 `getMessage` 读取,不要新增 `i18n/locales` 目录 ## 注意事项 - 修改图标后需同步更新 `wxt.config.ts` 中的 manifest 配置 - 图标格式推荐使用 PNG,确保透明背景 -- 修改 `_locales` 后需确认 `manifest.default_locale` 与语言目录名一致 +- UI 文案不在此目录维护,见 `src/config/features.tsx` 与各页面组件 diff --git a/src/providers/README.md b/src/providers/README.md index 5bb97ce..ce37705 100644 --- a/src/providers/README.md +++ b/src/providers/README.md @@ -28,36 +28,46 @@ React.StrictMode - **当前页面**:`currentPage`(`PageType`) - **可见页面列表**:`visiblePages` - **页面排序**:`pageOrder` +- **最近使用工具**:`recentlyUsedTools`(最多 3 项,供 TopBar 搜索历史使用) - **加载状态**:`isLoaded` 核心特性: - 通过 `chrome.storage` 持久化路由状态 -- 使用 `localStorage` 快照实现首屏 0 闪烁 +- 使用 `localStorage` 快照(`snapshot/{key}`)实现首屏 0 闪烁 - 支持 popup/sidepanel/tab 三种入口的独立路由同步(通过 `syncKey`、`visiblePagesKey`、`pageOrderKey` 配置) - 处理右键菜单待处理数据的路由跳转 - 监听 `chrome.storage.onChanged` 实现跨端同步 +### 初始化与防覆盖 + +异步加载 storage 期间,快照值会作为首屏初始 state。加载完成后: + +1. **`canPersistRef`**:仅在 `loadInitialData` 成功后才设为 `true`,在此之前不会向 storage 写入,避免默认值覆盖已有路由 +2. **`hasUserNavigatedRef`**:用户调用 `navigateTo` / `goHome` 后设为 `true`,异步加载结果不会覆盖用户已选页面 +3. **`mergeWithDefaults`**:将已保存的页面列表与默认列表合并,新增功能会自动出现在列表末尾 + 导出: - `RouterProvider` 组件 -- `useRouter()` Hook — 获取 `currentPage`、`visiblePages`、`pageOrder`、`navigateTo`、`goHome` 等 +- `useRouter()` Hook — 获取 `currentPage`、`visiblePages`、`pageOrder`、`recentlyUsedTools`、`navigateTo`、`goHome` 等 ## ThemeModeProvider.tsx 主题模式 Provider,管理: - **主题模式**:`light` / `dark` / `system` -- **解析后的主题**:`resolvedTheme`(`light` / `dark`) +- **解析后的主题**:`resolvedMode`(`light` / `dark`) 核心特性: -- 使用 `localStorage` 快照实现首屏 0 闪烁 +- 使用 `themeSnapshot.ts` 读写 `localStorage` 快照,实现首屏 0 闪烁 - 监听系统级暗色模式变化(`matchMedia`) - 通过 `chrome.storage` 跨端同步主题偏好 - 自动在 `document.documentElement` 上切换 `dark` class +- **`hasUserSetMode`**:用户主动切换主题后,异步 storage 加载不会覆盖用户选择 导出: - `ThemeModeProvider` 组件 -- `useThemeMode()` Hook — 获取 `themeMode`、`resolvedTheme`、`setThemeMode` +- `useThemeMode()` Hook — 获取 `mode`、`resolvedMode`、`setMode` diff --git a/src/utils/README.md b/src/utils/README.md index 62a13a4..0cbdcd7 100644 --- a/src/utils/README.md +++ b/src/utils/README.md @@ -7,6 +7,9 @@ | 文件 | 用途 | | ------------------------ | --------------------------------------------------------------------------------------------------- | | `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 封装:获取活动标签页、获取域名、在新标签页打开扩展页面 | | `clipboard.ts` | 剪贴板操作:`copyTextToClipboard`(文本)、`copyImageToClipboard`(图片) | | `messages.ts` | 扩展消息通信:基于 `@webext-core/messaging`,定义 `MessageAction` 枚举和 `ProtocolMap` 类型安全映射 | @@ -17,11 +20,10 @@ | `jsonToYaml.ts` | JSON→YAML 转换 | | `jsonToToml.ts` | JSON→TOML 转换 | | `qrCodeParser.ts` | 二维码解析:基于 `qr-scanner` 库从文件中解析二维码 | -| `storageCleaner.ts` | 存储清理:获取当前标签页、检测受限 URL、计算 Cookie/Storage 大小、清理操作 | +| `storageCleaner.ts` | 存储清理:获取当前标签页、计算 Cookie/Storage 大小、清理操作 | | `textStatistics.ts` | 文本统计:使用 `Intl.Segmenter` 计算字符数/单词数/行数/字节大小 | | `format.ts` | 通用格式化:`formatBytes` 将字节转为可读字符串(B/KB/MB/GB/TB) | | `dayjs.ts` | Day.js 初始化:扩展 UTC、Timezone、RelativeTime 插件,加载中文本地化 | -| `chromeI18n.ts` | Chrome `chrome.i18n` 包装:提供 `getMessage` 和兼容 React 使用的 `useI18n` Hook | | `ruleStorage.ts` | 测试数据生成器规则存储:基于 `localStorage` 的 CRUD、搜索、导入/导出和数量限制 | | `dataExporter.ts` | 测试数据导出:JSON/CSV 转换、文件下载和复制到剪贴板 | | `rightClickInjection.ts` | 右键恢复注入脚本:在页面上下文恢复 contextmenu/copy/paste 等事件默认行为 | @@ -34,6 +36,15 @@ | `useContextMenuData.ts` | 右键菜单数据 Hook:从 storage 读取待处理数据,匹配 featureKey 后消费并触发回调 | | `useDebounce.ts` | 防抖 Hook:对值进行延迟更新,避免频繁触发 | +### useStorageState 初始化防覆盖 + +`useStorageState` 在挂载时从 storage 异步加载。写入 storage 需满足以下任一条件: + +- **`loadSucceededRef`**:storage 读取成功 +- **`userModifiedRef`**:用户通过 setter 主动修改过值 + +若 storage 读取失败且用户未修改,不会将默认值写回 storage,避免静默覆盖已有数据。 + ## 使用约定 - 工具函数使用**命名导出**(`export function xxx()`) @@ -41,3 +52,4 @@ - Hook 使用 `use` 前缀命名,定义返回值接口类型 - 存储操作使用 `chromeStorage.ts` 的 `storageUtil` 封装,不要直接调用 `chrome.storage` - 消息通信使用 `messages.ts` 的 `sendMessage`/`onMessage`,不要使用原生 `chrome.runtime.sendMessage` +- 需要首屏快照的新持久化状态,优先复用 `syncSnapshot.ts` 或参考 `themeSnapshot.ts` 模式