docs: 添加组件文档注释和类型导入
refactor: 统一使用 SnackbarOptions 类型
style: 优化导入语句顺序和格式
refactor: 简化假数据生成器中的faker导入和使用
feat(form-recognizer): 增强表单识别功能并优化UI交互
refactor: 移除未使用的组件文件
refactor(页面头部): 提取通用 PageHeader 组件
style(组件): 调整自动刷新开关和存储选项网格的样式
style(ui): 调整时间戳页面和结果视图的样式
ci(workflow): 移除Firefox测试以简化CI流程
refactor(qrcode): 重构二维码解析功能并提取为独立模块
style(页面样式): 统一页面头部图标颜色并优化表单样式
feat(formMapping): 添加表单映射功能
feat(表单映射): 添加配置导出功能及状态提示
feat(表单填充): 新增智能表单填充功能
ci: 精简触发CI的工作流分支
test: 更新路由测试以匹配新增的路由数量
test: 将测试文件中的描述和断言翻译为中文
test(PageHeader): 添加组件测试用例
refactor(theme): 重构主题样式并优化仪表盘卡片组件
refactor(Button): 统一按钮样式并移除重复样式定义
docs: 更新 README 文件中的目录结构说明
refactor: 优化代码类型声明和UI布局
refactor(消息通信): 使用 @webext-core/messaging 重构消息处理逻辑
fix: 修复依赖项缺失导致的潜在问题
refactor: 重构表单识别和消息处理逻辑
feat(侧边栏): 添加侧边栏状态变化通知功能
refactor: 优化高亮组件渲染逻辑并添加防抖处理
refactor: 统一组件导出方式为默认导出
refactor: 重构样式系统并迁移至 MUI 主题
docs: 更新项目文档以反映新增功能和技术栈细节
feat: 添加错误边界组件以捕获子组件错误
refactor(ui): 使用全局 snackbar 替换本地通知组件
refactor(snackbar): 重构全局消息提示为 SnackbarProvider 组件
refactor(Snackbar): 重构消息提示组件并优化样式
refactor: 重构路由和功能配置系统
refactor(GlobalSnackbar): 合并SnackbarProvider到GlobalSnackbar组件
feat(表单映射): 增强高亮器功能并优化元素定位
refactor: 移除表单映射相关功能
feat: 实现页面组件懒加载并优化加载状态显示
refactor: 移除未使用的数据生成和验证工具类
chore: 更新应用图标文件
refactor(qrcode): 替换二维码生成和解析库以减小体积
feat: 添加在标签页打开功能并优化错误处理
feat: 支持独立标签页模式并重构路由配置
feat(ui): 优化响应式布局和样式设计
refactor: 移除URL工具相关代码
refactor: 简化页面标题和存储选项网格的响应式布局
refactor: 优化样式和错误处理
refactor: 重构页面组件结构
style(DashboardPage): 优化仪表盘布局为响应式网格
refactor(ToolCard): 重构工具卡片组件并添加详细注释
feat: 新增文本统计功能
feat(jwt): 添加 JWT 解析工具功能
docs: 更新 README 以更清晰地描述项目功能和结构
docs: 更新AGENTS.md文档以反映项目最新状态
test: 更新特征计数测试以匹配新增工具
style: 统一导入语句顺序
refactor(pages): 重构工具页面组件结构并优化功能实现
feat(顶部栏): 添加搜索功能和历史记录
feat: 添加多语言支持(i18n)
feat(顶部栏): 添加语言切换功能并优化国际化支持
feat(i18n): 增加语言归一化和同步功能
fix(剪贴板): 使用国际化文本替换剪贴板消息
test(顶部栏): 更新测试以匹配国际化标题
refactor: 重构组件目录结构
refactor(Dashboard): 优化卡片组件结构并移除无用属性
refactor(timestamp): 合并输入处理函数并优化样式结构
refactor(dashboard): 重构仪表盘卡片组件并优化布局
refactor(storageCleaner): 重构样式代码并优化国际化处理
refactor(StorageCleaner): 优化条件判断和清理未使用的代码
refactor(QrCode): 重构二维码页面样式和组件结构
refactor(ErrorBoundary): 移除冗余状态更新并调整错误背景色
feat(TopBar): 优化顶部导航栏功能并添加样式配置
chore: 在.gitignore中添加docs目录
feat(JsonDiff): 添加差异引擎类型定义
feat: 新增 JSON 差异比较工具功能
feat(主题): 添加暗色模式支持并重构主题系统
This commit is contained in:
LingandRX
2026-05-09 00:18:29 +08:00
committed by GitHub
parent ff964e3608
commit 3c9c9e740e
43 changed files with 1812 additions and 388 deletions
+4 -3
View File
@@ -67,15 +67,16 @@ export const CopyButton: React.FC<CopyButtonProps> = ({
style={style}
sx={{
color: copied ? 'success.main' : color,
bgcolor: '#fff',
boxShadow: '0 2px 8px rgba(0,0,0,0.05)',
bgcolor: 'background.paper',
boxShadow: (theme) =>
`0 2px 8px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.3)' : 'rgba(0,0,0,0.05)'}`,
'&:hover': {
bgcolor: copied
? 'success.main'
: !['primary', 'secondary', 'success', 'error', 'info', 'warning'].includes(color)
? color
: `${color}.main`,
color: '#fff',
color: 'background.paper',
},
}}
>
+3 -1
View File
@@ -1,5 +1,6 @@
import { Component, ErrorInfo, ReactNode } from 'react';
import { Box, Button, Container, Paper, Typography } from '@mui/material';
import type { Theme } from '@mui/material/styles';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import RefreshIcon from '@mui/icons-material/Refresh';
@@ -60,7 +61,8 @@ export class ErrorBoundary extends Component<Props, State> {
sx={{
mb: 3,
p: 2,
bgcolor: 'grey.100',
bgcolor: (theme: Theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'grey.100',
borderRadius: 2,
textAlign: 'left',
maxHeight: '200px',
+2 -2
View File
@@ -182,8 +182,8 @@ export function GlobalSnackbar({
backgroundImage: 'none',
boxShadow: (theme: Theme) =>
`0 12px 32px ${alpha(theme.palette[severity].main, 0.35)}`,
'& .MuiAlert-icon': { mr: 0.5, fontSize: '1.1rem', color: '#fff' },
'& .MuiAlert-message': { color: '#fff', padding: '6px 0' },
'& .MuiAlert-icon': { mr: 0.5, fontSize: '1.1rem' },
'& .MuiAlert-message': { padding: '6px 0' },
}}
>
{message}
+41 -12
View File
@@ -20,16 +20,22 @@ import SearchIcon from '@mui/icons-material/Search';
import HistoryIcon from '@mui/icons-material/History';
import CloseIcon from '@mui/icons-material/Close';
import LanguageIcon from '@mui/icons-material/Language';
import LightModeIcon from '@mui/icons-material/LightMode';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import SettingsBrightnessIcon from '@mui/icons-material/SettingsBrightness';
import { useRouter } from '@/providers/RouterProvider';
import { useThemeMode } from '@/providers/ThemeModeProvider';
import { FEATURES, FeatureConfig } from '@/config/features';
import { storageUtil } from '@/utils/chromeStorage';
import { openExtensionPage } from '@/utils/chromeTabs';
import { useTranslation } from 'react-i18next';
import { alpha } from '@mui/material/styles';
import { SUPPORTED_LANGUAGES, normalizeLanguage } from '@/i18n';
import { topBarStyles } from '@/config/pageTheme';
export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void }) {
const { currentPage, goBack, navigateTo } = useRouter();
const { mode, setMode } = useThemeMode();
const { t, i18n } = useTranslation(['common', 'features']);
const [searchQuery, setSearchQuery] = useState('');
const [showResults, setShowResults] = useState(false);
@@ -107,6 +113,14 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
await storageUtil.set('app/language', newLng);
};
const cycleThemeMode = () => {
const next = { light: 'dark', dark: 'system', system: 'light' } as const;
setMode(next[mode]);
};
const ThemeIcon =
mode === 'light' ? LightModeIcon : mode === 'dark' ? DarkModeIcon : SettingsBrightnessIcon;
const handleKeyDown = (e: React.KeyboardEvent) => {
const totalItems = searchQuery.trim() ? searchResults.length : displayedHistory.length;
@@ -152,7 +166,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
px: { xs: 1, sm: 2 },
py: 1.5,
borderBottom: '1px solid',
borderColor: 'grey.100',
borderColor: 'divider',
bgcolor: 'background.paper',
zIndex: topBarStyles.Z_INDEX,
position: 'relative',
@@ -165,8 +179,12 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
onClick={goBack}
aria-label={t('common:buttons.back')}
sx={{
bgcolor: 'grey.50',
'&:hover': { bgcolor: 'grey.200' },
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'grey.50',
'&:hover': {
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : 'grey.200',
},
}}
>
<ArrowBackIosNewIcon sx={{ fontSize: 14 }} />
@@ -217,18 +235,24 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
}
sx={{
width: '100%',
bgcolor: 'grey.50',
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'grey.50',
px: 1.5,
py: 0.5,
borderRadius: 2,
fontSize: '0.875rem',
border: '1px solid transparent',
border: '1px solid',
borderColor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.1)' : 'transparent',
transition: 'all 0.2s',
'&:hover': { bgcolor: 'grey.100' },
'&:hover': {
bgcolor: (theme) =>
theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'grey.100',
},
'&.Mui-focused': {
bgcolor: 'background.paper',
borderColor: 'primary.main',
boxShadow: '0 0 0 2px rgba(25, 118, 210, 0.1)',
boxShadow: (theme) => `0 0 0 2px ${alpha(theme.palette.primary.main, 0.15)}`,
},
}}
/>
@@ -312,11 +336,7 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
</ClickAwayListener>
</Box>
<Stack
direction="row"
spacing={0.5}
sx={{ width: { xs: 100, sm: 150 }, justifyContent: 'flex-end' }}
>
<Stack direction="row" spacing={0.5} sx={{ justifyContent: 'flex-end', flexShrink: 0 }}>
<Tooltip title={t('common:buttons.toggleLanguage')}>
<IconButton
size="small"
@@ -326,6 +346,15 @@ export default function TopBar({ onOpenOptions }: { onOpenOptions: () => void })
<LanguageIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
<Tooltip title={t(`common:buttons.themeMode.${mode}`)}>
<IconButton
size="small"
onClick={cycleThemeMode}
aria-label={t('common:buttons.toggleTheme')}
>
<ThemeIcon sx={{ fontSize: 18 }} />
</IconButton>
</Tooltip>
<Tooltip title={t('common:buttons.openInTab')}>
<IconButton
size="small"
+9 -25
View File
@@ -14,7 +14,7 @@ describe('ToolCard 组件', () => {
<ToolCard
title="测试工具"
description="这是一个测试工具"
colorCode="#2196f3"
colorKey="primary"
icon={<AccessTimeIcon />}
onClick={() => {}}
/>,
@@ -26,12 +26,7 @@ describe('ToolCard 组件', () => {
it('无描述时仅渲染标题', () => {
render(
<ToolCard
title="仅标题"
colorCode="#2196f3"
icon={<AccessTimeIcon />}
onClick={() => {}}
/>,
<ToolCard title="仅标题" colorKey="primary" icon={<AccessTimeIcon />} onClick={() => {}} />,
);
expect(screen.getByText('仅标题')).toBeInTheDocument();
@@ -41,7 +36,7 @@ describe('ToolCard 组件', () => {
render(
<ToolCard
title="带图标"
colorCode="#2196f3"
colorKey="primary"
icon={<AccessTimeIcon data-testid="test-icon" />}
onClick={() => {}}
/>,
@@ -54,7 +49,7 @@ describe('ToolCard 组件', () => {
render(
<ToolCard
title="带快照"
colorCode="#2196f3"
colorKey="primary"
icon={<AccessTimeIcon />}
onClick={() => {}}
snapshot={<div data-testid="snapshot"></div>}
@@ -66,12 +61,7 @@ describe('ToolCard 组件', () => {
it('未提供快照时不渲染快照区域', () => {
const { container } = render(
<ToolCard
title="无快照"
colorCode="#2196f3"
icon={<AccessTimeIcon />}
onClick={() => {}}
/>,
<ToolCard title="无快照" colorKey="primary" icon={<AccessTimeIcon />} onClick={() => {}} />,
);
expect(container.querySelector('[data-testid="snapshot"]')).not.toBeInTheDocument();
@@ -79,12 +69,7 @@ describe('ToolCard 组件', () => {
it('应使用 CardActionArea 渲染,支持键盘聚焦', () => {
render(
<ToolCard
title="可聚焦"
colorCode="#2196f3"
icon={<AccessTimeIcon />}
onClick={() => {}}
/>,
<ToolCard title="可聚焦" colorKey="primary" icon={<AccessTimeIcon />} onClick={() => {}} />,
);
const button = screen.getByRole('button', { name: /可聚焦/ });
@@ -99,7 +84,7 @@ describe('ToolCard 组件', () => {
render(
<ToolCard
title="可点击"
colorCode="#2196f3"
colorKey="primary"
icon={<AccessTimeIcon />}
onClick={handleClick}
/>,
@@ -116,7 +101,7 @@ describe('ToolCard 组件', () => {
render(
<ToolCard
title="键盘可触发"
colorCode="#2196f3"
colorKey="primary"
icon={<AccessTimeIcon />}
onClick={handleClick}
/>,
@@ -131,11 +116,10 @@ describe('ToolCard 组件', () => {
describe('样式测试', () => {
it('应应用自定义颜色代码', () => {
const customColor = '#ff5722';
render(
<ToolCard
title="自定义颜色"
colorCode={customColor}
colorKey="warning"
icon={<AccessTimeIcon data-testid="custom-color-icon" />}
onClick={() => {}}
/>,
+24 -3
View File
@@ -1,10 +1,27 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { fireEvent, render, screen } from '@testing-library/react';
import TopBar from '@/components/TopBar';
import { RouterProvider } from '@/providers/RouterProvider';
import type { PageType } from '@/types/storage';
import React from 'react';
// matchMedia must be mocked before ThemeModeProvider is imported
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
import TopBar from '@/components/TopBar';
import { RouterProvider } from '@/providers/RouterProvider';
import { ThemeModeProvider } from '@/providers/ThemeModeProvider';
const mockRouterValue = {
currentPage: 'dashboard' as PageType,
visiblePages: ['dashboard', 'timestamp'] as PageType[],
@@ -29,7 +46,11 @@ describe('TopBar 组件', () => {
});
const renderWithProvider = (ui: React.ReactElement) => {
return render(<RouterProvider>{ui}</RouterProvider>);
return render(
<ThemeModeProvider>
<RouterProvider>{ui}</RouterProvider>
</ThemeModeProvider>,
);
};
describe('渲染测试', () => {