Develop (#74)
* feat: 生成成功改为 toast 提示 * refactor: 数据预览改为显示示例数据,简化组件逻辑 * fix: 非必填字段预览显示 null * fix: 修复空值率与必填状态的逻辑 * fix: 切换为非必填时默认空值率 100% * feat: 字段编辑弹窗添加「完成」按钮 去除关闭按钮,添加取消按钮 * refactor: 删除未使用的 defaultNullRate 功能 * feat: 优化规则管理功能 * fix: 更新 features 测试断言以匹配新增的 testDataGenerator * refactor: 将 formatFileSize 重命名为 formatBytes 并移至通用工具模块 * refactor: 使用 Label 组件替换原生 label 元素,并在 DecodeResultPaper 中引入 Input 组件 * refactor: 规范化页面组件目录结构 * refactor(JsonTools): 规范化页面组件目录结构 * refactor(StorageCleaner): 规范化页面组件目录结构 * refactor(Timestamp): 规范化页面组件目录结构 * refactor(i18n): 移除 chrome.i18n 国际化,统一使用中文硬编码 * refactor(AppRoot): 移除 RouterProvider 组件,直接渲染子组件 * fix(RouterProvider): 将 goBack 重命名为 goHome,修复跨窗同步 merge 不一致 * fix(ThemeModeProvider): 修复主题快照同步与首屏闪烁问题 * refactor: 将 UI 文案改为中文并优化相关逻辑 * refactor: 清理代码注释并优化组件结构 * feat(Dashboard): 重构仪表板功能组件和逻辑 * refactor: 更新功能键名称并移除 README 文件 * refactor: 重构 TopBar 组件并更新项目结构 * feat(TopBar): 添加搜索功能组件并优化结构 * refactor: 统一使用 Button 组件替换原有 button 元素 * feat: 添加 EmptyPlaceholder 组件并更新相关页面 * feat(TextStatistics): 引入 StatCard 组件以优化统计信息展示 * feat(Timestamp): 重构时间戳转换功能,新增 ConverterForm 组件 * feat(Jwt): 重构 JWT 组件,新增常量和结果视图 * refactor(RightClickRestorer): 用状态配置表简化页面 UI 结构 * fix(business): 修复 skewedRandom 函数中的数学计算错误 * refactor(ImageUploader): 移除预览 URL 相关逻辑并简化粘贴事件处理 * feat(TestDataGenerator): 更新规则编辑提示和警告显示逻辑 * refactor(CopyButton.test): 移除冗余测试用例 * refactor(RouterProvider, chromeStorage, useContextMenuData): 更新存储获取逻辑以移除默认值参数 * refactor(代码重复): 抽取共享工具函数与 UI 组件,消除多处重复实现 * fix(RouterProvider): 修复初始加载状态逻辑,确保在加载前不覆盖 chrome.storage * feat(TopBar): 添加搜索框快捷键提示功能及相关测试 * refactor(StorageCleaner): 移动 isRestrictedUrl 函数到独立模块并更新相关引用 * fix(StorageCleaner): 修复 IndexedDB 清理竞态、部分成功计数与刷新后状态同步 * fix: 修复路由初始化竞态、存储状态静默覆盖与存储清理误删 * refactor: 统一中文文案并简化组件结构,提升代码可读性 * refactor: 更新 Worker 消息类型以支持生成任务 ID * fix: 修复 ruleStorage 写入失败处理与 CopyButton 成功态样式
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# CI/CD 配置
|
||||
|
||||
## CI 步骤
|
||||
|
||||
严格顺序,任一步骤失败则停止并标记 CI 失败:
|
||||
|
||||
1. `setup`(安装依赖、`wxt prepare`)
|
||||
2. 并行运行 `lint`、`typecheck`、`test`(三者全部通过才继续)
|
||||
3. `build`(仅当步骤 2 全部成功时执行)
|
||||
|
||||
## Pre-commit Hook
|
||||
|
||||
`.husky/pre-commit` 调用 `lint-staged`,任一步骤返回非零则终止提交:
|
||||
|
||||
1. 代码文件 (`*.{ts,tsx,js,jsx,mjs}`):运行 `eslint --fix --max-warnings=0 --no-warn-ignored`;若失败则终止并报告错误
|
||||
2. 同一代码文件:运行 `prettier --write`
|
||||
3. 其他文件 (`*.{json,css,scss,md}`):运行 `prettier --write`
|
||||
+74
-74
@@ -265,12 +265,11 @@ const visibleSet = useMemo(() => new Set<string>(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 <h1>{t('timestamp_title')}</h1>;
|
||||
}
|
||||
|
||||
// ✅ 带占位符
|
||||
export function NotFoundMessage() {
|
||||
const { t } = useI18n('router');
|
||||
return <p>{t('router_notFoundDescription', { entryPointType: 'popup' })}</p>;
|
||||
{
|
||||
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 <h1 className="font-bold text-sm">时间戳转换</h1>;
|
||||
}
|
||||
|
||||
// ✅ 常量文件 — 可复用文案
|
||||
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 (
|
||||
<div className="p-4 w-full flex flex-col space-y-4 select-none">
|
||||
{/* 纯 UI 渲染 */}
|
||||
{/* 纯 UI 渲染,文案直接写中文 */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<Mode>('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<HTMLDivElement> {
|
||||
// ✅ 使用 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() {
|
||||
<SwitchButtonGroup
|
||||
value={pageMode}
|
||||
options={[
|
||||
{ value: 'modeA', label: t('feature:modeA') },
|
||||
{ value: 'modeB', label: t('feature:modeB') },
|
||||
{ value: 'modeA', label: '模式 A' },
|
||||
{ value: 'modeB', label: '模式 B' },
|
||||
]}
|
||||
onChange={(v: PageMode) => 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/` | 静态资源(图标等) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
# Copilot 指令
|
||||
|
||||
基于 WXT 框架的浏览器扩展项目(React 19 + TypeScript),为开发者和测试人员提供效率工具:时间戳转换、存储清理、JWT 解析、JSON 工具、二维码、Base64、测试数据生成器等。
|
||||
|
||||
## 核心命令
|
||||
|
||||
```bash
|
||||
npm run dev # Chrome 开发模式(支持 HMR)
|
||||
npm run dev:firefox # Firefox 开发模式
|
||||
npm run build # Chrome 生产构建
|
||||
npm run build:firefox # Firefox 生产构建
|
||||
npm run zip # 打包 Chrome 扩展(.output/*.zip)
|
||||
npm run zip:firefox # 打包 Firefox 扩展
|
||||
npm run lint # ESLint 检查(--max-warnings=0)
|
||||
npm run typecheck # TypeScript 类型检查(tsc --noEmit)
|
||||
npm run test # 运行全部单元测试(vitest run)
|
||||
npm run test:watch # Vitest 监视模式
|
||||
npm run test:coverage # 带覆盖率的测试
|
||||
```
|
||||
|
||||
运行单个测试文件:`npx vitest run path/to/file.test.ts`
|
||||
|
||||
修改 `package.json` 后需运行 `npm install`(会自动触发 `postinstall` → `wxt prepare` 重新生成 `.wxt/` 类型声明)。
|
||||
|
||||
## CI 流水线(GitHub Actions)
|
||||
|
||||
严格顺序门控,任一步骤失败则终止:
|
||||
|
||||
1. **setup** — 安装依赖,缓存 `node_modules`
|
||||
2. **lint**、**typecheck**、**test** — 三者并行运行,全部通过才继续
|
||||
3. **build** — Chrome + Firefox 矩阵构建(仅当步骤 2 全部通过时执行)
|
||||
|
||||
Pre-commit 钩子(`.husky/pre-commit` → `lint-staged`):
|
||||
|
||||
1. 代码文件(`*.{ts,tsx,js,jsx,mjs}`):运行 `eslint --fix --max-warnings=0`
|
||||
2. 同一代码文件:运行 `prettier --write`
|
||||
3. 其他文件(`*.{json,css,scss,md}`):运行 `prettier --write`
|
||||
|
||||
## 项目架构
|
||||
|
||||
### 路由(不使用 React Router)
|
||||
|
||||
路由完全通过 `config/features.tsx` 中的 `FEATURES` 数组管理。每个功能定义一个 `key`(类型为 `types/storage.d.ts` 中的 `PageType`)和三个懒加载组件,分别对应 `popup`、`sidepanel`、`tab` 三种渲染模式。`providers/RouterProvider.tsx` 中的 `RouterProvider` 根据存储状态渲染当前页面。
|
||||
|
||||
存在三套独立的路由作用域:`app/popupRoute`、`app/sidepanelRoute`、`app/tabRoute`,各自维护独立的可见页面列表和页面排序。
|
||||
|
||||
### 存储
|
||||
|
||||
所有 Chrome Storage 键必须在 `types/storage.d.ts` 的 `StorageSchema` 中声明,键名使用 kebab-case 格式(如 `app/currentRoute`)。使用 `utils/chromeStorage.ts` 中的类型安全封装(`storageUtil.get/set/remove`)。
|
||||
|
||||
Router 同时使用 `chrome.storage.local` 持久化和 `localStorage` 快照来消除首屏闪烁。
|
||||
|
||||
### 扩展通信
|
||||
|
||||
使用 `@webext-core/messaging`。通信协议在 `utils/messages.ts` 中通过 `ProtocolMap` 定义。使用该模块导出的 `sendMessage` / `onMessage`,不要直接使用原生 `chrome.runtime.sendMessage`。
|
||||
|
||||
### 页面组件模式
|
||||
|
||||
功能页面遵循 **UI + Hook 分离** 模式,详见 [CODING_STANDARDS.md § 11](./CODING_STANDARDS.md#11-页面开发规范):
|
||||
|
||||
```
|
||||
pages/FeatureName/
|
||||
├── index.tsx # UI 组件(纯展示,使用 shadcn/ui 组件)
|
||||
├── useFeatureName.ts # 业务逻辑 Hook(状态管理 + 转换逻辑)
|
||||
└── constants.ts # 常量定义(可选)
|
||||
```
|
||||
|
||||
- 页面组件调用 `useI18n('featureName')` 获取翻译函数
|
||||
- Hook 负责所有状态管理,通过返回值暴露给页面
|
||||
- 子组件可进一步拆分(如 `LiveClock.tsx`、`ResultView.tsx`)
|
||||
|
||||
### 新功能开发清单
|
||||
|
||||
1. 在 `types/storage.d.ts` 的 `PageType` 联合类型中添加新成员
|
||||
2. 在 `config/features.tsx` 的 `FEATURES` 数组中添加配置(key、翻译键、图标、三种渲染模式组件)
|
||||
3. 在 `pages/` 目录创建页面组件(懒加载):
|
||||
- `index.tsx` — 使用 `useI18n` 的 UI 组件
|
||||
- `useFeatureName.ts` — 业务逻辑 Hook
|
||||
- `constants.ts` — 常量(可选)
|
||||
4. 在 `public/_locales/zh_CN/messages.json` 添加 Chrome i18n 翻译
|
||||
5. 如需新权限,更新 `wxt.config.ts` 的 `manifest.permissions`
|
||||
6. 添加对应的单元测试
|
||||
|
||||
## 关键规范
|
||||
|
||||
> 完整的代码编写规范详见 [CODING_STANDARDS.md](./CODING_STANDARDS.md)。
|
||||
|
||||
### 浏览器 API
|
||||
|
||||
始终使用 `wxt/browser` 导出的 `browser` 对象,而非原生 `chrome` API,以确保跨浏览器兼容性。
|
||||
|
||||
### 路径别名
|
||||
|
||||
`@/` 映射到项目根目录(已在 tsconfig 和 vitest.config 中配置)。跨目录导入使用 `@/` 绝对别名,同目录导入使用 `./` 相对路径。
|
||||
|
||||
### UI 组件
|
||||
|
||||
- 使用 `components/ui/` 下的 shadcn/ui 组件(button、dialog、select、input 等)
|
||||
- 图标:`lucide-react`
|
||||
- 样式:Tailwind CSS + `@/lib/utils` 中的 `cn()` 工具函数(clsx + tailwind-merge)
|
||||
- 主题:使用 shadcn/ui 语义化 token(`bg-background`、`text-foreground`、`border-border` 等),禁止硬编码颜色
|
||||
|
||||
### 代码分割
|
||||
|
||||
`wxt.config.ts` 通过 `manualChunksForHtmlOnly()` 自动分组 vendor 依赖(vendor-react、vendor-qr、vendor-dnd),无需手动配置。
|
||||
|
||||
### 代码风格
|
||||
|
||||
- 禁止使用 `any`(测试文件除外)
|
||||
- 未使用的变量/参数:使用 `_` 前缀(如 `_unused`)
|
||||
- Prettier:100 字符宽、单引号、尾逗号 all、LF 换行
|
||||
- ESLint 使用 `typescript-eslint` 的 `projectService: true`
|
||||
- 导出模式:页面组件 default export,工具函数/Hook 命名导出,UI 组件 forwardRef + 命名导出
|
||||
|
||||
### 测试
|
||||
|
||||
- 环境:jsdom
|
||||
- 全局变量:`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}`
|
||||
- 使用 `vi.mock()` 进行模块级 mock;避免重复 mock `vitest.setup.ts` 中已有的内容
|
||||
- 测试工具:`@testing-library/react` + `@testing-library/user-event`
|
||||
|
||||
### 国际化(i18n)
|
||||
|
||||
- 使用 Chrome 扩展标准 `chrome.i18n`
|
||||
- 默认语言目录:`public/_locales/zh_CN/messages.json`
|
||||
- 使用方式:`import { useI18n } from '@/utils/chromeI18n'`
|
||||
- 翻译键格式:直接 key(如 `timestamp_title`);兼容 `namespace:key.path` 并转换为下划线
|
||||
- 回退策略:缺失翻译返回 key 本身,并在开发模式下记录 warning
|
||||
- 限制:`chrome.i18n` 跟随浏览器语言,不能在运行时动态切换语言
|
||||
|
||||
### WXT 生成文件
|
||||
|
||||
- `.wxt/` 目录由 `postinstall`(`wxt prepare`)自动生成,包含 TypeScript 类型声明和扩展 tsconfig
|
||||
- 生产构建输出到 `.output/` 目录
|
||||
- `tsconfig.json` 继承自 `./.wxt/tsconfig.json`
|
||||
Reference in New Issue
Block a user