- {/* 左侧自定义动作 */}
{bottomActions.map((action) => (
((props
))}
- {/* 右侧系统按钮组 */}
{allowCopy && value && (
-
+
)}
{showClear && value && !disabled && !readOnly && (
- {/* 错误提示 */}
{displayError && (
{displayError}
diff --git a/src/components/__tests__/CopyButton.test.tsx b/src/components/__tests__/CopyButton.test.tsx
index 845fa0d..6d1c8ac 100644
--- a/src/components/__tests__/CopyButton.test.tsx
+++ b/src/components/__tests__/CopyButton.test.tsx
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { act, render, screen, waitFor } from '@testing-library/react';
+import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
// unmock the globally-mocked component so we test the real implementation
@@ -68,15 +68,10 @@ describe('CopyButton', () => {
expect(mockedToast.error).toHaveBeenCalled();
});
- // ==================== 新增测试 ====================
-
- it('初始渲染时显示 Copy 图标且无 emerald 样式', () => {
+ it('初始渲染时带有默认 aria-label', () => {
render();
- const button = screen.getByRole('button');
- expect(button.className).not.toContain('text-emerald');
- // 通过 aria-label 确认按钮存在,图标由 lucide 渲染为 svg
- expect(button).toHaveAttribute('aria-label');
+ expect(screen.getByRole('button')).toHaveAttribute('aria-label', '复制');
});
it('自定义 tooltip 会覆盖默认 title 和 aria-label', () => {
@@ -119,15 +114,12 @@ describe('CopyButton', () => {
await user.click(screen.getByRole('button'));
- // 在 1.5 秒超时到期前卸载组件
act(() => {
vi.advanceTimersByTime(500);
});
- // 卸载不应抛出 "Can't perform a React state update on an unmounted component" 警告
expect(() => unmount()).not.toThrow();
- // 前进剩余时间,确认没有异常
act(() => {
vi.advanceTimersByTime(2000);
});
@@ -141,23 +133,15 @@ describe('CopyButton', () => {
const button = screen.getByRole('button');
- // 快速点击 3 次
await user.click(button);
await user.click(button);
await user.click(button);
- // copyTextToClipboard 应该被调用 3 次(每次点击都执行)
expect(mockedCopy).toHaveBeenCalledTimes(3);
- // 但 setTimeout 相关的 clearTimeout + setTimeout 组合应正常工作
- // advance 1.5 秒后,copied 状态应恢复为 false
act(() => {
vi.advanceTimersByTime(1500);
});
-
- await waitFor(() => {
- expect(button.className).not.toContain('text-emerald');
- });
});
it('其他 button props 通过 ...props 透传', () => {
diff --git a/src/components/__tests__/EmptyPlaceholder.test.tsx b/src/components/__tests__/EmptyPlaceholder.test.tsx
index e72fbb8..d5e6ce7 100644
--- a/src/components/__tests__/EmptyPlaceholder.test.tsx
+++ b/src/components/__tests__/EmptyPlaceholder.test.tsx
@@ -4,13 +4,13 @@ import EmptyPlaceholder from '@/components/EmptyPlaceholder';
describe('EmptyPlaceholder 组件', () => {
it('应渲染字符串提示文本', () => {
- render({'请输入内容'});
+ render(请输入内容);
expect(screen.getByText('请输入内容')).toBeInTheDocument();
});
it('应应用规范空状态容器样式', () => {
- const { container } = render({'提示'});
+ const { container } = render(提示);
const placeholder = container.firstChild;
expect(placeholder).toHaveClass(
@@ -24,14 +24,14 @@ describe('EmptyPlaceholder 组件', () => {
it('应支持 className 自定义容器样式', () => {
const { container } = render(
- {'提示'},
+ 提示,
);
expect(container.firstChild).toHaveClass('flex-1', 'min-h-[320px]');
});
it('应支持 messageClassName 自定义文本样式', () => {
- render({'提示'});
+ render(提示);
expect(screen.getByText('提示')).toHaveClass('text-sm', 'max-w-none');
});
diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts
index 90f0771..7c56c95 100644
--- a/src/entrypoints/background.ts
+++ b/src/entrypoints/background.ts
@@ -41,14 +41,11 @@ export default defineBackground(() => {
try {
await browser.action.openPopup();
} catch (err) {
- console.warn(
- '[Context Menu] 自动打开 popup 失败,请手动点击扩展图标,暂存数据已安全保留:',
- err,
- );
+ console.warn('[Context Menu] 自动打开 popup 失败,请手动点击扩展图标,数据已暂存:', err);
}
});
- // 监听扩展图标点击事件,安全激活侧边栏
+ // 扩展图标点击时打开侧边栏
browser.action.onClicked.addListener(async (tab) => {
if (tab.id) {
try {
diff --git a/src/entrypoints/content/contextMenuHandler.ts b/src/entrypoints/content/contextMenuHandler.ts
index 4594960..c5671e2 100644
--- a/src/entrypoints/content/contextMenuHandler.ts
+++ b/src/entrypoints/content/contextMenuHandler.ts
@@ -4,18 +4,17 @@ import { getTextStats } from '@/utils/textStatistics';
import { hidePopover, showTextStatsResult, showTimestampResult } from './uiPopover';
function convertTimestamp(input: string): string {
- const invalidText = '无效时间戳';
const num = Number(input.trim());
if (isNaN(num)) {
- return invalidText;
+ return '无效时间戳';
}
- // 1e12 判定毫秒级/秒级时间戳兼容
+ // 1e12 区分毫秒/秒级时间戳
const d = num > 1e12 ? new Date(num) : new Date(num * 1000);
if (isNaN(d.getTime())) {
- return invalidText;
+ return '无效时间戳';
}
const year = d.getFullYear();
diff --git a/src/entrypoints/popup/main.tsx b/src/entrypoints/popup/main.tsx
index e13a462..6f602b5 100644
--- a/src/entrypoints/popup/main.tsx
+++ b/src/entrypoints/popup/main.tsx
@@ -1,11 +1,8 @@
import ReactDOM from 'react-dom/client';
-import { applyThemeFromSnapshot } from '@/utils/themeSnapshot';
import AppRoot from '@/providers/AppRoot';
import '@/index.css';
import App from './App.tsx';
-applyThemeFromSnapshot();
-
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/src/entrypoints/sidepanel/main.tsx b/src/entrypoints/sidepanel/main.tsx
index e13a462..6f602b5 100644
--- a/src/entrypoints/sidepanel/main.tsx
+++ b/src/entrypoints/sidepanel/main.tsx
@@ -1,11 +1,8 @@
import ReactDOM from 'react-dom/client';
-import { applyThemeFromSnapshot } from '@/utils/themeSnapshot';
import AppRoot from '@/providers/AppRoot';
import '@/index.css';
import App from './App.tsx';
-applyThemeFromSnapshot();
-
ReactDOM.createRoot(document.getElementById('root')!).render(
diff --git a/src/layout/TopBar/SearchResultItem.tsx b/src/layout/TopBar/SearchResultItem.tsx
index db020a9..62ca2b4 100644
--- a/src/layout/TopBar/SearchResultItem.tsx
+++ b/src/layout/TopBar/SearchResultItem.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { type FeatureConfig } from '@/config/features';
import { cn } from '@/lib/utils';
@@ -8,7 +7,7 @@ interface SearchResultItemProps {
onSelect: (feature: FeatureConfig) => void;
}
-const SearchResultItem = React.memo(({ feature, selected, onSelect }: SearchResultItemProps) => {
+export default function SearchResultItem({ feature, selected, onSelect }: SearchResultItemProps) {
return (
);
-});
-SearchResultItem.displayName = 'SearchResultItem';
-
-export default SearchResultItem;
+}
diff --git a/src/layout/TopBar/constants.ts b/src/layout/TopBar/constants.ts
index 41cbb2b..7e1924f 100644
--- a/src/layout/TopBar/constants.ts
+++ b/src/layout/TopBar/constants.ts
@@ -4,8 +4,6 @@ export const SEARCH_HISTORY_DISPLAY = 5;
export const isSearchHistory = (val: unknown): val is string[] =>
Array.isArray(val) && val.every((item) => typeof item === 'string');
-/** 根据平台返回搜索框快捷键提示文案 */
export function getSearchShortcutLabel(): string {
- if (typeof navigator === 'undefined') return 'Ctrl+K';
return /Mac|iPhone|iPad|iPod/i.test(navigator.userAgent) ? '⌘K' : 'Ctrl+K';
}
diff --git a/src/layout/TopBar/index.tsx b/src/layout/TopBar/index.tsx
index 3a66eb4..6513e12 100644
--- a/src/layout/TopBar/index.tsx
+++ b/src/layout/TopBar/index.tsx
@@ -8,18 +8,17 @@ import { useTopBar } from './useTopBar';
export default function TopBar() {
const {
searchQuery,
- showResults,
searchResults,
recentFeatures,
selectedIndex,
isDashboard,
ThemeIcon,
themeTitle,
+ showDropdown,
containerRef,
inputRef,
- setSearchQuery,
- setShowResults,
- setSelectedIndex,
+ handleSearchQueryChange,
+ handleSearchFocus,
handleSelectFeature,
handleKeyDown,
cycleThemeMode,
@@ -40,14 +39,6 @@ export default function TopBar() {
},
];
- const handleSearchQueryChange = (value: string) => {
- setSearchQuery(value);
- setShowResults(true);
- setSelectedIndex(-1);
- };
-
- const showDropdown = showResults && (searchQuery.trim() || recentFeatures.length > 0);
-
return (
@@ -70,7 +61,7 @@ export default function TopBar() {
inputRef={inputRef}
searchQuery={searchQuery}
onSearchQueryChange={handleSearchQueryChange}
- onFocus={() => setShowResults(true)}
+ onFocus={handleSearchFocus}
onKeyDown={handleKeyDown}
onClear={clearSearch}
/>
diff --git a/src/layout/TopBar/useTopBar.ts b/src/layout/TopBar/useTopBar.ts
index 632b103..1e4b87c 100644
--- a/src/layout/TopBar/useTopBar.ts
+++ b/src/layout/TopBar/useTopBar.ts
@@ -9,18 +9,17 @@ import { isSearchHistory, SEARCH_HISTORY_DISPLAY, SEARCH_HISTORY_LIMIT } from '.
export interface UseTopBarReturn {
searchQuery: string;
- showResults: boolean;
searchResults: FeatureConfig[];
recentFeatures: FeatureConfig[];
selectedIndex: number;
isDashboard: boolean;
ThemeIcon: typeof Sun;
themeTitle: string;
+ showDropdown: boolean;
containerRef: RefObject;
inputRef: RefObject;
- setSearchQuery: (value: string) => void;
- setShowResults: (value: boolean) => void;
- setSelectedIndex: (value: number | ((prev: number) => number)) => void;
+ handleSearchQueryChange: (value: string) => void;
+ handleSearchFocus: () => void;
handleSelectFeature: (feature: FeatureConfig) => void;
handleKeyDown: (e: React.KeyboardEvent) => void;
cycleThemeMode: () => void;
@@ -90,7 +89,6 @@ export function useTopBar(): UseTopBarReturn {
}, [searchHistory, searchQuery]);
const saveToHistory = (featureKey: string) => {
- if (!featureKey.trim()) return;
setSearchHistory((prev) =>
[featureKey, ...prev.filter((h) => h !== featureKey)].slice(0, SEARCH_HISTORY_LIMIT),
);
@@ -124,18 +122,14 @@ export function useTopBar(): UseTopBarReturn {
setSelectedIndex((prev) => (prev > 0 ? prev - 1 : prev));
} else if (e.key === 'Enter') {
e.preventDefault();
- if (selectedIndex >= 0 && selectedIndex < totalItems) {
- if (searchQuery.trim()) {
- handleSelectFeature(searchResults[selectedIndex]);
- } else {
- const selected = displayedHistory[selectedIndex];
- if (selected) {
- handleSelectFeature(selected);
- }
- }
- } else if (searchQuery.trim() && searchResults.length > 0) {
- handleSelectFeature(searchResults[0]);
- }
+ const items = searchQuery.trim() ? searchResults : displayedHistory;
+ const feature =
+ selectedIndex >= 0 && selectedIndex < totalItems
+ ? items[selectedIndex]
+ : searchQuery.trim() && searchResults.length > 0
+ ? searchResults[0]
+ : undefined;
+ if (feature) handleSelectFeature(feature);
} else if (e.key === 'Escape') {
setShowResults(false);
inputRef.current?.blur();
@@ -147,20 +141,30 @@ export function useTopBar(): UseTopBarReturn {
setSelectedIndex(-1);
};
+ const handleSearchQueryChange = (value: string) => {
+ setSearchQuery(value);
+ setShowResults(true);
+ setSelectedIndex(-1);
+ };
+
+ const handleSearchFocus = () => setShowResults(true);
+
+ const showDropdown =
+ showResults && (searchQuery.trim().length > 0 || displayedHistory.length > 0);
+
return {
searchQuery,
- showResults,
searchResults,
recentFeatures: displayedHistory,
selectedIndex,
+ showDropdown,
isDashboard: currentPage === 'dashboard',
ThemeIcon,
themeTitle,
containerRef,
inputRef,
- setSearchQuery,
- setShowResults,
- setSelectedIndex,
+ handleSearchQueryChange,
+ handleSearchFocus,
handleSelectFeature,
handleKeyDown,
cycleThemeMode,
diff --git a/src/lib/README.md b/src/lib/README.md
index 9274705..4ea8d0f 100644
--- a/src/lib/README.md
+++ b/src/lib/README.md
@@ -37,7 +37,6 @@ import { cn } from '@/lib/utils';
| `business.ts` | 业务数据生成器(订单号、价格、日期、状态等) |
| `technical.ts` | 技术数据生成器(UUID、IPv4、URL) |
| `basic.ts` | 基础类型生成器(整数、浮点数、布尔值、字符串) |
-| `types.ts` | 生成器库内部类型与通用选项 |
使用示例:
diff --git a/src/lib/generators/basic.ts b/src/lib/generators/basic.ts
index c7118ca..f0decf2 100644
--- a/src/lib/generators/basic.ts
+++ b/src/lib/generators/basic.ts
@@ -1,14 +1,6 @@
-/**
- * 基础类型生成器
- * 包含:随机整数、随机浮点数、随机字符串、从列表选择
- */
-
import type { GeneratorDefinition } from '@/types/testDataGenerator';
import { randomInt, randomPick } from './random';
-/**
- * 随机整数生成器
- */
export const randomIntGenerator: GeneratorDefinition = {
id: 'randomInt',
name: '随机整数',
@@ -43,9 +35,6 @@ export const randomIntGenerator: GeneratorDefinition = {
},
};
-/**
- * 随机浮点数生成器
- */
export const randomFloat: GeneratorDefinition = {
id: 'randomFloat',
name: '随机浮点数',
@@ -85,9 +74,6 @@ export const randomFloat: GeneratorDefinition = {
},
};
-/**
- * 随机字符串生成器
- */
export const randomString: GeneratorDefinition = {
id: 'randomString',
name: '随机字符串',
@@ -166,9 +152,6 @@ export const randomString: GeneratorDefinition = {
},
};
-/**
- * 从列表选择生成器
- */
export const fromList: GeneratorDefinition = {
id: 'fromList',
name: '从列表选择',
diff --git a/src/lib/generators/business.ts b/src/lib/generators/business.ts
index cc366ae..32791a9 100644
--- a/src/lib/generators/business.ts
+++ b/src/lib/generators/business.ts
@@ -1,14 +1,6 @@
-/**
- * 业务数据生成器
- * 包含:订单号、价格、日期、状态、数量、评分、折扣、库存
- */
-
import type { GeneratorDefinition } from '@/types/testDataGenerator';
import { randomInt, randomPick } from './random';
-/**
- * 格式化日期
- */
function formatDate(date: Date, format: string): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
@@ -26,9 +18,6 @@ function formatDate(date: Date, format: string): string {
.replace('ss', seconds);
}
-/**
- * 订单号生成器
- */
export const orderId: GeneratorDefinition = {
id: 'orderId',
name: '订单号',
@@ -68,9 +57,6 @@ export const orderId: GeneratorDefinition = {
},
};
-/**
- * 价格生成器
- */
export const price: GeneratorDefinition = {
id: 'price',
name: '价格',
@@ -142,9 +128,6 @@ export const price: GeneratorDefinition = {
},
};
-/**
- * 生成心理定价(如 9.99, 19.9, 99)
- */
function generatePsychologicalPrice(min: number, max: number): number {
const patterns = [
() => {
@@ -167,9 +150,6 @@ function generatePsychologicalPrice(min: number, max: number): number {
return price;
}
-/**
- * 生成真实分布价格(对数正态分布)
- */
function generateRealisticPrice(min: number, max: number): number {
const mean = (Math.log(min) + Math.log(max)) / 2;
const stdDev = (Math.log(max) - Math.log(min)) / 4;
@@ -182,9 +162,6 @@ function generateRealisticPrice(min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
-/**
- * 日期生成器
- */
export const date: GeneratorDefinition = {
id: 'date',
name: '日期',
@@ -235,9 +212,6 @@ export const date: GeneratorDefinition = {
},
};
-/**
- * 状态生成器
- */
export const status: GeneratorDefinition = {
id: 'status',
name: '状态',
@@ -290,9 +264,6 @@ export const status: GeneratorDefinition = {
},
};
-/**
- * 数量生成器
- */
export const quantity: GeneratorDefinition = {
id: 'quantity',
name: '数量',
@@ -339,9 +310,6 @@ export const quantity: GeneratorDefinition = {
},
};
-/**
- * 泊松分布随机数
- */
function poissonRandom(lambda: number): number {
const L = Math.exp(-lambda);
let k = 0;
@@ -353,9 +321,6 @@ function poissonRandom(lambda: number): number {
return k - 1;
}
-/**
- * 评分生成器
- */
export const rating: GeneratorDefinition = {
id: 'rating',
name: '评分',
@@ -410,9 +375,6 @@ export const rating: GeneratorDefinition = {
},
};
-/**
- * 偏向高分的随机数(模拟真实评分分布)
- */
function skewedRandom(min: number, max: number): number {
// 使用 Beta 分布模拟评分偏向
const alpha = 3;
@@ -425,9 +387,6 @@ function skewedRandom(min: number, max: number): number {
return min + x * (max - min);
}
-/**
- * 折扣生成器
- */
export const discount: GeneratorDefinition = {
id: 'discount',
name: '折扣',
@@ -479,9 +438,6 @@ export const discount: GeneratorDefinition = {
},
};
-/**
- * 生成心理折扣(如 5%, 10%, 20%, 30%)
- */
function generatePsychologicalDiscount(min: number, max: number): number {
const psychologicalDiscounts = [5, 10, 15, 20, 25, 30, 40, 50, 60, 70, 80];
const validDiscounts = psychologicalDiscounts.filter((d) => d >= min && d <= max);
@@ -491,9 +447,6 @@ function generatePsychologicalDiscount(min: number, max: number): number {
return randomInt(min, max);
}
-/**
- * 库存生成器
- */
export const stock: GeneratorDefinition = {
id: 'stock',
name: '库存',
diff --git a/src/lib/generators/index.ts b/src/lib/generators/index.ts
index 6ce4f5a..9b63cb1 100644
--- a/src/lib/generators/index.ts
+++ b/src/lib/generators/index.ts
@@ -1,17 +1,9 @@
-/**
- * 生成器库索引
- * 导出所有生成器和分类配置
- */
-
import type { GeneratorDefinition, GeneratorCategory } from '@/types/testDataGenerator';
import { personalGenerators } from './personal';
import { businessGenerators } from './business';
import { technicalGenerators } from './technical';
import { basicGenerators } from './basic';
-/**
- * 生成器分类配置
- */
export const generatorCategories: GeneratorCategory[] = [
{ id: 'personal', name: '个人信息', icon: 'User' },
{ id: 'business', name: '业务数据', icon: 'Briefcase' },
@@ -19,9 +11,6 @@ export const generatorCategories: GeneratorCategory[] = [
{ id: 'basic', name: '基础类型', icon: 'Hash' },
];
-/**
- * 所有生成器列表
- */
export const allGenerators: GeneratorDefinition[] = [
...personalGenerators,
...businessGenerators,
@@ -29,23 +18,14 @@ export const allGenerators: GeneratorDefinition[] = [
...basicGenerators,
];
-/**
- * 根据 ID 获取生成器
- */
export function getGeneratorById(id: string): GeneratorDefinition | undefined {
return allGenerators.find((g) => g.id === id);
}
-/**
- * 根据分类 ID 获取生成器列表
- */
export function getGeneratorsByCategory(categoryId: string): GeneratorDefinition[] {
return allGenerators.filter((g) => g.categoryId === categoryId);
}
-/**
- * 搜索生成器
- */
export function searchGenerators(query: string): GeneratorDefinition[] {
const lowerQuery = query.toLowerCase();
return allGenerators.filter(
diff --git a/src/lib/generators/personal.ts b/src/lib/generators/personal.ts
index d7d2235..5fc89ab 100644
--- a/src/lib/generators/personal.ts
+++ b/src/lib/generators/personal.ts
@@ -1,12 +1,6 @@
-/**
- * 个人信息生成器
- * 包含:中文姓名、邮箱、手机号、身份证号、中文地址、年龄
- */
-
import type { GeneratorDefinition } from '@/types/testDataGenerator';
import { randomInt, randomPick } from './random';
-// 中文姓氏
const SURNAMES = [
'王',
'李',
@@ -60,7 +54,6 @@ const SURNAMES = [
'阎',
];
-// 中文名字常用字
const NAME_CHARS = [
'伟',
'芳',
@@ -114,10 +107,8 @@ const NAME_CHARS = [
'慧',
];
-// 邮箱域名
const EMAIL_DOMAINS = ['qq.com', '163.com', '126.com', 'gmail.com', 'outlook.com', 'hotmail.com'];
-// 省份
const PROVINCES = [
'北京市',
'天津市',
@@ -153,7 +144,6 @@ const PROVINCES = [
'新疆维吾尔自治区',
];
-// 城市
const CITIES: Record = {
北京市: ['东城区', '西城区', '朝阳区', '海淀区'],
上海市: ['黄浦区', '徐汇区', '长宁区', '静安区'],
@@ -163,9 +153,6 @@ const CITIES: Record = {
四川省: ['成都市', '绵阳市', '德阳市', '宜宾市'],
};
-/**
- * 中文姓名生成器
- */
export const chineseName: GeneratorDefinition = {
id: 'chineseName',
name: '中文姓名',
@@ -179,9 +166,6 @@ export const chineseName: GeneratorDefinition = {
},
};
-/**
- * 邮箱生成器
- */
export const email: GeneratorDefinition = {
id: 'email',
name: '邮箱',
@@ -208,9 +192,6 @@ export const email: GeneratorDefinition = {
},
};
-/**
- * 手机号生成器
- */
export const chinesePhone: GeneratorDefinition = {
id: 'chinesePhone',
name: '手机号',
@@ -241,9 +222,6 @@ export const chinesePhone: GeneratorDefinition = {
},
};
-/**
- * 身份证号生成器
- */
export const idCard: GeneratorDefinition = {
id: 'idCard',
name: '身份证号',
@@ -268,20 +246,12 @@ export const idCard: GeneratorDefinition = {
generate: (params) => {
const regions = ['110101', '310101', '440103', '440305', '510104', '330102'];
const region = params.region === 'random' ? randomPick(regions) : (params.region as string);
-
- // 出生日期 1960-2005年
const year = randomInt(1960, 2005);
const month = String(randomInt(1, 12)).padStart(2, '0');
const day = String(randomInt(1, 28)).padStart(2, '0');
const birthday = `${year}${month}${day}`;
-
- // 顺序码
const sequence = String(randomInt(1, 999)).padStart(3, '0');
-
- // 前17位
const prefix17 = region + birthday + sequence;
-
- // 校验码
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let sum = 0;
@@ -317,9 +287,6 @@ export const idCard: GeneratorDefinition = {
},
};
-/**
- * 中文地址生成器
- */
export const chineseAddress: GeneratorDefinition = {
id: 'chineseAddress',
name: '中文地址',
@@ -348,9 +315,6 @@ export const chineseAddress: GeneratorDefinition = {
},
};
-/**
- * 年龄生成器
- */
export const age: GeneratorDefinition = {
id: 'age',
name: '年龄',
@@ -402,9 +366,6 @@ export const age: GeneratorDefinition = {
},
};
-/**
- * 正态分布随机数
- */
function normalRandom(min: number, max: number): number {
const mean = (min + max) / 2;
const stdDev = (max - min) / 6;
@@ -417,9 +378,6 @@ function normalRandom(min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
-/**
- * 人口统计分布随机数(模拟真实年龄分布)
- */
function demographicRandom(): number {
const ageGroups = [
{ min: 0, max: 14, weight: 0.18 },
diff --git a/src/lib/generators/technical.ts b/src/lib/generators/technical.ts
index e7da0ac..bbbd38e 100644
--- a/src/lib/generators/technical.ts
+++ b/src/lib/generators/technical.ts
@@ -1,14 +1,6 @@
-/**
- * 技术数据生成器
- * 包含:UUID、IPv4、URL
- */
-
import type { GeneratorDefinition } from '@/types/testDataGenerator';
import { randomInt, randomPick } from './random';
-/**
- * UUID 生成器
- */
export const uuid: GeneratorDefinition = {
id: 'uuid',
name: 'UUID',
@@ -65,9 +57,6 @@ export const uuid: GeneratorDefinition = {
},
};
-/**
- * 生成 UUID v4
- */
function generateUUIDv4(): string {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
@@ -76,9 +65,6 @@ function generateUUIDv4(): string {
});
}
-/**
- * 生成 UUID v1(简化版本,模拟时间戳)
- */
function generateUUIDv1(): string {
const now = Date.now();
const timeLow = (now & 0xffffffff).toString(16).padStart(8, '0');
@@ -92,9 +78,6 @@ function generateUUIDv1(): string {
return `${timeLow}-${timeMid}-${timeHi.toString(16)}-${clockSeq.toString(16)}-${node}`;
}
-/**
- * IPv4 生成器
- */
export const ipv4: GeneratorDefinition = {
id: 'ipv4',
name: 'IPv4',
@@ -129,16 +112,10 @@ export const ipv4: GeneratorDefinition = {
},
};
-/**
- * 生成完全随机 IPv4
- */
function generateRandomIPv4(): string {
return Array.from({ length: 4 }, () => randomInt(0, 255)).join('.');
}
-/**
- * 生成内网 IPv4
- */
function generatePrivateIPv4(): string {
const ranges = [
{ prefix: '10', second: () => randomInt(0, 255) },
@@ -153,9 +130,6 @@ function generatePrivateIPv4(): string {
return `${range.prefix}.${range.second()}.${randomInt(1, 254)}.${randomInt(1, 254)}`;
}
-/**
- * 生成公网 IPv4
- */
function generatePublicIPv4(): string {
let first: number;
do {
@@ -165,9 +139,6 @@ function generatePublicIPv4(): string {
return `${first}.${randomInt(0, 255)}.${randomInt(0, 255)}.${randomInt(1, 254)}`;
}
-/**
- * URL 生成器
- */
export const url: GeneratorDefinition = {
id: 'url',
name: 'URL',
@@ -237,8 +208,6 @@ export const url: GeneratorDefinition = {
path = `/${randomPick(['about', 'contact', 'products', 'services', 'blog'])}`;
}
}
-
- // 添加查询参数
let query = '';
if (Math.random() > 0.5) {
const params = new URLSearchParams();
diff --git a/src/lib/generators/types.ts b/src/lib/generators/types.ts
deleted file mode 100644
index f21c44e..0000000
--- a/src/lib/generators/types.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-/**
- * 生成器内部类型定义
- * 与 testDataGenerator.ts 分离,用于生成器库内部
- */
-
-import type { GeneratorDefinition, GeneratorCategory } from '@/types/testDataGenerator';
-
-export type { GeneratorDefinition, GeneratorCategory };
-
-/**
- * 生成器注册表
- */
-export interface GeneratorRegistry {
- /** 分类列表 */
- categories: GeneratorCategory[];
- /** 生成器列表 */
- generators: GeneratorDefinition[];
-}
-
-/**
- * 随机数生成选项
- */
-export interface RandomOptions {
- /** 最小值 */
- min?: number;
- /** 最大值 */
- max?: number;
- /** 是否包含最小值 */
- includeMin?: boolean;
- /** 是否包含最大值 */
- includeMax?: boolean;
-}
-
-/**
- * 字符集选项
- */
-export interface CharsetOptions {
- /** 是否包含大写字母 */
- uppercase?: boolean;
- /** 是否包含小写字母 */
- lowercase?: boolean;
- /** 是否包含数字 */
- digits?: boolean;
- /** 是否包含特殊字符 */
- special?: boolean;
- /** 自定义字符集 */
- custom?: string;
-}
diff --git a/src/pages/Base64Converter/components/Base64ConverterSection.tsx b/src/pages/Base64Converter/components/Base64ConverterSection.tsx
index 75d19e4..a3ec4ee 100644
--- a/src/pages/Base64Converter/components/Base64ConverterSection.tsx
+++ b/src/pages/Base64Converter/components/Base64ConverterSection.tsx
@@ -40,12 +40,12 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
decodedFileName,
setCustomFileName,
resetAll,
- safeFileSelect,
+ handleFileSelect,
maxFileSizeStr,
} = useBase64Converter({ mode });
const handleDirectionChange = (next: Base64ConvertDirection) => {
- if (!next || next === direction) return;
+ if (next === direction) return;
resetAll();
setDirection(next);
};
@@ -80,7 +80,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
- if (file) safeFileSelect(file);
+ if (file) void handleFileSelect(file);
}}
onClick={() => fileInputRef.current?.click()}
className={cn(
@@ -99,7 +99,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
hidden
onChange={(e) => {
const file = e.target.files?.[0];
- if (file) safeFileSelect(file);
+ if (file) void handleFileSelect(file);
}}
/>
{isLoading ? (
@@ -123,7 +123,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
{formatBytes(info.size)} · {info.type}
- {'点击或拖拽以替换文件'}
+ 点击或拖拽以替换文件
) : (
@@ -158,17 +158,17 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
- {'Base64 编码结果'}
+ Base64 编码结果
@@ -186,14 +186,14 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
- {'原始大小'}:{' '}
+ 原始大小:{' '}
{formatBytes(result.originalBytes)}
|
- {'编码大小'}:{' '}
+ 编码大小:{' '}
{formatBytes(result.outputBytes)}
@@ -206,7 +206,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
className="h-7 rounded-md text-muted-foreground hover:text-destructive text-[11px] gap-1 px-2"
>
- {'清空'}
+ 清空
@@ -215,7 +215,7 @@ export default function Base64ConverterSection({ mode }: Base64ConverterSectionP
) : (
void;
- /** 下载按钮点击回调 */
onDownload: () => void;
- /** 可选的预览内容,ImageMode 用于渲染图片预览 */
children?: React.ReactNode;
}
@@ -41,31 +25,20 @@ export default function DecodeResultPaper({
}: DecodeResultPaperProps) {
return (
- {/* 标题 */}
{title}
- {/* 可选预览内容(ImageMode 的图片) */}
{children}
- {/* 文件信息 */}
-
- {'推断的 MIME 类型'}: {mimeType}
-
-
- {'解码大小'}: {formatBytes(blobSize)}
-
+ 推断的 MIME 类型: {mimeType}
+ 解码大小: {formatBytes(blobSize)}
- {/* 文件名输入 */}
-
+
onFileNameChange(e.target.value)} />
- {/* 下载按钮 */}
);
diff --git a/src/pages/Base64Converter/components/TextMode.tsx b/src/pages/Base64Converter/components/TextMode.tsx
index 560ca66..19125cb 100644
--- a/src/pages/Base64Converter/components/TextMode.tsx
+++ b/src/pages/Base64Converter/components/TextMode.tsx
@@ -8,7 +8,7 @@ interface TextModeProps {
onSwitchToImageMode?: () => void;
}
-export default function TextMode({ onSwitchToImageMode }: TextModeProps = {}) {
+export default function TextMode({ onSwitchToImageMode }: TextModeProps) {
const {
input,
setInput,
diff --git a/src/pages/Base64Converter/useBase64Converter.ts b/src/pages/Base64Converter/useBase64Converter.ts
index b9f8485..89a4baa 100644
--- a/src/pages/Base64Converter/useBase64Converter.ts
+++ b/src/pages/Base64Converter/useBase64Converter.ts
@@ -93,16 +93,7 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
[mode],
);
- const safeFileSelect = useCallback(
- (file: File) => {
- handleFileSelect(file).catch((err) => {
- console.error(`Base64 [${mode}] pipeline crash:`, err);
- });
- },
- [handleFileSelect, mode],
- );
-
- const decodePipeline = useMemo(() => {
+ const decodeResult = useMemo(() => {
const cleanedInput = debouncedDecodeInput.replace(/^data:image\/[a-z+]+;base64,/i, '').trim();
if (!cleanedInput) return { decoded: null, error: null };
@@ -118,8 +109,8 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
}
}, [debouncedDecodeInput]);
- const decoded = decodePipeline.decoded;
- const decodeError = decodePipeline.error;
+ const decoded = decodeResult.decoded;
+ const decodeError = decodeResult.error;
const decodedFileName = useMemo(() => {
if (customFileName) return customFileName;
@@ -144,7 +135,7 @@ export function useBase64Converter({ mode }: UseBase64ConverterProps) {
decodedFileName,
setCustomFileName,
resetAll,
- safeFileSelect,
+ handleFileSelect,
maxFileSizeStr,
};
}
diff --git a/src/pages/Base64Converter/useTextMode.ts b/src/pages/Base64Converter/useTextMode.ts
index a5ac180..73cddcc 100644
--- a/src/pages/Base64Converter/useTextMode.ts
+++ b/src/pages/Base64Converter/useTextMode.ts
@@ -4,7 +4,7 @@ import { useContextMenuData } from '@/utils/useContextMenuData';
const IMAGE_DATA_URI_PATTERN = /^\s*data:image\//i;
-const ERROR_MESSAGE_TO_I18N: Record = {
+const ERROR_MESSAGES: Record = {
'Invalid Base64 string': '无效的 Base64 字符串',
'Input appears to be binary data (e.g. an image). Please use the Image tab instead.':
'检测到二进制数据(如图像),请使用图像选项卡',
@@ -30,7 +30,7 @@ export function useTextMode() {
useContextMenuData({ featureKey: 'base64Converter', onData: handleContextMenuData });
- const conversionPipeline = useMemo(() => {
+ const conversion = useMemo(() => {
const trimmed = debouncedInput.trim();
if (!trimmed) return { output: '', error: null };
@@ -38,31 +38,26 @@ export function useTextMode() {
if (direction === 'encode') {
const result = textToBase64(debouncedInput);
return { output: result.output, error: null };
- } else {
- const decoded = base64ToText(trimmed);
- return { output: decoded, error: null };
}
+ const decoded = base64ToText(trimmed);
+ return { output: decoded, error: null };
} catch (e) {
const message = e instanceof Error ? e.message : '';
- const i18nKey = ERROR_MESSAGE_TO_I18N[message];
return {
output: '',
- error: i18nKey || message || '转换失败',
+ error: ERROR_MESSAGES[message] || message || '转换失败',
};
}
}, [debouncedInput, direction]);
- const output = conversionPipeline.output;
- const error = conversionPipeline.error;
+ const output = conversion.output;
+ const error = conversion.error;
const placeholder =
direction === 'encode' ? '输入需要编码为 Base64 的文本...' : '输入需要解码的 Base64 字符串...';
const outputLabel = direction === 'encode' ? 'Base64 编码结果' : '解码文本结果';
- const showImageHint = useMemo(
- () => direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input),
- [direction, input],
- );
+ const showImageHint = direction === 'decode' && IMAGE_DATA_URI_PATTERN.test(input);
const handleDirectionChange = (value: 'encode' | 'decode') => {
if (value === direction) return;
diff --git a/src/pages/Dashboard/__tests__/index.test.tsx b/src/pages/Dashboard/__tests__/index.test.tsx
index 5810dd4..60d73b2 100644
--- a/src/pages/Dashboard/__tests__/index.test.tsx
+++ b/src/pages/Dashboard/__tests__/index.test.tsx
@@ -1,8 +1,7 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { getFeatureByKey } from '@/config/features';
-import type { DashboardFeatureItem } from '../useDashboard';
+import { resolveDashboardFeatures, type DashboardFeatureItem } from '../dashboardFeatures';
import Index from '../index';
const mockNavigateTo = vi.fn();
@@ -13,11 +12,7 @@ vi.mock('../useDashboard', () => ({
}));
function buildFeatureItem(key: 'timestamp' | 'jwt'): DashboardFeatureItem {
- const feature = getFeatureByKey(key)!;
- return {
- key,
- feature: feature as DashboardFeatureItem['feature'],
- };
+ return resolveDashboardFeatures([key], [key])[0]!;
}
describe('Dashboard 页面', () => {
diff --git a/src/pages/Dashboard/dashboardFeatures.ts b/src/pages/Dashboard/dashboardFeatures.ts
index ee38d29..fee39ad 100644
--- a/src/pages/Dashboard/dashboardFeatures.ts
+++ b/src/pages/Dashboard/dashboardFeatures.ts
@@ -14,10 +14,6 @@ function isDashboardFeature(feature: FeatureConfig): feature is DashboardFeature
return feature.icon != null;
}
-/**
- * 将页面 key 列表解析为 Dashboard 可展示的工具项。
- * 过滤条件:在 visiblePages 中且 feature 配置了 icon。
- */
export function resolveDashboardFeatures(
keys: PageType[],
visiblePages: PageType[],
diff --git a/src/pages/Dashboard/index.tsx b/src/pages/Dashboard/index.tsx
index 72c532e..69acdde 100644
--- a/src/pages/Dashboard/index.tsx
+++ b/src/pages/Dashboard/index.tsx
@@ -9,7 +9,7 @@ export default function Index() {
{showRecent && (
- {'最近使用'}
+ 最近使用
{recentFeatures.map(({ key, feature }) => {
@@ -37,10 +37,10 @@ export default function Index() {
- {'全部工具'}
+ 全部工具
{visibleFeatures.length === 0 ? (
-
{'没有可用的工具'}
+
没有可用的工具
) : (
{
it('应该渲染模式切换按钮', () => {
render();
- // 基本渲染测试:页面含多个模式切换按钮,应至少渲染一个
expect(screen.getAllByRole('button').length).toBeGreaterThan(0);
});
});
diff --git a/src/pages/JsonTools/components/DiffNavigator.tsx b/src/pages/JsonTools/components/DiffNavigator.tsx
index cc5cb25..7392bcc 100644
--- a/src/pages/JsonTools/components/DiffNavigator.tsx
+++ b/src/pages/JsonTools/components/DiffNavigator.tsx
@@ -30,7 +30,7 @@ export default function DiffNavigator({
)}
{...props}
>
- {'无差异'}
+ 无差异
);
}
@@ -43,11 +43,10 @@ export default function DiffNavigator({
)}
{...props}
>
- {/* 上一处差异按钮 */}
@@ -133,7 +133,6 @@ const UnifiedView = ({ node, depth, activePath }: UnifiedViewProps) => {
);
}
- // 容器节点整块渲染处理
if (node.type === 'added') {
return (
{
- translationPrefix: string;
+ mode: string;
convertFunction: ConvertFunction;
}
export default function JsonConvertSection({
- translationPrefix,
+ mode,
convertFunction,
className,
...props
@@ -41,10 +41,8 @@ export default function JsonConvertSection({
const [input, setInput] = useState('');
const [debouncedInput, setDebouncedInput] = useState('');
- const pk = translationPrefix;
- const labels = CONVERT_LABELS[pk] || CONVERT_LABELS.yaml;
+ const labels = CONVERT_LABELS[mode] ?? CONVERT_LABELS.yaml;
- // Debounce input
useEffect(() => {
const handle = setTimeout(() => {
setDebouncedInput(input);
@@ -56,33 +54,25 @@ export default function JsonConvertSection({
return validateJson(debouncedInput);
}, [debouncedInput]);
- const conversionPipeline = useMemo(() => {
+ const { result, runtimeError } = useMemo((): {
+ result: ConvertResult | null;
+ runtimeError: string | null;
+ } => {
const trimmed = debouncedInput.trim();
- if (!trimmed || error) return null;
+ if (!trimmed || error) return { result: null, runtimeError: null };
try {
- return convertFunction(debouncedInput);
+ return { result: convertFunction(debouncedInput), runtimeError: null };
} catch (e) {
- // 捕获可能从外部转换器(如 YAML.stringify)中抛出的底层异常
return {
- isRuntimeError: true,
- errorMessage: e instanceof Error ? e.message : String(e),
+ result: null,
+ runtimeError: e instanceof Error ? e.message : String(e),
};
}
}, [debouncedInput, error, convertFunction]);
- const runtimeError =
- conversionPipeline && 'isRuntimeError' in conversionPipeline
- ? conversionPipeline.errorMessage
- : null;
- const result =
- conversionPipeline && !('isRuntimeError' in conversionPipeline)
- ? (conversionPipeline as ConvertResult)
- : null;
-
return (
- {/* 输入区 */}
setInput('')}
/>
- {/* Result display */}
- {result && result.output ? (
+ {result?.output ? (
(2);
const [sortKeys, setSortKeys] = useState(false);
- // Debounce input
useEffect(() => {
const handle = setTimeout(() => {
setDebouncedInput(input);
@@ -30,40 +29,31 @@ export default function JsonFormatSection() {
return validateJson(debouncedInput);
}, [debouncedInput]);
- // Real-time formatting pipeline
- const formattedPipeline = useMemo(() => {
+ const { result, runtimeError } = useMemo((): {
+ result: JsonFormatResult | null;
+ runtimeError: string | null;
+ } => {
const trimmed = debouncedInput.trim();
- if (!trimmed || error) return null;
+ if (!trimmed || error) return { result: null, runtimeError: null };
try {
const options: JsonFormatOptions = { indentSize, sortKeys };
- return formatJson(debouncedInput, options);
+ return { result: formatJson(debouncedInput, options), runtimeError: null };
} catch (e) {
return {
- isRuntimeError: true,
- errorMessage: e instanceof SyntaxError ? e.message : String(e),
+ result: null,
+ runtimeError: e instanceof SyntaxError ? e.message : String(e),
};
}
}, [debouncedInput, error, indentSize, sortKeys]);
- const runtimeError =
- formattedPipeline && 'isRuntimeError' in formattedPipeline
- ? formattedPipeline.errorMessage
- : null;
- const result =
- formattedPipeline && !('isRuntimeError' in formattedPipeline)
- ? (formattedPipeline as JsonFormatResult)
- : null;
-
return (
- {/* 工具控制栏 */}
- {/* 缩进配置区 */}
- {'缩进'}
+ 缩进
- {/* 键名排序区 */}
setSortKeys(!sortKeys)}
className="flex items-center gap-2 cursor-pointer select-none group py-1"
@@ -91,15 +80,14 @@ export default function JsonFormatSection() {
htmlFor="sort-keys-checkbox"
className="text-xs font-bold text-foreground/80 cursor-pointer tracking-tight group-hover:text-foreground"
>
- {'键名排序'}
+ 键名排序
- {/* 满血版输入终端 */}
setInput('')}
/>
- {/* 格式化结果流面板展示 */}
- {result && result.formatted ? (
+ {result?.formatted ? (
- {'原始大小'}:{' '}
+ 原始大小:{' '}
{formatBytes(originalBytes)}
|
diff --git a/src/pages/JsonTools/components/JsonTree.tsx b/src/pages/JsonTools/components/JsonTree.tsx
index 0641979..e3ee6f2 100644
--- a/src/pages/JsonTools/components/JsonTree.tsx
+++ b/src/pages/JsonTools/components/JsonTree.tsx
@@ -1,5 +1,5 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
-import { ChevronDown, ChevronRight } from 'lucide-react'; // 用正统的矢量箭头平替原生的字符 '▾' '▸'
+import { ChevronDown, ChevronRight } from 'lucide-react';
import type { DiffNode, DiffType } from '../types';
import { cn } from '@/lib/utils';
@@ -82,14 +82,12 @@ const NodeRow = React.memo(
? false
: onActivePath || depth < defaultExpandDepth;
- // 当激活路径精准定位到本行时,平滑滚动至容器中心
useEffect(() => {
if (activePath === node.path) {
rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}, [activePath, node.path]);
- // 占位空行分支:必须加 h-[22px] 锁定绝对等高,防止两侧文本高度塌陷发生高低错位
if (!shouldRenderOnSide(node.type, side)) {
return (
- {/* 大容器开端行 */}
setOverride(expanded ? 'closed' : 'open')}
className={cn(
@@ -135,7 +132,6 @@ const NodeRow = React.memo(
)}
style={indentStyle}
>
- {/* 折叠小箭头:升级为精巧的 Lucide SVG 矢量微动效 */}
{expanded ? (
@@ -164,7 +160,6 @@ const NodeRow = React.memo(
)}
- {/* 容器子节点递归区 */}
{expanded && (
{node.children.map((child, idx) => (
@@ -181,7 +176,6 @@ const NodeRow = React.memo(
)}
- {/* 大容器收尾行 */}
{expanded && (
-
{/* 与上方的折叠键轴线严格对齐 */}
+
{!isRoot && (
{node.key}:
)}
@@ -231,7 +224,6 @@ export default function JsonTree({
...props
}: JsonTreeProps) {
return (
- /* 最外层承载器:统一收拢至标准的 bg-card 与等宽 tabular-nums 控制轴 */
) : pageMode === 'yaml' ? (
-
+
) : pageMode === 'toml' ? (
-
+
) : (
-
+
)}
);
diff --git a/src/pages/JsonTools/useJsonTools.ts b/src/pages/JsonTools/useJsonTools.ts
index 2954413..46623a1 100644
--- a/src/pages/JsonTools/useJsonTools.ts
+++ b/src/pages/JsonTools/useJsonTools.ts
@@ -11,7 +11,6 @@ import type { ConvertFunction, ViewMode } from './types';
export interface UseJsonToolsReturn {
pageMode: JsonToolsPageMode;
setPageMode: (mode: JsonToolsPageMode) => void;
- // Diff mode state
leftInput: string;
rightInput: string;
setLeftInput: (val: string) => void;
@@ -26,7 +25,6 @@ export interface UseJsonToolsReturn {
handlePrev: () => void;
handleNext: () => void;
activePath: string | undefined;
- // Convert functions
yamlConvert: ConvertFunction;
tomlConvert: ConvertFunction;
minifyConvert: ConvertFunction;
@@ -35,13 +33,11 @@ export interface UseJsonToolsReturn {
export function useJsonTools(): UseJsonToolsReturn {
const [pageMode, setPageMode] = useStorageState('jsonTools/pageMode', 'diff', isValidPageMode);
- // Diff inputs
const [leftInput, setLeftInput] = useState('');
const [rightInput, setRightInput] = useState('');
const [debouncedLeft, setDebouncedLeft] = useState('');
const [debouncedRight, setDebouncedRight] = useState('');
- // Debounce
useEffect(() => {
const handle = setTimeout(() => {
setDebouncedLeft(leftInput);
@@ -50,7 +46,6 @@ export function useJsonTools(): UseJsonToolsReturn {
return () => clearTimeout(handle);
}, [leftInput, rightInput]);
- // Parse debounced inputs
const parseState = useMemo(() => {
const invalidMsg = '无效的 JSON 格式';
return {
@@ -65,7 +60,6 @@ export function useJsonTools(): UseJsonToolsReturn {
const [viewMode, setViewMode] = useState
('sideBySide');
const [currentDiffIndex, setCurrentDiffIndex] = useState(0);
- // Real-time diff computation
const diffResult = useMemo(() => {
const { left, right } = parseState;
if (left.error || right.error || debouncedLeft.trim() === '' || debouncedRight.trim() === '') {
@@ -88,7 +82,6 @@ export function useJsonTools(): UseJsonToolsReturn {
const activePath = diffResult && total > 0 ? diffResult.diffPaths[currentDiffIndex] : undefined;
- // Convert functions
const yamlConvert: ConvertFunction = useCallback((text: string) => {
const r = jsonToYaml(text);
return { output: r.output, originalBytes: r.originalBytes, outputBytes: r.outputBytes };
diff --git a/src/pages/QrCode/__tests__/ImageUploader.test.tsx b/src/pages/QrCode/__tests__/ImageUploader.test.tsx
index 5d4807a..33b5df0 100644
--- a/src/pages/QrCode/__tests__/ImageUploader.test.tsx
+++ b/src/pages/QrCode/__tests__/ImageUploader.test.tsx
@@ -2,12 +2,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { act, fireEvent, render, screen } from '@testing-library/react';
import ImageUploader from '../components/ImageUploader';
-// 配置多端一致性常驻桩(WXT 规范)
const storageOnChangedMock = { addListener: vi.fn(), removeListener: vi.fn() };
(globalThis as any).chrome = { storage: { onChanged: storageOnChangedMock } };
(globalThis as any).browser = { storage: { onChanged: storageOnChangedMock } };
-// 模拟 URL API
const mockCreateObjectURL = vi.fn();
const mockRevokeObjectURL = vi.fn();
Object.defineProperty(window.URL, 'createObjectURL', { value: mockCreateObjectURL });
@@ -171,21 +169,4 @@ describe('ImageUploader 组件', () => {
expect(pasteListeners).toHaveLength(0);
});
});
-
- describe('样式测试', () => {
- it('拖拽状态时应应用拖拽样式', () => {
- const { container } = render();
- const dropzone = container.firstChild as HTMLElement;
- expect(dropzone).toBeInTheDocument();
- });
-
- it('有文件时应应用有文件样式', () => {
- const mockFile = new File(['test'], 'test.png', { type: 'image/png' });
- const { container } = render(
- ,
- );
- const dropzone = container.firstChild as HTMLElement;
- expect(dropzone).toBeInTheDocument();
- });
- });
});
diff --git a/src/pages/QrCode/components/GeneratePanel.tsx b/src/pages/QrCode/components/GeneratePanel.tsx
index 7db02f6..f8c9bd3 100644
--- a/src/pages/QrCode/components/GeneratePanel.tsx
+++ b/src/pages/QrCode/components/GeneratePanel.tsx
@@ -6,7 +6,6 @@ import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { cn } from '@/lib/utils';
-/** 文本预览的最大显示字符数 */
const TEXT_PREVIEW_MAX_LENGTH = 80;
export default function GeneratePanel() {
@@ -22,13 +21,11 @@ export default function GeneratePanel() {
const isInputStep = generatorState.step === 'input';
const hasText = generatorState.textToEncode.trim().length > 0;
- /** 截断文本用于预览显示 */
const getTruncatedText = (text: string) => {
if (text.length <= TEXT_PREVIEW_MAX_LENGTH) return text;
return text.slice(0, TEXT_PREVIEW_MAX_LENGTH) + '...';
};
- // 输入态:只显示输入框和生成按钮
if (isInputStep) {
return (
@@ -40,13 +37,13 @@ export default function GeneratePanel() {
>
- {'生成中...'}
+ 生成中...
>
) : (
<>
- {'生成二维码'}
+ 生成二维码
>
)}
@@ -79,18 +76,16 @@ export default function GeneratePanel() {
);
}
- // 预览态:显示文本预览 + 二维码(紧凑布局,适配popup窗口)
return (
- {/* 文本预览区域(固定高度,单行显示) */}
- {'编辑'}
+ 编辑
- {/* 二维码预览区域(自适应剩余空间) */}
void;
- /** 清除文件回调 */
onClearFile: () => void;
- /** 文件预览 URL */
previewUrl: string;
- /** 是否正在拖拽 */
dragging: boolean;
- /** 拖拽状态变更回调 */
onDraggingChange: (dragging: boolean) => void;
}
@@ -27,26 +20,18 @@ const ImageUploader = ({
dragging,
onDraggingChange,
}: ImageUploaderProps) => {
- const fileInputRef = useRef(null);
-
- const handleFileChange = useCallback(
- (file: File) => {
- onFileChange(file);
- },
- [onFileChange],
- );
-
- const handleClearFile = useCallback(() => {
+ const handleClearFile = () => {
if (previewUrl) {
URL.revokeObjectURL(previewUrl);
}
onClearFile();
toast.success('图片已清除');
- }, [previewUrl, onClearFile]);
+ };
const handleInputChange = (e: React.ChangeEvent) => {
- if (e.target.files && e.target.files.length > 0) {
- handleFileChange(e.target.files[0]);
+ const file = e.target.files?.[0];
+ if (file) {
+ onFileChange(file);
}
};
@@ -64,7 +49,7 @@ const ImageUploader = ({
onDraggingChange(false);
const droppedFile = e.dataTransfer.files?.[0];
if (droppedFile) {
- handleFileChange(droppedFile);
+ onFileChange(droppedFile);
}
};
@@ -82,7 +67,6 @@ const ImageUploader = ({
onDrop={handleDrop}
>
{selectedFile.name}
-
{'点击更换图片'}
+
点击更换图片
) : (
<>
@@ -122,10 +106,10 @@ const ImageUploader = ({
className="w-12 h-12 text-muted-foreground mx-auto mb-2"
/>
- {'点击、拖拽或粘贴上传二维码图片'}
+ 点击、拖拽或粘贴上传二维码图片
- {'支持 PNG、JPG、WEBP、Base64 格式'}
+ 支持 PNG、JPG、WEBP、Base64 格式
>
)}
diff --git a/src/pages/QrCode/components/ParsePanel.tsx b/src/pages/QrCode/components/ParsePanel.tsx
index 5c3c6b3..d1426f5 100644
--- a/src/pages/QrCode/components/ParsePanel.tsx
+++ b/src/pages/QrCode/components/ParsePanel.tsx
@@ -13,7 +13,6 @@ export default function ParsePanel() {
const hasFile = parserState.selectedFile !== null;
- // 全局粘贴事件监听
const handlePaste = useCallback(
async (e: ClipboardEvent) => {
const items = e.clipboardData?.items;
@@ -56,7 +55,6 @@ export default function ParsePanel() {
};
}, [handlePaste]);
- // 未上传图片:只显示上传区域
if (!hasFile) {
return (
@@ -72,18 +70,16 @@ export default function ParsePanel() {
);
}
- // 已上传图片:显示图片预览 + 解析结果
return (
- {/* 图片预览区域(小图预览) */}
- {'重新上传'}
+ 重新上传
@@ -95,13 +91,12 @@ export default function ParsePanel() {
{parserState.selectedFile?.name}
{parserState.parsing && (
-
{'解析中...'}
+
解析中...
)}
- {/* 解析结果区域 */}
{
- /** 二维码 Data URL */
qrCodeDataUrl: string;
- /** 下载回调 */
onDownload: () => void;
- /** 复制回调 */
onCopy: () => void;
- /** 自定义占位文本,用于空状态提示 */
placeholderText?: string;
}
@@ -24,7 +19,6 @@ const QrCodePreview = ({
className,
...props
}: QrCodePreviewProps) => {
- // 空状态下的虚线骨架屏
if (!qrCodeDataUrl) {
return (
- {/*
- 二维码容器适配:
- 在暗黑模式下,纯黑白的二维码如果直接暴露在暗色背景下,会导致手机摄像头极难识别。
- 通过裹一层 bg-white 和 p-2,确保黑白对比度绝对安全,同时加入 shadow 增强卡片感。
- */}
- {'下载二维码'}
+ 下载二维码
- {'复制二维码'}
+ 复制二维码
diff --git a/src/pages/QrCode/hooks/useQrCode.ts b/src/pages/QrCode/hooks/useQrCode.ts
index 9c07a6d..4c8e199 100644
--- a/src/pages/QrCode/hooks/useQrCode.ts
+++ b/src/pages/QrCode/hooks/useQrCode.ts
@@ -12,32 +12,26 @@ function isUrl(text: string): boolean {
const trimmed = text.trim();
if (!trimmed) return false;
- // 检查是否以 http:// 或 https:// 开头
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
return true;
}
- // 检查是否为域名格式(包含.且不以特殊字符开头)
const domainPattern = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?(\.[a-zA-Z]{2,})+([/:].*)?$/;
return domainPattern.test(trimmed);
}
-/** 检测URL是否为图片格式 */
function isImageUrl(url: string): boolean {
const trimmedUrl = url.trim().toLowerCase();
- // 检查是否为 data:image 格式
if (trimmedUrl.startsWith('data:image/')) {
return true;
}
- // 检查是否包含图片扩展名
const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico'];
if (imageExtensions.some((ext) => trimmedUrl.includes(ext))) {
return true;
}
- // 检查URL路径中是否包含图片相关关键词
const imageKeywords = ['/image/', '/img/', '/photo/', '/pic/', '/upload/'];
if (imageKeywords.some((keyword) => trimmedUrl.includes(keyword))) {
return true;
@@ -46,7 +40,6 @@ function isImageUrl(url: string): boolean {
return false;
}
-/** 生成二维码的核心逻辑 */
function generateQrCodeDataUrl(text: string): string {
const trimmedText = text.trim();
if (!trimmedText) return '';
@@ -241,7 +234,17 @@ export function useQrCode(): QrCodeContextValue {
const qrCodeDataUrl = generateQrCodeDataUrl(payload);
- if (!qrCodeDataUrl) {
+ if (qrCodeDataUrl) {
+ setGeneratorState((prev) => ({
+ ...prev,
+ step: 'preview',
+ textToEncode: payload,
+ savedText: payload.trim(),
+ qrCodeDataUrl,
+ generating: false,
+ inputError: '',
+ }));
+ } else {
setGeneratorState((prev) => ({
...prev,
step: 'input',
@@ -328,7 +331,7 @@ export function useQrCode(): QrCodeContextValue {
});
parseQrCode(file).catch((err) => {
- console.error('Parser standalone task thread exploded:', err);
+ console.error('解析二维码失败:', err);
});
},
[parseQrCode],
diff --git a/src/pages/RightClickRestorer/constants.ts b/src/pages/RightClickRestorer/constants.ts
index 5474777..165900b 100644
--- a/src/pages/RightClickRestorer/constants.ts
+++ b/src/pages/RightClickRestorer/constants.ts
@@ -6,10 +6,6 @@ export type RestorerStatus = 'unsupported' | 'locked' | 'unlocked';
export const CARD_CLASS =
'w-full p-4 rounded-xl border border-border bg-card text-card-foreground shadow-sm overflow-hidden space-y-3';
-export const LOADING_TEXT = '正在加载...';
-
-export const DOMAIN_LABEL = '当前域名';
-
interface StatusConfig {
badgeVariant: 'default' | 'secondary' | 'destructive';
badgeClassName: string;
@@ -50,7 +46,7 @@ export const STATUS_CONFIG: Record = {
badgeClassName: 'gap-1 bg-green-600 hover:bg-green-700 shrink-0',
badgeIcon: ShieldCheck,
badgeLabel: '已解锁',
- description: '点击下方按钮,为当前网站临时解锁右键菜单。刷新页面后需要重新解锁。',
+ description: '当前网站右键菜单已临时解锁。刷新页面后需要重新解锁。',
buttonIcon: MousePointerClick,
buttonLabel: '右键已解锁',
buttonDisabled: true,
diff --git a/src/pages/RightClickRestorer/index.tsx b/src/pages/RightClickRestorer/index.tsx
index 97121fc..42b8143 100644
--- a/src/pages/RightClickRestorer/index.tsx
+++ b/src/pages/RightClickRestorer/index.tsx
@@ -2,7 +2,7 @@ import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Badge } from '@/components/ui/badge';
import { Loader2 } from 'lucide-react';
-import { CARD_CLASS, DOMAIN_LABEL, LOADING_TEXT, STATUS_CONFIG } from './constants';
+import { CARD_CLASS, STATUS_CONFIG } from './constants';
import { useRightClickRestorer } from './useRightClickRestorer';
export default function Index() {
@@ -13,7 +13,7 @@ export default function Index() {
- {LOADING_TEXT}
+ 正在加载...
);
@@ -24,9 +24,9 @@ export default function Index() {
const ButtonIcon = config.buttonIcon;
return (
-
+
-
+
{domain || '—'}
@@ -42,7 +42,7 @@ export default function Index() {
className="w-full gap-2"
disabled={config.buttonDisabled}
variant={config.buttonVariant}
- onClick={config.buttonDisabled ? undefined : () => void unlock()}
+ onClick={() => void unlock()}
>
{config.buttonLabel}
diff --git a/src/pages/RightClickRestorer/useRightClickRestorer.ts b/src/pages/RightClickRestorer/useRightClickRestorer.ts
index b209126..0cc99dd 100644
--- a/src/pages/RightClickRestorer/useRightClickRestorer.ts
+++ b/src/pages/RightClickRestorer/useRightClickRestorer.ts
@@ -3,11 +3,6 @@ import { MessageAction, sendMessageToContent } from '@/utils/messages';
import { isUnsupportedPageUrl } from '@/utils/restrictedUrls';
import type { RestorerStatus } from './constants';
-function deriveStatus(isUnsupported: boolean, isUnlocked: boolean): RestorerStatus {
- if (isUnsupported) return 'unsupported';
- return isUnlocked ? 'unlocked' : 'locked';
-}
-
export interface UseRightClickRestorerReturn {
domain: string;
isLoading: boolean;
@@ -16,10 +11,9 @@ export interface UseRightClickRestorerReturn {
}
export function useRightClickRestorer(): UseRightClickRestorerReturn {
- const [domain, setDomain] = useState('');
- const [isLoading, setIsLoading] = useState(true);
- const [isUnlocked, setIsUnlocked] = useState(false);
- const [isUnsupported, setIsUnsupported] = useState(false);
+ const [domain, setDomain] = useState('');
+ const [isLoading, setIsLoading] = useState(true);
+ const [status, setStatus] = useState('locked');
useEffect(() => {
const load = async () => {
@@ -30,13 +24,13 @@ export function useRightClickRestorer(): UseRightClickRestorerReturn {
setDomain(url ? new URL(url).hostname : '');
if (isUnsupportedPageUrl(url)) {
- setIsUnsupported(true);
+ setStatus('unsupported');
return;
}
const response = await sendMessageToContent(MessageAction.QUERY_RIGHT_CLICK_STATUS);
if (response?.success) {
- setIsUnlocked(response.restored);
+ setStatus(response.restored ? 'unlocked' : 'locked');
}
} catch (err) {
console.error('[RightClickRestorer] Failed to load state:', err);
@@ -49,12 +43,12 @@ export function useRightClickRestorer(): UseRightClickRestorerReturn {
}, []);
async function unlock() {
- if (isUnsupported) return;
+ if (status === 'unsupported') return;
try {
const response = await sendMessageToContent(MessageAction.RESTORE_RIGHT_CLICK);
if (response?.success) {
- setIsUnlocked(response.restored);
+ setStatus(response.restored ? 'unlocked' : 'locked');
}
} catch (err) {
console.error('[RightClickRestorer] Failed to unlock:', err);
@@ -64,7 +58,7 @@ export function useRightClickRestorer(): UseRightClickRestorerReturn {
return {
domain,
isLoading,
- status: deriveStatus(isUnsupported, isUnlocked),
+ status,
unlock,
};
}
diff --git a/src/pages/StorageCleaner/__tests__/index.test.tsx b/src/pages/StorageCleaner/__tests__/index.test.tsx
index e73564d..bd348fc 100644
--- a/src/pages/StorageCleaner/__tests__/index.test.tsx
+++ b/src/pages/StorageCleaner/__tests__/index.test.tsx
@@ -5,7 +5,6 @@ import Index from '../index';
import { clearStorage, getCookieSize, getCurrentTab } from '@/utils/storageCleaner';
import { toast } from 'sonner';
-// Mock the chrome APIs
vi.mock('@/utils/chromeStorage', () => ({
storageUtil: {
get: vi.fn().mockResolvedValue(null),
diff --git a/src/pages/StorageCleaner/components/AutoRefreshToggle.tsx b/src/pages/StorageCleaner/components/AutoRefreshToggle.tsx
index 0926cd7..3b57b4f 100644
--- a/src/pages/StorageCleaner/components/AutoRefreshToggle.tsx
+++ b/src/pages/StorageCleaner/components/AutoRefreshToggle.tsx
@@ -1,18 +1,3 @@
-/**
- * 自动刷新开关行
- *
- * 用于 StorageCleaner 页面的操作区域内(非独立卡片),
- * 控制清理后是否自动刷新页面。左侧为标签文本,右侧为 shadcn/ui Switch 开关。
- *
- * @example
- * ```tsx
- * setReloadAfterClean(checked)}
- * />
- * ```
- */
-
import React from 'react';
import { cn } from '@/lib/utils';
import { Switch } from '@/components/ui/switch';
@@ -22,20 +7,10 @@ export interface AutoRefreshToggleProps extends Omit<
React.HTMLAttributes,
'onChange'
> {
- /** 开关的当前状态(受控) */
reloadAfterClean: boolean;
- /** 状态变化时的回调函数 */
onChange: (checked: boolean) => void;
}
-/**
- * 自动刷新开关
- *
- * @param reloadAfterClean - 当前开关状态
- * @param onChange - 状态变化回调,接收新的布尔值
- * @param className - 额外的 CSS 类名,用于覆盖或扩展样式
- * @param props - 透传给外层 div 的其他 HTML 属性
- */
export default function AutoRefreshToggle({
reloadAfterClean,
onChange,
@@ -54,7 +29,7 @@ export default function AutoRefreshToggle({
htmlFor="auto-refresh-switch"
className="text-xs font-bold text-muted-foreground/90 cursor-pointer select-none tracking-wide uppercase"
>
- {'清理后自动刷新页面'}
+ 清理后自动刷新页面
diff --git a/src/pages/StorageCleaner/components/ErrorDisplay.tsx b/src/pages/StorageCleaner/components/ErrorDisplay.tsx
index 5f3728d..2879711 100644
--- a/src/pages/StorageCleaner/components/ErrorDisplay.tsx
+++ b/src/pages/StorageCleaner/components/ErrorDisplay.tsx
@@ -24,7 +24,7 @@ export default function ErrorDisplay({ error, className, ...props }: ErrorDispla
- {'存储清理功能仅适用于标准网页'}
+ 存储清理功能仅适用于标准网页
diff --git a/src/pages/StorageCleaner/components/OptionItem.tsx b/src/pages/StorageCleaner/components/OptionItem.tsx
index 745c506..7eefb08 100644
--- a/src/pages/StorageCleaner/components/OptionItem.tsx
+++ b/src/pages/StorageCleaner/components/OptionItem.tsx
@@ -2,11 +2,11 @@ import React from 'react';
import { formatBytes } from '@/utils/format';
import { cn } from '@/lib/utils';
import type { StorageSizeInfo } from '../useStorageCleaner';
-import { OPTION_LABELS } from '../constants';
+import { CLEAN_OPTION_KEYS, OPTION_LABELS } from '../constants';
import { Checkbox } from '@/components/ui/checkbox';
interface OptionItemProps extends React.HTMLAttributes
{
- labelKey: string;
+ labelKey: (typeof CLEAN_OPTION_KEYS)[number];
checked: boolean;
sizeInfo?: StorageSizeInfo;
onChange: () => void;
@@ -22,8 +22,7 @@ export default function OptionItem({
}: OptionItemProps) {
const sizeValue = sizeInfo?.value;
const isCount = sizeInfo?.displayType === 'count';
- const label =
- labelKey in OPTION_LABELS ? OPTION_LABELS[labelKey as keyof typeof OPTION_LABELS] : labelKey;
+ const label = OPTION_LABELS[labelKey];
return (
) : (
- {'无数据'}
+ 无数据
)}
diff --git a/src/pages/StorageCleaner/components/StorageCleanerConfirm.tsx b/src/pages/StorageCleaner/components/StorageCleanerConfirm.tsx
index e619e80..116bcc3 100644
--- a/src/pages/StorageCleaner/components/StorageCleanerConfirm.tsx
+++ b/src/pages/StorageCleaner/components/StorageCleanerConfirm.tsx
@@ -10,7 +10,7 @@ import {
import { Badge } from '@/components/ui/badge';
import { AlertTriangle } from 'lucide-react';
import type { StorageCleanerOptions } from '@/types/storage';
-import { OPTION_LABELS } from '../constants';
+import { OPTION_LABELS, CLEAN_OPTION_KEYS } from '../constants';
import { cn } from '@/lib/utils';
export interface StorageCleanerConfirmProps {
@@ -26,11 +26,9 @@ export function StorageCleanerConfirm({
onConfirm,
options,
}: StorageCleanerConfirmProps) {
- const selectedOptions = Object.entries(options)
- .filter(([_, value]) => value)
- .map(([key]) =>
- key in OPTION_LABELS ? OPTION_LABELS[key as keyof typeof OPTION_LABELS] : key,
- );
+ const selectedOptions = CLEAN_OPTION_KEYS.filter((key) => options[key]).map(
+ (key) => OPTION_LABELS[key],
+ );
return (
);
@@ -73,7 +73,7 @@ export default function Index() {
{loading ? (
<>
- {'正在清理...'}
+ 正在清理...
>
) : (
'立即清理'
diff --git a/src/pages/StorageCleaner/useStorageCleaner.ts b/src/pages/StorageCleaner/useStorageCleaner.ts
index a3588a3..5dbe66e 100644
--- a/src/pages/StorageCleaner/useStorageCleaner.ts
+++ b/src/pages/StorageCleaner/useStorageCleaner.ts
@@ -117,7 +117,6 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
};
}, []);
- // 核心数据拉取链条
const loadInfo = useCallback(async () => {
const currentRequestId = ++requestIdRef.current;
setIsRefreshingSizes(true);
@@ -189,7 +188,6 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
}, 300);
}, []);
- // 监听浏览器标签行为
useEffect(() => {
loadInfoRef.current().catch(console.error);
@@ -216,15 +214,12 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
if (storageTimerRef.current) clearTimeout(storageTimerRef.current);
storageTimerRef.current = setTimeout(async () => {
- try {
- await storageUtil.set('storageCleaner/preferences', {
+ await storageUtil
+ .set('storageCleaner/preferences', {
reloadAfterClean,
selectedTypes: options,
- });
- } catch (err) {
- console.error('Failed to save storage cleaner preferences:', err);
- toast.warning('偏好保存失败,本次设置可能不会保留');
- }
+ })
+ .catch(console.error);
}, 500);
}, [options, reloadAfterClean, isInitializing]);
@@ -251,7 +246,7 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
if (loadingRef.current) return;
const tab = await getCurrentTab();
- if (!tab || tab.id === undefined || !tab.url) {
+ if (!tab || !tab.id || !tab.url) {
toast.warning('无法获取当前标签页');
return;
}
@@ -286,7 +281,6 @@ export function useStorageCleaner(): UseStorageCleanerReturn {
toast.error(`清理失败: ${String(err)}`);
} finally {
setLoading(false);
- setShowConfirm(false);
}
}, [options, reloadAfterClean, loadInfo]);
diff --git a/src/pages/TestDataGenerator/components/DataPreview.tsx b/src/pages/TestDataGenerator/components/DataPreview.tsx
index 5c09da4..151c520 100644
--- a/src/pages/TestDataGenerator/components/DataPreview.tsx
+++ b/src/pages/TestDataGenerator/components/DataPreview.tsx
@@ -75,7 +75,7 @@ export default function DataPreview({ fields }: DataPreviewProps) {
return (
-
{'配置字段后点击「生成数据」按钮'}
+
配置字段后点击「生成数据」按钮
);
}
@@ -87,10 +87,10 @@ export default function DataPreview({ fields }: DataPreviewProps) {
-
{'示例数据'}
+
示例数据
- {Object.keys(sampleData).length} {'字段'}
+ {Object.keys(sampleData).length} 字段
diff --git a/src/pages/TestDataGenerator/components/ExportPanel.tsx b/src/pages/TestDataGenerator/components/ExportPanel.tsx
index 29f2591..483e6c9 100644
--- a/src/pages/TestDataGenerator/components/ExportPanel.tsx
+++ b/src/pages/TestDataGenerator/components/ExportPanel.tsx
@@ -50,27 +50,27 @@ export default function ExportPanel({ result }: ExportPanelProps) {
return (
-
{'导出数据'}
+
导出数据
- {'复制 JSON'}
+ 复制 JSON
- {'复制 CSV'}
+ 复制 CSV
- {'下载 JSON'}
+ 下载 JSON
- {'下载 CSV'}
+ 下载 CSV
diff --git a/src/pages/TestDataGenerator/components/FieldEditor.tsx b/src/pages/TestDataGenerator/components/FieldEditor.tsx
index a72af42..d23fab9 100644
--- a/src/pages/TestDataGenerator/components/FieldEditor.tsx
+++ b/src/pages/TestDataGenerator/components/FieldEditor.tsx
@@ -102,13 +102,13 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
-
+
{field.name.length}/20
handleNameChange(e.target.value)}
- placeholder={'请输入字段名称'}
+ placeholder="请输入字段名称"
maxLength={20}
className={`h-9 ${nameError ? 'border-destructive' : ''}`}
/>
@@ -117,7 +117,7 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
-
+
{(field.description || '').length}/50
@@ -125,7 +125,7 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
handleDescriptionChange(e.target.value)}
- placeholder={'可选,添加字段说明'}
+ placeholder="可选,添加字段说明"
maxLength={50}
className="h-9"
/>
@@ -134,14 +134,14 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
-
+
{!field.required && (
- {'空值率'}
+ 空值率
{field.nullRate}%
@@ -180,18 +180,18 @@ export default function FieldEditor({ field, onChange, allFieldNames = [] }: Fie
-
+
-
+
{generator && (
-
+
- {/* 第一行:字段名 + 标签 */}
{field.name}
@@ -41,11 +40,10 @@ export default function FieldItem({ field, onClick }: FieldItemProps) {
)}
{field.unique && (
- {'唯一'}
+ 唯一
)}
- {/* 第二行:描述 */}
{field.description && (
{field.description}
diff --git a/src/pages/TestDataGenerator/components/FieldList.tsx b/src/pages/TestDataGenerator/components/FieldList.tsx
index 3b5fa65..1c4870a 100644
--- a/src/pages/TestDataGenerator/components/FieldList.tsx
+++ b/src/pages/TestDataGenerator/components/FieldList.tsx
@@ -86,7 +86,6 @@ function SortableFieldItem({
}`}
>
- {/* 拖拽手柄 */}
- {'字段'} ({fields.length}/{MAX_FIELDS})
+ 字段 ({fields.length}/{MAX_FIELDS})
{editingRule ? (
@@ -278,10 +277,10 @@ export default function FieldList({
onClick={handleUpdateRule}
disabled={fields.length === 0}
className="h-8 gap-1.5 px-2.5"
- title={`${'编辑中'}: ${editingRule.name}`}
+ title={`编辑中: ${editingRule.name}`}
>
- {'更新规则'}
+ 更新规则
- {'另存为'}
+ 另存为
>
) : (
@@ -302,7 +301,7 @@ export default function FieldList({
className="h-8 gap-1.5 px-2.5"
>
- {'保存规则'}
+ 保存规则
)}
- {'添加字段'}
+ 添加字段
@@ -322,8 +321,8 @@ export default function FieldList({
{fields.length === 0 ? (
-
{'暂无字段'}
-
{'点击上方按钮添加第一个字段'}
+
暂无字段
+
点击上方按钮添加第一个字段
) : (
- {/* 保存规则对话框 */}
setShowSaveDialog(false)}>
- {'取消'}
+ 取消
handleSave()} disabled={!ruleName.trim()}>
- {'确认'}
+ 确认
- {/* 覆盖确认对话框 */}
diff --git a/src/pages/TestDataGenerator/components/GenerateOptions.tsx b/src/pages/TestDataGenerator/components/GenerateOptions.tsx
index fdd62dd..105e48a 100644
--- a/src/pages/TestDataGenerator/components/GenerateOptions.tsx
+++ b/src/pages/TestDataGenerator/components/GenerateOptions.tsx
@@ -28,9 +28,8 @@ export default function GenerateOptions({
}: GenerateOptionsProps) {
return (
- {/* 生成数量 */}
-
+
{COUNT_PRESETS.map((preset) => (
- {/* 数据格式 */}
-
+
{FORMAT_OPTIONS.map((option) => (
{'此生成器无可配置参数'};
+ return 此生成器无可配置参数
;
}
return (
@@ -108,7 +108,7 @@ export default function GeneratorConfig({ generator, params, onChange }: Generat
.filter(Boolean),
)
}
- placeholder={'用逗号分隔多个值'}
+ placeholder="用逗号分隔多个值"
className="h-9"
/>
)}
diff --git a/src/pages/TestDataGenerator/components/GeneratorSelector.tsx b/src/pages/TestDataGenerator/components/GeneratorSelector.tsx
index 72532b0..6d20326 100644
--- a/src/pages/TestDataGenerator/components/GeneratorSelector.tsx
+++ b/src/pages/TestDataGenerator/components/GeneratorSelector.tsx
@@ -30,18 +30,16 @@ export default function GeneratorSelector({ selectedId, onChange }: GeneratorSel
return (
- {/* 搜索框 */}
setSearchQuery(e.target.value)}
- placeholder={'搜索生成器...'}
+ placeholder="搜索生成器..."
className="pl-9 h-9"
/>
- {/* 分类标签 */}
{!searchQuery && (
{generatorCategories.map((category) => {
@@ -64,7 +62,6 @@ export default function GeneratorSelector({ selectedId, onChange }: GeneratorSel
)}
- {/* 生成器列表 */}
{generators.map((generator) => (
- {/* 状态标题 */}
{getStatusIcon()}
{getStatusText()}
- {/* 统计信息 */}
{result.stats && (
@@ -49,7 +47,7 @@ export default function ResultPanel({ result }: ResultPanelProps) {
{result.stats.total.toLocaleString()}
- {'总条数'}
+ 总条数
@@ -57,7 +55,7 @@ export default function ResultPanel({ result }: ResultPanelProps) {
{result.stats.success.toLocaleString()}
- {'成功'}
+ 成功
@@ -65,17 +63,16 @@ export default function ResultPanel({ result }: ResultPanelProps) {
{(result.stats.duration / 1000).toFixed(2)}s
- {'耗时'}
+ 耗时
)}
- {/* 警告信息 */}
{result.warnings && result.warnings.length > 0 && (
{result.warnings.slice(0, 5).map((warning, index) => (
@@ -92,7 +89,6 @@ export default function ResultPanel({ result }: ResultPanelProps) {
)}
- {/* 错误信息 */}
{result.error && (
diff --git a/src/pages/TestDataGenerator/components/RuleManager.tsx b/src/pages/TestDataGenerator/components/RuleManager.tsx
index b7ec20a..b65c89b 100644
--- a/src/pages/TestDataGenerator/components/RuleManager.tsx
+++ b/src/pages/TestDataGenerator/components/RuleManager.tsx
@@ -165,7 +165,6 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
return (
- {/* 删除确认对话框 */}
- {/* 工具栏 */}
)}
- {'导入'}
+ 导入
)}
- {'导出数据'}
+ 导出数据
- {/* 搜索框 */}
setSearchQuery(e.target.value.slice(0, 20))}
- placeholder={'搜索规则...'}
+ placeholder="搜索规则..."
className="pl-9 pr-24 h-9"
maxLength={20}
/>
@@ -234,7 +231,6 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
- {/* 规则列表 */}
{filteredRules.length === 0 ? (
@@ -252,9 +248,7 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
{rule.name}
-
- {rule.fields.length} {'字段'}
-
+ {rule.fields.length} 字段
{rule.description && (
@@ -276,7 +270,7 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
size="icon"
className="h-7 w-7"
onClick={() => handleLoad(rule)}
- title={'加载'}
+ title="加载"
>
@@ -285,7 +279,7 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
size="icon"
className="h-7 w-7"
onClick={() => handleEdit(rule)}
- title={'编辑'}
+ title="编辑"
>
@@ -294,7 +288,7 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
size="icon"
className="h-7 w-7"
onClick={() => handleDuplicate(rule.id)}
- title={'复制'}
+ title="复制"
>
@@ -303,7 +297,7 @@ export default function RuleManager({ onLoad, onEdit, onRulesChanged }: RuleMana
size="icon"
className="h-7 w-7 text-destructive hover:text-destructive"
onClick={() => setRuleToDelete(rule)}
- title={'删除'}
+ title="删除"
>
diff --git a/src/pages/TestDataGenerator/index.tsx b/src/pages/TestDataGenerator/index.tsx
index fd0700e..782edc7 100644
--- a/src/pages/TestDataGenerator/index.tsx
+++ b/src/pages/TestDataGenerator/index.tsx
@@ -1,21 +1,10 @@
-/**
- * 测试数据生成器主页面
- */
-
import { useState, useCallback, useRef, useEffect } from 'react';
import { Settings, Database, Tag } from 'lucide-react';
import { cn } from '@/lib/utils';
-import { useGenerator } from './hooks/useGenerator';
-import type { FieldConfig, GenerateResult, DataRule } from '@/types/testDataGenerator';
import { Dialog, DialogContent } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { toast } from 'sonner';
-
-// 生成唯一 ID 的辅助函数
-function generateId(): string {
- return `field_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
-}
-
+import { useGenerator } from './hooks/useGenerator';
import FieldList, { MAX_FIELDS } from './components/FieldList';
import FieldEditor from './components/FieldEditor';
import GenerateOptions from './components/GenerateOptions';
@@ -24,39 +13,38 @@ import DataPreview from './components/DataPreview';
import ResultPanel from './components/ResultPanel';
import ExportPanel from './components/ExportPanel';
import RuleManager from './components/RuleManager';
+import type { FieldConfig, GenerateResult, DataRule } from '@/types/testDataGenerator';
+
+function generateId(): string {
+ return `field_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
+}
type TabType = 'fields' | 'rules';
export default function TestDataGeneratorPage() {
const { isGenerating, progress, result, error, generate, cancel, clearResult } = useGenerator();
- // 字段配置
const [fields, setFields] = useState([]);
const [selectedIndex, setSelectedIndex] = useState(null);
const [isEditorOpen, setIsEditorOpen] = useState(false);
const [editingRule, setEditingRule] = useState(null);
- // 生成选项
const [count, setCount] = useState(100);
const [format, setFormat] = useState<'json' | 'csv'>('json');
- // 当前标签页
const [activeTab, setActiveTab] = useState('fields');
- // 生成完成时显示 toast 提示(避免重复触发)
const lastToastResultRef = useRef(null);
- // 生成完成时显示 toast 提示(避免重复触发)
useEffect(() => {
if (result?.success && result.stats && result !== lastToastResultRef.current) {
lastToastResultRef.current = result;
toast.success('生成完成', {
- description: `${result.stats.total} ${'条数据'}`,
+ description: `${result.stats.total} 条数据`,
});
}
}, [result]);
- // 添加新字段
const handleAddField = useCallback(() => {
setFields((prev) => {
if (prev.length >= MAX_FIELDS) return prev;
@@ -74,7 +62,6 @@ export default function TestDataGeneratorPage() {
});
}, []);
- // 更新字段
const handleUpdateField = useCallback((index: number, field: FieldConfig) => {
setFields((prev) => {
const newFields = [...prev];
@@ -83,7 +70,6 @@ export default function TestDataGeneratorPage() {
});
}, []);
- // 删除字段
const handleRemoveField = useCallback((index: number) => {
setFields((prev) => prev.filter((_, i) => i !== index));
setSelectedIndex((prev) => {
@@ -93,7 +79,6 @@ export default function TestDataGeneratorPage() {
});
}, []);
- // 拖拽排序
const handleReorder = useCallback((oldIndex: number, newIndex: number) => {
setFields((prev) => {
const newFields = [...prev];
@@ -111,7 +96,6 @@ export default function TestDataGeneratorPage() {
});
}, []);
- // 加载规则
const handleLoadRule = useCallback(
(loadedFields: FieldConfig[]) => {
setFields(loadedFields);
@@ -122,7 +106,6 @@ export default function TestDataGeneratorPage() {
[clearResult],
);
- // 编辑规则
const handleEditRule = useCallback(
(rule: DataRule) => {
setFields(rule.fields);
@@ -135,21 +118,17 @@ export default function TestDataGeneratorPage() {
[clearResult],
);
- // 保存规则成功后清除编辑状态
const handleRuleSaved = useCallback(() => {
setEditingRule(null);
}, []);
- // 开始生成
const handleGenerate = useCallback(() => {
if (fields.length === 0) return;
generate(fields, count, format === 'csv');
}, [fields, count, format, generate]);
- // 获取选中的字段(-1 或 null 表示未选中)
const selectedField = selectedIndex !== null && selectedIndex >= 0 ? fields[selectedIndex] : null;
- // 打开字段编辑器
const handleOpenEditor = useCallback((index: number) => {
setSelectedIndex(index);
setIsEditorOpen(true);
@@ -158,11 +137,8 @@ export default function TestDataGeneratorPage() {
return (
- {/* 主要内容区域 - 左右分栏 */}
- {/* 左侧面板 - 字段配置 */}
- {/* 标签页切换 */}
setActiveTab('fields')}
@@ -174,7 +150,7 @@ export default function TestDataGeneratorPage() {
)}
>
- {'字段配置'}
+ 字段配置
setActiveTab('rules')}
@@ -186,11 +162,10 @@ export default function TestDataGeneratorPage() {
)}
>
- {'规则管理'}
+ 规则管理
- {/* 字段配置标签页 */}
{activeTab === 'fields' && (
)}
- {/* 规则管理标签页 */}
{activeTab === 'rules' && (
)}
- {/* 生成选项 */}
- {/* 生成按钮 */}
- {/* 右侧面板 - 数据预览和结果 */}
- {/* 结果状态 */}
- {/* 仅在失败或有警告时显示结果面板 */}
{((result && !result.success) ||
(result?.warnings && result.warnings.length > 0) ||
error) && (
@@ -253,18 +222,16 @@ export default function TestDataGeneratorPage() {
)}
- {/* 数据预览 */}
- {/* 导出面板 */}
{result?.data && result.data.length > 0 && (
@@ -274,7 +241,6 @@ export default function TestDataGeneratorPage() {
- {/* 字段编辑弹窗 */}
);
}
diff --git a/src/pages/Timestamp/components/ResultView.tsx b/src/pages/Timestamp/components/ResultView.tsx
index 06d70ba..464601a 100644
--- a/src/pages/Timestamp/components/ResultView.tsx
+++ b/src/pages/Timestamp/components/ResultView.tsx
@@ -14,15 +14,16 @@ export default function ResultView({
className,
...props
}: ResultViewProps) {
+ if (!result && !showEmptyPlaceholder) return null;
+
if (!result) {
- if (!showEmptyPlaceholder) return null;
return (
- {'请输入并点击转换'}
+ 请输入并点击转换
);
}
@@ -30,7 +31,7 @@ export default function ResultView({
return (
- {'转换结果'}
+ 转换结果
@@ -39,7 +40,7 @@ export default function ResultView({
{
- children: React.ReactNode;
-}
-
-export default function ToolCard({ children, className, ...props }: ToolCardProps) {
- return (
-
- {children}
-
- );
-}
diff --git a/src/pages/Timestamp/index.tsx b/src/pages/Timestamp/index.tsx
index 31f699d..4bf7d78 100644
--- a/src/pages/Timestamp/index.tsx
+++ b/src/pages/Timestamp/index.tsx
@@ -1,7 +1,8 @@
+import { cn } from '@/lib/utils';
import LiveClock from './components/LiveClock';
import ConverterForm from './components/ConverterForm';
import ResultView from './components/ResultView';
-import ToolCard from './components/ToolCard';
+import { CARD_CLASS } from './constants';
import { useTimestampConverter } from './useTimestampConverter';
export default function Index() {
@@ -14,9 +15,9 @@ export default function Index() {
);
diff --git a/src/pages/Timestamp/useTimestampConverter.ts b/src/pages/Timestamp/useTimestampConverter.ts
index b8bda29..400258d 100644
--- a/src/pages/Timestamp/useTimestampConverter.ts
+++ b/src/pages/Timestamp/useTimestampConverter.ts
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import dayjs from '@/utils/dayjs';
import type { UnitType, ZoneType, ModeType } from './constants';
-import { DATE_FORMAT, INPUT_PLACEHOLDERS, msToUnit, dayjsFromTimestamp } from './constants';
+import { DATE_FORMAT, msToUnit, dayjsFromTimestamp } from './constants';
import { useContextMenuData } from '@/utils/useContextMenuData';
export interface UseTimestampConverterReturn {
@@ -11,7 +11,6 @@ export interface UseTimestampConverterReturn {
zone: ZoneType;
result: string;
error: string;
- inputPlaceholder: string;
setMode: (mode: ModeType) => void;
setInput: (value: string) => void;
@@ -101,7 +100,6 @@ export function useTimestampConverter(): UseTimestampConverterReturn {
zone,
result,
error,
- inputPlaceholder: INPUT_PLACEHOLDERS[mode],
setMode: handleSetMode,
setInput,
setUnit,
diff --git a/src/providers/RouterProvider.tsx b/src/providers/RouterProvider.tsx
index d72bf84..8790f09 100644
--- a/src/providers/RouterProvider.tsx
+++ b/src/providers/RouterProvider.tsx
@@ -108,9 +108,6 @@ export function RouterProvider({
const hasUserNavigatedRef = useRef(false);
const canPersistRef = useRef(false);
- /**
- * 从异步存储中安全溯源初始数据
- */
const loadInitialData = useCallback(async () => {
try {
const savedRoute = await storageUtil.get(syncKey, defaultRoute);
@@ -149,7 +146,6 @@ export function RouterProvider({
.then(() => {
if (cancelled) return;
- // Check URL params for context menu data
if (typeof window !== 'undefined') {
const params = new URLSearchParams(window.location.search);
const feature = params.get('feature') as PageType | null;
@@ -169,7 +165,6 @@ export function RouterProvider({
}
}
- // Check storage for pending context menu data
storageUtil
.get('contextMenu/pendingData')
.then((pendingData) => {
@@ -192,7 +187,7 @@ export function RouterProvider({
useEffect(() => {
if (isLoaded && canPersistRef.current && syncRoute) {
- void storageUtil.set(syncKey, currentPage as PageType).catch(console.error);
+ void storageUtil.set(syncKey, currentPage).catch(console.error);
try {
localStorage.setItem(`snapshot/${syncKey}`, JSON.stringify(currentPage));
} catch (err) {
@@ -324,7 +319,7 @@ export function RouterProvider({
export function useRouter() {
const context = useContext(RouterContext);
if (!context) {
- throw new Error('useRouter must be used within a valid RouterProvider context wrapper');
+ throw new Error('useRouter must be used within RouterProvider');
}
return context;
}
diff --git a/src/providers/ThemeModeProvider.tsx b/src/providers/ThemeModeProvider.tsx
index f41921a..4ee6d32 100644
--- a/src/providers/ThemeModeProvider.tsx
+++ b/src/providers/ThemeModeProvider.tsx
@@ -59,7 +59,7 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
applyResolvedThemeClass(resolveThemeMode(next));
void storageUtil.set(THEME_MODE_STORAGE_KEY, next).catch((err) => {
- console.error('[Theme Storage Error] Failed to persistent theme state:', err);
+ console.error('[Theme Storage Error] Failed to persist theme mode:', err);
});
persistThemeModeSnapshot(next);
@@ -82,7 +82,7 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
applyResolvedThemeClass(resolveThemeMode(saved));
})
.catch((err) => {
- console.error('[Theme Restore Thread Failed]', err);
+ console.error('[Theme Restore Error]', err);
});
return () => {
@@ -135,9 +135,7 @@ export function ThemeModeProvider({ children }: ThemeModeProviderProps) {
export function useThemeMode(): ThemeModeContextType {
const ctx = useContext(ThemeModeContext);
if (!ctx) {
- throw new Error(
- 'useThemeMode must be used within a valid ThemeModeProvider sandboxed container',
- );
+ throw new Error('useThemeMode must be used within ThemeModeProvider');
}
return ctx;
}
diff --git a/src/utils/diffEngine.ts b/src/utils/diffEngine.ts
index 0736a8c..bc63c15 100644
--- a/src/utils/diffEngine.ts
+++ b/src/utils/diffEngine.ts
@@ -1,4 +1,4 @@
-import type { DiffNode, DiffResult, DiffType } from '@/pages/JsonTools/types';
+import type { DiffNode, DiffResult } from '@/pages/JsonTools/types';
const ROOT_PATH = '$';
const SENTINEL = Symbol('missing');
@@ -37,7 +37,6 @@ const diffNode = (
path: string,
diffPaths: string[],
): DiffNode => {
- // 分支 1:节点增加行为拦截 (叶子节点状态)
if (left === SENTINEL && right !== SENTINEL) {
diffPaths.push(path);
return {
@@ -51,7 +50,6 @@ const diffNode = (
};
}
- // 分支 2:节点删除行为拦截 (叶子节点状态)
if (right === SENTINEL && left !== SENTINEL) {
diffPaths.push(path);
return {
@@ -181,5 +179,3 @@ export const diffJson = (left: unknown, right: unknown): DiffResult => {
diffCount: diffPaths.length,
};
};
-
-export type { DiffNode, DiffResult, DiffType };
diff --git a/src/utils/indexedDbCleaner.ts b/src/utils/indexedDbCleaner.ts
index f101e43..07a5b63 100644
--- a/src/utils/indexedDbCleaner.ts
+++ b/src/utils/indexedDbCleaner.ts
@@ -70,12 +70,12 @@ export async function clearAllIndexedDBs(
const deleteReq = indexedDB.deleteDatabase(dbName);
const timeoutId = setTimeout(() => {
- console.warn('IndexedDB delete timeout:', dbName);
+ console.warn(`IndexedDB 删除超时: ${dbName}`);
settle('timeout');
}, timeoutMs);
deleteReq.onblocked = () => {
- console.warn('IndexedDB delete blocked:', dbName);
+ console.warn(`IndexedDB 删除被阻塞: ${dbName}`);
settle('blocked');
};
deleteReq.onsuccess = () => settle('deleted');
@@ -137,7 +137,7 @@ export async function clearAllIndexedDBs(
const transaction = db.transaction(storeNames, 'readwrite');
// 必须在 clear 请求完成前注册 oncomplete,否则事务可能已结束导致永久挂起
const transactionDone = waitForTransaction(transaction, clearStoreTimeoutMs);
- void transactionDone.catch(() => undefined);
+ void transactionDone.catch(() => {});
const errors = (
await Promise.all(
storeNames.map((storeName) => clearStore(transaction.objectStore(storeName), storeName)),
@@ -145,23 +145,11 @@ export async function clearAllIndexedDBs(
).filter((error): error is string => Boolean(error));
if (errors.length > 0) {
- try {
- transaction.abort();
- } catch {
- // ignore abort failures on already-finished transactions
- }
+ transaction.abort();
return { success: false, errors };
}
- try {
- await transactionDone;
- } catch {
- return {
- success: false,
- errors: [`清空 IndexedDB 失败(${dbName}),请刷新后重试`],
- };
- }
-
+ await transactionDone;
return { success: true, errors: [] };
} catch {
return {
diff --git a/src/utils/restrictedUrls.ts b/src/utils/restrictedUrls.ts
index 898f83d..af447f7 100644
--- a/src/utils/restrictedUrls.ts
+++ b/src/utils/restrictedUrls.ts
@@ -23,7 +23,7 @@ export function isUnsupportedPageUrl(url: string | undefined): boolean {
if (!url) return true;
const protocol = getUrlProtocol(url);
if (!protocol) return true;
- return (RESTRICTED_PROTOCOLS as readonly string[]).includes(protocol);
+ return RESTRICTED_PROTOCOLS.some((p) => p === protocol);
}
/** 用于 storage cleaner tab 检测(前缀匹配,兼容无 protocol 的场景) */
diff --git a/src/utils/storageCleaner.ts b/src/utils/storageCleaner.ts
index 0a23b65..4da5a6f 100644
--- a/src/utils/storageCleaner.ts
+++ b/src/utils/storageCleaner.ts
@@ -9,8 +9,6 @@ import {
} from '@/utils/indexedDbCleaner';
import { browser } from 'wxt/browser';
-type CleanScriptResult = IndexedDBCleanResult;
-
export async function getCurrentTab() {
// For popup pages, we need to get the active tab from the browser window that triggered the popup.
// We should ONLY care about the currently active tab in the last focused window.
@@ -220,7 +218,7 @@ export async function clearCookies(url: string): Promise
{
*/
async function runCleanScript(
tabId: number,
- func: (...args: A) => CleanScriptResult | Promise,
+ func: (...args: A) => IndexedDBCleanResult | Promise,
errorLabel: string,
args?: A,
): Promise {
diff --git a/src/utils/syncSnapshot.ts b/src/utils/syncSnapshot.ts
index 62ce828..08af5c4 100644
--- a/src/utils/syncSnapshot.ts
+++ b/src/utils/syncSnapshot.ts
@@ -15,7 +15,7 @@ export function getSyncSnapshot(
}
return (parsed as T) ?? defaultValue;
} catch (error) {
- console.error(`[SyncSnapshot] Failed to read snapshot/${key}:`, error);
+ console.error(`读取快照失败 (${key}):`, error);
return defaultValue;
}
}
diff --git a/src/utils/themeSnapshot.ts b/src/utils/themeSnapshot.ts
index 2ce539a..596b34d 100644
--- a/src/utils/themeSnapshot.ts
+++ b/src/utils/themeSnapshot.ts
@@ -35,7 +35,7 @@ export const persistThemeModeSnapshot = (mode: ThemeMode): void => {
try {
localStorage.setItem(THEME_MODE_SNAPSHOT_KEY, JSON.stringify(mode));
} catch (error) {
- console.error('[Theme Snapshot Error] LocalStorage quota exceeded:', error);
+ console.error('保存主题快照失败:', error);
}
};
diff --git a/src/utils/useStorageState.ts b/src/utils/useStorageState.ts
index 94782a4..345f827 100644
--- a/src/utils/useStorageState.ts
+++ b/src/utils/useStorageState.ts
@@ -28,7 +28,6 @@ export const useStorageState = (
setValueInternal(next);
}, []);
- // Only load from storage once on mount
useEffect(() => {
if (hasLoadedFromStorage.current) return;
@@ -64,7 +63,6 @@ export const useStorageState = (
};
}, [defaultValue, key, validator]);
- // Save to storage and localStorage snapshot when value changes (after initial load)
useEffect(() => {
if (!isInitialized) return;
if (!loadSucceededRef.current && !userModifiedRef.current) return;