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
+9 -8
View File
@@ -9,8 +9,8 @@ import {
describe('features', () => {
describe('FEATURES', () => {
it('should have 6 features defined', () => {
expect(FEATURES).toHaveLength(6);
it('should have 7 features defined', () => {
expect(FEATURES).toHaveLength(7);
});
it('should have all required properties for each feature', () => {
@@ -32,8 +32,8 @@ describe('features', () => {
// Optional UI properties for non-hidden features
if (feature.key !== 'dashboard') {
expect(feature).toHaveProperty('icon');
expect(feature).toHaveProperty('themeColor');
expect(typeof feature.themeColor).toBe('string');
expect(feature).toHaveProperty('themeColorKey');
expect(typeof feature.themeColorKey).toBe('string');
}
});
});
@@ -58,7 +58,7 @@ describe('features', () => {
expect(feature).toBeDefined();
expect(feature?.key).toBe('timestamp');
expect(feature?.labelKey).toBe('features:timestamp.title');
expect(feature?.themeColor).toBeDefined();
expect(feature?.themeColorKey).toBeDefined();
});
it('should return storageCleaner feature', () => {
@@ -95,13 +95,14 @@ describe('features', () => {
describe('getAllFeatureKeys', () => {
it('should return all feature keys', () => {
const allKeys = getAllFeatureKeys();
expect(allKeys).toHaveLength(6);
expect(allKeys).toHaveLength(7);
expect(allKeys).toContain('dashboard');
expect(allKeys).toContain('timestamp');
expect(allKeys).toContain('storageCleaner');
expect(allKeys).toContain('qrCode');
expect(allKeys).toContain('textStatistics');
expect(allKeys).toContain('jwt');
expect(allKeys).toContain('jsonDiff');
});
});
@@ -118,9 +119,9 @@ describe('features', () => {
expect(pageOrder).toContain('qrCode');
});
it('should have 5 items in page order', () => {
it('should have 6 items in page order', () => {
const pageOrder = getDefaultPageOrder();
expect(pageOrder).toHaveLength(5);
expect(pageOrder).toHaveLength(6);
});
});
});
+23 -8
View File
@@ -5,8 +5,9 @@ import StorageIcon from '@mui/icons-material/Storage';
import QrCodeIcon from '@mui/icons-material/QrCode';
import DescriptionIcon from '@mui/icons-material/Description';
import VpnKeyIcon from '@mui/icons-material/VpnKey';
import CompareArrowsIcon from '@mui/icons-material/CompareArrows';
import { THEME_COLORS } from './pageTheme';
export type PaletteColorKey = 'primary' | 'success' | 'warning' | 'error' | 'secondary' | 'info';
// 懒加载页面组件
const DashboardPage = lazy(() => import('@/pages/Dashboard'));
@@ -15,6 +16,7 @@ const StorageCleanerPage = lazy(() => import('@/pages/StorageCleaner'));
const QrCodePage = lazy(() => import('@/pages/QrCode'));
const TextStatisticsPage = lazy(() => import('@/pages/TextStatistics'));
const JwtPage = lazy(() => import('@/pages/Jwt'));
const JsonDiffPage = lazy(() => import('@/pages/JsonDiff'));
/**
* 功能配置接口
@@ -28,8 +30,8 @@ export interface FeatureConfig {
labelKey: string;
/** 功能描述翻译键 */
descriptionKey: string;
/** 主题颜色(用于仪表盘卡片) */
themeColor?: string;
/** 主题颜色(用于仪表盘卡片,映射到 theme.palette[key].main */
themeColorKey?: PaletteColorKey;
/** 图标组件(用于仪表盘卡片) */
icon?: ReactNode;
/** 默认是否在仪表盘显示 */
@@ -61,7 +63,7 @@ export const FEATURES: FeatureConfig[] = [
key: 'timestamp',
labelKey: 'features:timestamp.title',
descriptionKey: 'features:timestamp.description',
themeColor: THEME_COLORS.primary,
themeColorKey: 'primary',
icon: <AccessTimeIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
@@ -74,7 +76,7 @@ export const FEATURES: FeatureConfig[] = [
key: 'storageCleaner',
labelKey: 'features:storageCleaner.title',
descriptionKey: 'features:storageCleaner.description',
themeColor: THEME_COLORS.warning,
themeColorKey: 'warning',
icon: <StorageIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
@@ -87,7 +89,7 @@ export const FEATURES: FeatureConfig[] = [
key: 'qrCode',
labelKey: 'features:qrCode.title',
descriptionKey: 'features:qrCode.description',
themeColor: THEME_COLORS.success,
themeColorKey: 'success',
icon: <QrCodeIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
@@ -100,7 +102,7 @@ export const FEATURES: FeatureConfig[] = [
key: 'textStatistics',
labelKey: 'features:textStatistics.title',
descriptionKey: 'features:textStatistics.description',
themeColor: THEME_COLORS.purple,
themeColorKey: 'secondary',
icon: <DescriptionIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
@@ -113,7 +115,7 @@ export const FEATURES: FeatureConfig[] = [
key: 'jwt',
labelKey: 'features:jwt.title',
descriptionKey: 'features:jwt.description',
themeColor: THEME_COLORS.indigo,
themeColorKey: 'info',
icon: <VpnKeyIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
@@ -122,6 +124,19 @@ export const FEATURES: FeatureConfig[] = [
tab: JwtPage,
},
},
{
key: 'jsonDiff',
labelKey: 'features:jsonDiff.title',
descriptionKey: 'features:jsonDiff.description',
themeColorKey: 'primary',
icon: <CompareArrowsIcon sx={{ fontSize: 20 }} />,
defaultVisible: true,
components: {
popup: JsonDiffPage,
sidepanel: JsonDiffPage,
tab: JsonDiffPage,
},
},
];
export function getFeatureByKey(key: PageType): FeatureConfig | undefined {
+187 -139
View File
@@ -11,6 +11,9 @@ export type ZoneType = (typeof ZONES)[number];
/**
* 符合 WCAG AA 标准(4.5:1 对比度)的主题颜色体系
* 所有颜色都经过对比度计算,确保可访问性
*
* 注意:这些颜色是品牌色源,实际组件应优先使用 theme.palette.* 令牌,
* 以便在亮色/暗色模式下自动切换。
*/
export const THEME_COLORS = {
// 主要颜色 - 蓝色系
@@ -66,11 +69,10 @@ export const STATUS_COLORS = {
} as const;
/**
* 全局样式配置
* 暗色模式下自动加深 alpha 值的辅助函数
*/
export const globalStyles = {
backgroundColor: '#f5f5f5',
} as const;
export const surfaceTint = (theme: Theme, color: string, baseAlpha: number) =>
alpha(color, theme.palette.mode === 'dark' ? Math.min(baseAlpha + 0.1, 0.9) : baseAlpha);
/**
* 时间戳转换页面样式
@@ -82,18 +84,18 @@ export const timestampPageStyles = {
bgcolor: 'background.paper',
borderRadius: 3,
border: '1px solid',
borderColor: 'grey.100',
borderColor: 'divider',
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
'& fieldset': { border: 'none' },
'&:hover': { borderColor: 'grey.300', bgcolor: 'grey.50' },
'&:hover': { borderColor: 'action.active', bgcolor: 'action.hover' },
'&.Mui-focused': {
bgcolor: '#fff',
bgcolor: 'background.paper',
borderColor: 'primary.main',
boxShadow: (theme: Theme) => `0 0 0 4px ${theme.palette.primary.main}1a`,
boxShadow: (theme: Theme) => `0 0 0 4px ${alpha(theme.palette.primary.main, 0.1)}`,
},
'&.Mui-error': {
borderColor: 'error.main',
boxShadow: (theme: Theme) => `0 0 0 4px ${theme.palette.error.main}1a`,
boxShadow: (theme: Theme) => `0 0 0 4px ${alpha(theme.palette.error.main, 0.1)}`,
},
},
'& .MuiInputBase-input': {
@@ -109,21 +111,21 @@ export const timestampPageStyles = {
sx: { borderRadius: 3, mt: 1, boxShadow: '0 12px 32px rgba(0,0,0,0.1)' },
},
},
cardBg: alpha(THEME_COLORS.primary, 0.04),
cardBorder: alpha(THEME_COLORS.primary, 0.1),
switcherBg: alpha(THEME_COLORS.primary, 0.08),
switcherBorder: alpha(THEME_COLORS.primary, 0.1),
mutedText: alpha(THEME_COLORS.primary, 0.4),
resultBg: alpha(THEME_COLORS.primary, 0.05),
buttonHover: `0 8px 24px ${alpha(THEME_COLORS.primary, 0.2)}`,
cardBg: (theme: Theme) => alpha(theme.palette.primary.main, 0.04),
cardBorder: (theme: Theme) => alpha(theme.palette.primary.main, 0.1),
switcherBg: (theme: Theme) => alpha(theme.palette.primary.main, 0.08),
switcherBorder: (theme: Theme) => alpha(theme.palette.primary.main, 0.1),
mutedText: (theme: Theme) => alpha(theme.palette.primary.main, 0.4),
resultBg: (theme: Theme) => alpha(theme.palette.primary.main, 0.05),
buttonHover: (theme: Theme) => `0 8px 24px ${alpha(theme.palette.primary.main, 0.2)}`,
/** 模式切换器 (ToggleButtonGroup) 样式 */
MODE_SWITCHER: {
width: '100%',
mb: 2.5,
borderRadius: 4,
bgcolor: 'grey.100',
bgcolor: (theme: Theme) => (theme.palette.mode === 'light' ? 'grey.100' : 'grey.900'),
border: '1px solid',
borderColor: 'grey.200',
borderColor: 'divider',
p: 0.6,
'& .MuiToggleButtonGroup-grouped': {
flex: 1,
@@ -135,7 +137,7 @@ export const timestampPageStyles = {
color: 'text.secondary',
transition: 'color 0.3s',
'&.Mui-selected': {
bgcolor: '#fff',
bgcolor: 'background.paper',
color: 'primary.main',
boxShadow: '0 4px 12px rgba(0,0,0,0.05)',
},
@@ -145,11 +147,11 @@ export const timestampPageStyles = {
UNIT_SWITCHER_CONTAINER: {
flex: 1,
display: 'flex',
bgcolor: 'grey.50',
bgcolor: 'action.hover',
p: 0.5,
borderRadius: 3.5,
border: '1px solid',
borderColor: 'grey.100',
borderColor: 'divider',
},
UNIT_SWITCHER_ITEM: (active: boolean) => ({
flex: 1,
@@ -160,12 +162,12 @@ export const timestampPageStyles = {
fontSize: '0.75rem',
fontWeight: 800,
transition: 'all 0.2s',
bgcolor: active ? '#fff' : 'transparent',
bgcolor: active ? 'background.paper' : 'transparent',
color: active ? 'primary.main' : 'text.disabled',
boxShadow: active ? '0 2px 8px rgba(0,0,0,0.05)' : 'none',
}),
/** LiveClock 卡片样式 */
LIVE_CLOCK_CARD: {
LIVE_CLOCK_CARD: (theme: Theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
@@ -173,13 +175,13 @@ export const timestampPageStyles = {
gap: 1.5,
p: 1.8,
mb: 2.5,
bgcolor: alpha(THEME_COLORS.primary, 0.04),
bgcolor: alpha(theme.palette.primary.main, 0.04),
borderRadius: 4,
border: '1px solid',
borderColor: alpha(THEME_COLORS.primary, 0.1),
},
borderColor: alpha(theme.palette.primary.main, 0.1),
}),
LIVE_CLOCK_LABEL: {
color: THEME_COLORS.primary,
color: 'primary.main',
fontWeight: 800,
fontSize: '0.6rem',
textTransform: 'uppercase',
@@ -187,21 +189,21 @@ export const timestampPageStyles = {
},
LIVE_CLOCK_VALUE: {
fontWeight: 800,
color: THEME_COLORS.primary,
color: 'primary.main',
fontFamily: 'monospace',
fontSize: { xs: '1.1rem', sm: '1.2rem' },
letterSpacing: '-0.5px',
lineHeight: 1.2,
},
LIVE_CLOCK_UNIT_SWITCHER: {
LIVE_CLOCK_UNIT_SWITCHER: (theme: Theme) => ({
display: 'flex',
p: 0.4,
bgcolor: alpha(THEME_COLORS.primary, 0.08),
bgcolor: alpha(theme.palette.primary.main, 0.08),
borderRadius: 2.5,
border: '1px solid',
borderColor: alpha(THEME_COLORS.primary, 0.1),
},
LIVE_CLOCK_UNIT_ITEM: (active: boolean) => ({
borderColor: alpha(theme.palette.primary.main, 0.1),
}),
LIVE_CLOCK_UNIT_ITEM: (active: boolean) => (theme: Theme) => ({
px: { xs: 1, sm: 1.2 },
py: 0.35,
borderRadius: 2,
@@ -209,20 +211,20 @@ export const timestampPageStyles = {
fontSize: '0.65rem',
fontWeight: 900,
transition: 'all 0.2s',
bgcolor: active ? '#fff' : 'transparent',
color: active ? 'primary.main' : alpha(THEME_COLORS.primary, 0.4),
bgcolor: active ? 'background.paper' : 'transparent',
color: active ? 'primary.main' : alpha(theme.palette.primary.main, 0.4),
boxShadow: active ? '0 2px 6px rgba(33, 150, 243, 0.2)' : 'none',
}),
LIVE_CLOCK_ICON_BUTTON: {
color: THEME_COLORS.primary,
bgcolor: '#fff',
color: 'primary.main',
bgcolor: 'background.paper',
boxShadow: '0 2px 4px rgba(0,0,0,0.05)',
'&:hover': { bgcolor: THEME_COLORS.primary, color: '#fff' },
'&:hover': { bgcolor: 'primary.main', color: 'primary.contrastText' },
},
LIVE_CLOCK_DIVIDER: {
mx: 0.5,
my: 1,
borderColor: alpha(THEME_COLORS.primary, 0.1),
borderColor: (theme: Theme) => alpha(theme.palette.primary.main, 0.1),
},
/** ResultView 样式 */
RESULT_LABEL: {
@@ -232,33 +234,33 @@ export const timestampPageStyles = {
fontWeight: 800,
fontSize: '0.7rem',
},
RESULT_MAIN_BOX: {
bgcolor: alpha(THEME_COLORS.primary, 0.05),
RESULT_MAIN_BOX: (theme: Theme) => ({
bgcolor: alpha(theme.palette.primary.main, 0.05),
p: 2,
borderRadius: 4,
position: 'relative',
mb: 2.5,
border: '1px solid',
borderColor: alpha(THEME_COLORS.primary, 0.1),
borderColor: alpha(theme.palette.primary.main, 0.1),
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
},
}),
RESULT_MAIN_TEXT: {
fontFamily: 'monospace',
fontWeight: 700,
color: THEME_COLORS.primary,
color: 'primary.main',
wordBreak: 'break-all',
pr: 4,
fontSize: '1rem',
},
RESULT_EXTRA_STACK: {
bgcolor: alpha(THEME_COLORS.primary, 0.05),
RESULT_EXTRA_STACK: (theme: Theme) => ({
bgcolor: alpha(theme.palette.primary.main, 0.05),
p: 2,
borderRadius: 4,
border: '1px solid',
borderColor: alpha(THEME_COLORS.primary, 0.1),
},
borderColor: alpha(theme.palette.primary.main, 0.1),
}),
RESULT_EXTRA_LABEL: {
color: 'text.disabled',
fontWeight: 700,
@@ -267,7 +269,7 @@ export const timestampPageStyles = {
},
RESULT_EXTRA_VALUE: {
fontFamily: 'monospace',
color: THEME_COLORS.primary,
color: 'primary.main',
fontWeight: 600,
fontSize: '0.65rem',
},
@@ -279,15 +281,14 @@ export const timestampPageStyles = {
export const storageCleanerPageStyles = {
warningColor: THEME_COLORS.warning,
warningDark: THEME_COLORS.warningDark,
warningBg: alpha(THEME_COLORS.warning, 0.05),
warningBorder: `1px solid ${alpha(THEME_COLORS.warning, 0.2)}`,
errorBorder: `1px solid ${alpha(THEME_COLORS.error, 0.2)}`,
errorBg: alpha(THEME_COLORS.error, 0.05),
warningBg: (theme: Theme) => surfaceTint(theme, theme.palette.warning.main, 0.05),
errorBorder: (theme: Theme) => `1px solid ${surfaceTint(theme, theme.palette.error.main, 0.2)}`,
errorBg: (theme: Theme) => surfaceTint(theme, theme.palette.error.main, 0.05),
/** 选项网格容器 */
OPTIONS_GRID_CONTAINER: {
mb: 3,
border: '1px solid',
borderColor: 'grey.100',
borderColor: 'divider',
borderRadius: 4,
bgcolor: 'background.paper',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.05)',
@@ -307,7 +308,7 @@ export const storageCleanerPageStyles = {
borderBottomRightRadius: 4,
transition: 'all 0.2s',
'&:hover': {
bgcolor: 'rgba(0, 0, 0, 0.04)',
bgcolor: 'action.hover',
},
},
OPTIONS_GRID_CHECKBOX: {
@@ -322,7 +323,7 @@ export const storageCleanerPageStyles = {
},
},
/** 选项项 */
OPTION_ITEM: (checked: boolean) => ({
OPTION_ITEM: (checked: boolean) => (theme: Theme) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
@@ -330,10 +331,10 @@ export const storageCleanerPageStyles = {
px: { xs: 1, sm: 1.5 },
borderRadius: 3,
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
bgcolor: checked ? alpha(THEME_COLORS.warning, 0.05) : 'transparent',
border: `1px solid ${checked ? alpha(THEME_COLORS.warning, 0.2) : 'transparent'}`,
bgcolor: checked ? surfaceTint(theme, theme.palette.warning.main, 0.05) : 'transparent',
border: `1px solid ${checked ? surfaceTint(theme, theme.palette.warning.main, 0.2) : 'transparent'}`,
'&:hover': {
bgcolor: checked ? alpha(THEME_COLORS.warning, 0.1) : 'rgba(0, 0, 0, 0.02)',
bgcolor: checked ? surfaceTint(theme, theme.palette.warning.main, 0.1) : 'action.hover',
transform: 'translateY(-1px)',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
},
@@ -346,7 +347,7 @@ export const storageCleanerPageStyles = {
overflow: 'hidden',
textOverflow: 'ellipsis',
transition: 'color 0.2s',
color: checked ? THEME_COLORS.warning : 'text.primary',
color: checked ? 'warning.main' : 'text.primary',
}),
OPTION_ITEM_SIZE: {
color: 'text.secondary',
@@ -359,7 +360,7 @@ export const storageCleanerPageStyles = {
opacity: 0.8,
},
OPTION_ITEM_NO_DATA: {
color: 'grey.400',
color: 'text.disabled',
fontSize: '0.65rem',
fontWeight: 500,
display: 'block',
@@ -384,7 +385,7 @@ export const storageCleanerPageStyles = {
borderRadius: 4,
bgcolor: 'background.paper',
border: '1px solid',
borderColor: 'grey.100',
borderColor: 'divider',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
@@ -407,30 +408,30 @@ export const storageCleanerPageStyles = {
},
},
/** DomainHeader */
DOMAIN_HEADER_BADGE: {
bgcolor: alpha(THEME_COLORS.warning, 0.15),
color: THEME_COLORS.warning,
DOMAIN_HEADER_BADGE: (theme: Theme) => ({
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.15),
color: 'warning.main',
px: 1.5,
py: 0.3,
borderRadius: 2,
fontWeight: 800,
fontSize: '0.7rem',
boxShadow: `0 2px 4px ${alpha(THEME_COLORS.warning, 0.2)}`,
boxShadow: `0 2px 4px ${surfaceTint(theme, theme.palette.warning.main, 0.2)}`,
transition: 'all 0.2s',
'&:hover': {
bgcolor: alpha(THEME_COLORS.warning, 0.25),
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.25),
},
},
DOMAIN_HEADER_ICON: {
}),
DOMAIN_HEADER_ICON: (theme: Theme) => ({
p: 1.2,
borderRadius: 3,
boxShadow: `0 2px 8px ${alpha(THEME_COLORS.warning, 0.15)}`,
boxShadow: `0 2px 8px ${surfaceTint(theme, theme.palette.warning.main, 0.15)}`,
transition: 'all 0.2s',
'&:hover': {
bgcolor: alpha(THEME_COLORS.warning, 0.15),
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.15),
transform: 'scale(1.05)',
},
},
}),
/** ErrorDisplay */
ERROR_DISPLAY_CONTAINER: {
py: 8,
@@ -440,17 +441,18 @@ export const storageCleanerPageStyles = {
minHeight: { xs: 'auto', sm: '400px' },
textAlign: 'center',
},
ERROR_DISPLAY_BOX: {
ERROR_DISPLAY_BOX: (theme: Theme) => ({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
p: 4,
boxShadow: `0 8px 24px ${alpha(THEME_COLORS.error, 0.15)}`,
border: `1px solid ${alpha(THEME_COLORS.error, 0.2)}`,
bgcolor: alpha(THEME_COLORS.error, 0.05),
},
boxShadow: `0 8px 24px ${surfaceTint(theme, theme.palette.error.main, 0.15)}`,
border: '1px solid',
borderColor: surfaceTint(theme, theme.palette.error.main, 0.2),
bgcolor: surfaceTint(theme, theme.palette.error.main, 0.05),
}),
/** CleaningResult */
CLEANING_RESULT_ALERT: {
borderRadius: 3,
@@ -471,7 +473,7 @@ export const storageCleanerPageStyles = {
CONFIRM_DIALOG_PAPER: {
borderRadius: 6,
backgroundImage: 'none',
boxShadow: `0 24px 64px -12px ${alpha(THEME_COLORS.black, 0.18)}`,
boxShadow: '0 24px 64px -12px rgba(0, 0, 0, 0.18)',
p: 1.5,
bgcolor: 'background.paper',
},
@@ -493,29 +495,29 @@ export const storageCleanerPageStyles = {
fontWeight: 500,
fontSize: '0.9rem',
},
CONFIRM_DIALOG_CHIP: {
bgcolor: alpha(THEME_COLORS.warning, 0.04),
CONFIRM_DIALOG_CHIP: (theme: Theme) => ({
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.04),
fontWeight: 700,
color: THEME_COLORS.warning,
color: 'warning.main',
fontSize: '0.75rem',
border: '1px solid',
borderColor: alpha(THEME_COLORS.warning, 0.15),
borderColor: surfaceTint(theme, theme.palette.warning.main, 0.15),
borderRadius: 2.5,
height: 'auto',
'& .MuiChip-label': { px: 1.2, py: 0.6 },
},
CONFIRM_DIALOG_WARNING_BOX: {
}),
CONFIRM_DIALOG_WARNING_BOX: (theme: Theme) => ({
display: 'inline-flex',
alignItems: 'center',
gap: 1,
bgcolor: alpha(THEME_COLORS.error, 0.05),
color: THEME_COLORS.error,
bgcolor: surfaceTint(theme, theme.palette.error.main, 0.05),
color: 'error.main',
px: 2,
py: 0.8,
borderRadius: 3,
border: '1px dashed',
borderColor: alpha(THEME_COLORS.error, 0.2),
},
borderColor: surfaceTint(theme, theme.palette.error.main, 0.2),
}),
CONFIRM_DIALOG_WARNING_TEXT: {
fontWeight: 800,
display: 'flex',
@@ -527,29 +529,23 @@ export const storageCleanerPageStyles = {
boxShadow: '0 0 1px 1px rgba(0, 0, 0, 0.1)',
color: 'text.secondary',
'&:hover': {
bgcolor: 'grey.100',
bgcolor: 'action.hover',
color: 'text.primary',
},
},
CONFIRM_DIALOG_CONFIRM: {
bgcolor: THEME_COLORS.warning,
bgcolor: 'warning.main',
'&:hover': {
bgcolor: THEME_COLORS.warningDark,
bgcolor: 'warning.dark',
},
},
} as const;
/**
* 二维码工具页面样式
* 注意:保留 successColor 和 successDark 以保持向后兼容性
*/
export const qrCodePageStyles = {
primaryColor: THEME_COLORS.success,
primaryDark: THEME_COLORS.successDark,
successColor: THEME_COLORS.success,
successDark: THEME_COLORS.successDark,
white: THEME_COLORS.white,
black: THEME_COLORS.black,
/** 加载状态容器 */
LOADING_CONTAINER: {
py: 4,
@@ -580,10 +576,10 @@ export const qrCodePageStyles = {
PRIMARY_BUTTON: {
py: 1.2,
borderRadius: 3,
bgcolor: THEME_COLORS.success,
bgcolor: 'success.main',
fontWeight: 700,
'&:hover': {
bgcolor: THEME_COLORS.successDark,
bgcolor: 'success.dark',
},
} as const,
/** 二维码展示区域 */
@@ -594,10 +590,10 @@ export const qrCodePageStyles = {
alignItems: 'center',
minHeight: 200,
border: '2px dashed',
borderColor: 'grey.200',
borderColor: 'divider',
borderRadius: 3,
p: 2,
bgcolor: 'grey.50',
bgcolor: 'action.hover',
} as const,
QR_PREVIEW_INNER: {
display: 'flex',
@@ -618,23 +614,23 @@ export const qrCodePageStyles = {
/** 下载按钮 */
DOWNLOAD_BUTTON: {
borderRadius: 2,
borderColor: THEME_COLORS.success,
color: THEME_COLORS.success,
borderColor: 'success.main',
color: 'success.main',
'&:hover': {
borderColor: THEME_COLORS.successDark,
bgcolor: alpha(THEME_COLORS.success, 0.05),
borderColor: 'success.dark',
bgcolor: (theme: Theme) => alpha(theme.palette.success.main, 0.05),
},
} as const,
/** 复制按钮 */
COPY_BUTTON: {
borderRadius: 2,
bgcolor: THEME_COLORS.success,
bgcolor: 'success.main',
'&:hover': {
bgcolor: THEME_COLORS.successDark,
bgcolor: 'success.dark',
},
} as const,
/** 拖拽上传区域 */
DROPZONE: (dragging: boolean, hasFile: boolean) =>
DROPZONE: (dragging: boolean, hasFile: boolean) => (theme: Theme) =>
({
display: 'flex',
flexDirection: 'column',
@@ -642,19 +638,19 @@ export const qrCodePageStyles = {
justifyContent: 'center',
minHeight: 200,
border: '2px dashed',
borderColor: dragging || hasFile ? THEME_COLORS.success : 'grey.200',
borderColor: dragging || hasFile ? 'success.main' : 'divider',
borderRadius: 3,
p: 4,
bgcolor: dragging
? alpha(THEME_COLORS.success, 0.1)
? alpha(theme.palette.success.main, 0.1)
: hasFile
? alpha(THEME_COLORS.success, 0.05)
: 'grey.50',
? alpha(theme.palette.success.main, 0.05)
: 'action.hover',
cursor: 'pointer',
transition: 'all 0.2s',
'&:hover': {
borderColor: THEME_COLORS.success,
bgcolor: alpha(THEME_COLORS.success, 0.05),
borderColor: 'success.main',
bgcolor: alpha(theme.palette.success.main, 0.05),
},
}) as const,
/** 图片预览容器 */
@@ -674,16 +670,16 @@ export const qrCodePageStyles = {
objectFit: 'contain',
} as const,
/** 清除按钮 */
CLEAR_BUTTON: {
CLEAR_BUTTON: (theme: Theme) => ({
position: 'absolute',
top: -8,
right: -8,
bgcolor: alpha(THEME_COLORS.error, 0.9),
bgcolor: alpha(theme.palette.error.main, 0.9),
color: 'white',
'&:hover': {
bgcolor: alpha(THEME_COLORS.errorDark, 0.95),
bgcolor: 'error.dark',
},
} as const,
}),
/** 结果输入框 */
RESULT_INPUT: {
position: 'relative',
@@ -700,9 +696,6 @@ export const qrCodePageStyles = {
* 仪表盘页面样式
*/
export const dashboardPageStyles = {
primaryColor: THEME_COLORS.primary,
backgroundColor: '#f5f5f5',
cardBackgroundColor: '#ffffff',
GRID_CONTAINER: {
display: 'grid',
gridTemplateColumns: {
@@ -719,14 +712,14 @@ export const dashboardPageStyles = {
* 使用语义化的颜色命名:valid(有效)、invalid(无效)、clear(清除)
*/
export const formRecognizerPageStyles = {
primaryColor: '#ff5722',
validColor: THEME_COLORS.success,
validDark: THEME_COLORS.successDark,
invalidColor: THEME_COLORS.warning,
invalidDark: THEME_COLORS.warningDark,
clearColor: THEME_COLORS.error,
clearDark: THEME_COLORS.errorDark,
clearBg: alpha(THEME_COLORS.error, 0.05),
primaryColor: 'warning.main',
validColor: 'success.main',
validDark: 'success.dark',
invalidColor: 'warning.main',
invalidDark: 'warning.dark',
clearColor: 'error.main',
clearDark: 'error.dark',
clearBg: (theme: Theme) => surfaceTint(theme, theme.palette.error.main, 0.05),
buttonStyle: {
py: 1.2,
borderRadius: 3,
@@ -738,7 +731,7 @@ export const formRecognizerPageStyles = {
* 表单映射页面样式
*/
export const formMappingPageStyles = {
secondaryColor: THEME_COLORS.purple,
secondaryColor: 'secondary.main',
} as const;
/**
@@ -746,13 +739,10 @@ export const formMappingPageStyles = {
*/
export const textStatisticsPageStyles = {
primaryColor: THEME_COLORS.purple,
cardBg: alpha(THEME_COLORS.purple, 0.04),
cardBorder: alpha(THEME_COLORS.purple, 0.1),
cardBg: (theme: Theme) => alpha(theme.palette.secondary.main, 0.04),
cardBorder: (theme: Theme) => alpha(theme.palette.secondary.main, 0.1),
} as const;
/**
* JWT 解析工具页面样式
*/
/**
* TopBar 组件样式
*/
@@ -770,8 +760,8 @@ export const topBarStyles = {
*/
export const jwtPageStyles = {
primaryColor: THEME_COLORS.indigo,
cardBg: alpha(THEME_COLORS.indigo, 0.04),
cardBorder: alpha(THEME_COLORS.indigo, 0.1),
cardBg: (theme: Theme) => alpha(theme.palette.info.main, 0.04),
cardBorder: (theme: Theme) => alpha(theme.palette.info.main, 0.1),
INPUT_STYLE: {
'& .MuiOutlinedInput-root': {
bgcolor: 'background.paper',
@@ -779,8 +769,66 @@ export const jwtPageStyles = {
fontSize: '0.85rem',
fontFamily: 'monospace',
transition: 'all 0.2s',
'&:hover': { bgcolor: 'grey.50' },
'&.Mui-focused': { bgcolor: '#fff' },
'&:hover': { bgcolor: 'action.hover' },
'&.Mui-focused': { bgcolor: 'background.paper' },
},
},
} as const;
/**
* JSON 差异比较工具页面样式
*/
export const jsonDiffPageStyles = {
primaryColor: THEME_COLORS.primary,
addedBg: (theme: Theme) => surfaceTint(theme, theme.palette.success.main, 0.15),
addedBorder: (theme: Theme) => surfaceTint(theme, theme.palette.success.main, 0.4),
addedText: 'success.main',
removedBg: (theme: Theme) => surfaceTint(theme, theme.palette.error.main, 0.15),
removedBorder: (theme: Theme) => surfaceTint(theme, theme.palette.error.main, 0.4),
removedText: 'error.main',
modifiedBg: (theme: Theme) => surfaceTint(theme, theme.palette.warning.main, 0.15),
modifiedBorder: (theme: Theme) => surfaceTint(theme, theme.palette.warning.main, 0.4),
modifiedText: 'warning.main',
INPUT_STYLE: {
'& .MuiOutlinedInput-root': {
bgcolor: 'background.paper',
borderRadius: 3,
fontSize: '0.8rem',
fontFamily: 'monospace',
alignItems: 'flex-start',
transition: 'all 0.2s',
'&:hover': { bgcolor: 'action.hover' },
'&.Mui-focused': {
bgcolor: 'background.paper',
boxShadow: (theme: Theme) => `0 0 0 4px ${alpha(theme.palette.primary.main, 0.1)}`,
},
'&.Mui-error': {
boxShadow: (theme: Theme) => `0 0 0 4px ${alpha(theme.palette.error.main, 0.1)}`,
},
},
},
TREE_CONTAINER: {
p: 1.5,
borderRadius: 3,
bgcolor: 'background.paper',
border: '1px solid',
borderColor: 'divider',
fontFamily: 'monospace',
fontSize: '0.8rem',
overflowX: 'auto',
minHeight: 200,
maxHeight: 480,
overflowY: 'auto',
},
NAVIGATOR: (theme: Theme) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: 1.5,
p: 1,
borderRadius: 3,
bgcolor: alpha(theme.palette.primary.main, 0.05),
border: '1px solid',
borderColor: alpha(theme.palette.primary.main, 0.15),
}),
} as const;
+139 -117
View File
@@ -1,131 +1,153 @@
import { createTheme } from '@mui/material/styles';
import { createTheme, type PaletteMode, type Theme } from '@mui/material/styles';
import { THEME_COLORS } from './pageTheme';
/**
* 统一的 MUI 主题配置
* 整合了原有的外部 CSS 样式(滚动条、动画、基础重置)
* 按模式生成 MUI 主题
*
* - light:使用 THEME_COLORS 中饱和度较高的版本作为 main
* - dark:使用 *Light 变体作为 main,保证暗底对比度满足 WCAG AA
*
* 所有调色板槽位均显式指定,不依赖 MUI 默认值。
*/
const theme = createTheme({
palette: {
primary: {
main: THEME_COLORS.primary,
dark: THEME_COLORS.primaryDark,
light: THEME_COLORS.primaryLight,
export function getTheme(mode: PaletteMode): Theme {
const isDark = mode === 'dark';
return createTheme({
palette: {
mode,
primary: {
main: isDark ? THEME_COLORS.primaryLight : THEME_COLORS.primary,
dark: THEME_COLORS.primaryDark,
light: THEME_COLORS.primaryLight,
},
secondary: {
main: isDark ? THEME_COLORS.purpleLight : THEME_COLORS.purple,
dark: THEME_COLORS.purpleDark,
light: THEME_COLORS.purpleLight,
},
success: {
main: isDark ? THEME_COLORS.successLight : THEME_COLORS.success,
dark: THEME_COLORS.successDark,
light: THEME_COLORS.successLight,
},
warning: {
main: isDark ? THEME_COLORS.warningLight : THEME_COLORS.warning,
dark: THEME_COLORS.warningDark,
light: THEME_COLORS.warningLight,
},
error: {
main: isDark ? THEME_COLORS.errorLight : THEME_COLORS.error,
dark: THEME_COLORS.errorDark,
light: THEME_COLORS.errorLight,
},
info: {
main: isDark ? THEME_COLORS.indigoLight : THEME_COLORS.indigo,
dark: THEME_COLORS.indigoDark,
light: THEME_COLORS.indigoLight,
},
background: {
default: isDark ? '#121212' : '#f5f5f5',
paper: isDark ? '#1e1e1e' : '#ffffff',
},
divider: isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.12)',
},
success: {
main: THEME_COLORS.success,
dark: THEME_COLORS.successDark,
light: THEME_COLORS.successLight,
typography: {
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
},
warning: {
main: THEME_COLORS.warning,
dark: THEME_COLORS.warningDark,
light: THEME_COLORS.warningLight,
},
error: {
main: THEME_COLORS.error,
dark: THEME_COLORS.errorDark,
light: THEME_COLORS.errorLight,
},
secondary: {
main: THEME_COLORS.purple,
dark: THEME_COLORS.purpleDark,
light: THEME_COLORS.purpleLight,
},
},
typography: {
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
'"Apple Color Emoji"',
'"Segoe UI Emoji"',
'"Segoe UI Symbol"',
].join(','),
},
components: {
MuiCssBaseline: {
styleOverrides: {
':root': {
'--sb-width': '6px',
'--sb-thumb-color': 'rgba(0, 0, 0, 0.1)',
'--sb-thumb-hover': 'rgba(0, 0, 0, 0.2)',
'--sb-track-color': 'transparent',
},
html: {
margin: 0,
padding: 0,
width: '100%',
minHeight: '100%',
backgroundColor: '#f5f5f5',
},
'body, #root': {
margin: 0,
padding: 0,
width: '100%',
minHeight: '100%',
},
// 针对 Popup 的特殊处理(如果需要固定宽高,可以在具体入口点或容器中处理,
// 这里提供全局基础,具体尺寸在 App 容器中限制)
body: {
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
},
code: {
fontFamily: 'source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace',
},
'h1, h2, h3, h4, h5, h6': {
fontSize: 'inherit',
fontWeight: 'inherit',
},
/* 全局极简滚动条定制 */
'*::-webkit-scrollbar': {
width: 'var(--sb-width)',
},
'*::-webkit-scrollbar-track': {
background: 'var(--sb-track-color)',
},
'*::-webkit-scrollbar-thumb': {
background: 'var(--sb-thumb-color)',
borderRadius: '10px',
backgroundClip: 'content-box',
border: '1px solid transparent',
},
'*::-webkit-scrollbar-thumb:hover': {
background: 'var(--sb-thumb-hover)',
},
/* Animations */
'@keyframes slideInRight': {
from: {
transform: 'translateX(30px)',
opacity: 0,
components: {
MuiCssBaseline: {
styleOverrides: (theme) => ({
':root': {
'--sb-width': '6px',
'--sb-thumb-color':
theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)',
'--sb-thumb-hover':
theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.25)' : 'rgba(0, 0, 0, 0.2)',
'--sb-track-color': 'transparent',
},
to: {
transform: 'translateX(0)',
opacity: 1,
html: {
margin: 0,
padding: 0,
width: '100%',
minHeight: '100%',
backgroundColor: theme.palette.background.default,
},
},
'@keyframes fadeIn': {
from: {
opacity: 0,
'body, #root': {
margin: 0,
padding: 0,
width: '100%',
minHeight: '100%',
},
to: {
opacity: 1,
body: {
WebkitFontSmoothing: 'antialiased',
MozOsxFontSmoothing: 'grayscale',
backgroundColor: theme.palette.background.default,
color: theme.palette.text.primary,
},
},
'.page-transition-enter': {
animation: 'slideInRight 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) forwards',
},
'.page-transition-dashboard': {
animation: 'fadeIn 0.3s ease-out forwards',
},
code: {
fontFamily: 'source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace',
},
'h1, h2, h3, h4, h5, h6': {
fontSize: 'inherit',
fontWeight: 'inherit',
},
/* 全局极简滚动条定制 */
'*::-webkit-scrollbar': {
width: 'var(--sb-width)',
},
'*::-webkit-scrollbar-track': {
background: 'var(--sb-track-color)',
},
'*::-webkit-scrollbar-thumb': {
background: 'var(--sb-thumb-color)',
borderRadius: '10px',
backgroundClip: 'content-box',
border: '1px solid transparent',
},
'*::-webkit-scrollbar-thumb:hover': {
background: 'var(--sb-thumb-hover)',
},
/* Animations */
'@keyframes slideInRight': {
from: {
transform: 'translateX(30px)',
opacity: 0,
},
to: {
transform: 'translateX(0)',
opacity: 1,
},
},
'@keyframes fadeIn': {
from: {
opacity: 0,
},
to: {
opacity: 1,
},
},
'.page-transition-enter': {
animation: 'slideInRight 0.3s cubic-bezier(0.25, 0.8, 0.25, 1) forwards',
},
'.page-transition-dashboard': {
animation: 'fadeIn 0.3s ease-out forwards',
},
}),
},
},
},
});
});
}
const theme = getTheme('light');
export default theme;