a5c34e5428
* feat: 添加 contextMenus 权限和消息类型定义 * feat: 实现右键菜单注册与点击分流逻辑 - 新增 utils/contextMenu.ts 封装菜单配置和解析函数 - 在 background.ts 监听 onInstalled 初始化菜单 - 实现 contextMenus.onClicked 点击事件处理 - 分流逻辑:优先发送到侧边栏,否则打开 options 页面 - 添加 contextMenu 单元测试(15个测试用例) - 更新 vitest.setup.ts 添加 contextMenus mock * feat: 实现 Content Script 原位轻量提示 UI - 新增 uiPopover.ts 实现原位弹窗组件(深色主题、自动定位、自动隐藏) - 新增 contextMenuHandler.ts 处理右键菜单消息 - 时间戳转换:在点击位置显示转换结果 - 文本统计:显示字符/单词/行数/字节统计 - 更新 messageHandler.ts 挂载右键菜单消息监听 * feat: 实现 React 页面层右键菜单数据联动 - 新增 useContextMenuData Hook 处理右键菜单数据传递 - 重构 Jwt/Base64Converter/TextStatistics/QrCode 页面接收右键数据 - RouterProvider 支持从 URL 参数解析右键菜单数据 - 添加 contextMenu/pendingData 存储键到 StorageSchema - 添加 useContextMenuData 单元测试(12个测试用例) * test: 添加 background 单元测试与边界情况处理 - 新增 entrypoints/__tests__/background.test.ts (12个测试用例) - 超长文本截断限制 (>10000字符) - Base64 内联图片拦截并返回错误提示 - 更新 parseContextMenuClick 返回 ParseResult 格式 - 更新测试匹配新的返回格式 - lint/typecheck/test 全部通过 (579个测试) * fix: 修复右键菜单跳转到错误页面的问题 - 将 options 页面路径从 '/entrypoints/options/index.html' 修正为 '/options.html' - WXT 构建后 entrypoints/options/index.html 会输出为根目录的 options.html * fix: 修复右键菜单跳转目标为 popup 页面 - 将 fallback 页面从 options.html 改为 popup.html - popup 页面使用 RouterProvider,可以正确处理 URL 参数并跳转到对应功能页面 - options 页面是独立设置页,不支持 URL 参数路由 * fix: 使用 openPopup() 替代 tabs.create() 打开弹窗 - background.ts: 使用 browser.action.openPopup() 打开 popup 弹窗 - 先保存数据到 storage,然后打开 popup - RouterProvider.tsx: 初始化时检查 storage 中的 pendingData 并跳转到对应页面 - 移除之前错误的 tabs.create() 方式 * fix: 修复右键菜单数据未传递到目标页面的问题 - RouterProvider 不再提前清除 pendingData - 让目标页面的 useContextMenuData 来消费和清除数据 - 这样确保 TextStatistics 等页面能正确接收选中的文本 * fix: 修复 popup 已打开时右键菜单不生效的问题 - 在 RouterProvider 中添加对 contextMenu/pendingData 的 storage 变化监听 - 当 pendingData 变化时,自动跳转到对应功能页面 - 解决了 openPopup() 只聚焦已有窗口而不触发重新挂载的问题 * feat: 支持右键菜单图片二维码识别 - 在 qrCodeParser.ts 中添加 parseQrCodeFromUrl 函数,支持从图片 URL 解析二维码 - 在 QrCodeToUrlSection 中使用 useContextMenuData 接收右键菜单传递的图片 URL - 自动下载图片并解析二维码,显示解析结果 * refactor: 暂时移除图片二维码识别功能 - 删除 qrCode-image 右键菜单配置 - 删除 parseQrCodeFromUrl 函数 - 删除 QrCodeToUrlSection 中的 useContextMenuData 相关代码 - 更新相关测试用例 * feat: 支持右键菜单时间戳转换 - 在 useTimestampConverter 中添加 useContextMenuData hook - 智能识别输入是时间戳还是日期时间字符串 - 自动切换到对应模式并执行转换 - 时间戳自动识别秒/毫秒单位 * fix: 修复右键菜单功能的多个逻辑漏洞 1. 修复时区硬编码问题 - 使用用户选择的时区而非固定 Asia/Shanghai 2. 修复 openPopup() 失败后数据残留 - 失败时清除 storage 中的待处理数据 3. 统一数据过期时间常量 - 导出 CONTEXT_MENU_DATA_EXPIRY_MS 并统一使用 4. 修复 featureKey 类型断言不安全 - 使用映射表处理非常规菜单 ID * refactor(qrcode): 提取独立组件并添加单元测试 - 创建 pages/QrCode/types.ts 定义状态类型接口 - 提取 QrCodePreview 组件用于二维码预览和操作 - 提取 ImageUploader 组件封装图片上传、拖拽、粘贴逻辑 - 重构 UrlToQrCodeSection 状态提升到父组件 - 重构 QrCodeToUrlSection 通过回调传递解析结果 - 更新主页面 index.tsx 集中管理所有状态 - 为 QrCodePreview 和 ImageUploader 添加单元测试 (23 个用例) * refactor(qrcode): 引入通用组件并重构双栏布局 - 引入 SwitchButtonGroup 用于模式切换 - 引入 TextInputArea 用于文本输入和结果展示 - 使用 MUI Grid 构建响应式双栏布局 - 实现 generate/parse 模式下的左右面板内容切换 - 修复 iconColor 颜色格式错误,使用 qrCodePageStyles.primaryColor * refactor(qrcode): 国际化补充、质量保障与错误处理优化 - 补充 i18n 翻译键(generateMode, parseMode, pasteHint 等) - 创建 useDebounce Hook 实现输入防抖 (200ms) - 使用 useRef 解决 useEffect 无限循环问题 - 编写 pages/QrCode 单元测试 (7 个用例) - 优化右键菜单错误处理,改进用户提示 - 运行 603 个测试全部通过 * refactor(qrcode): 组件化重构,采用 Context + Hook 模式 - 创建 QrCodeContext 和 QrCodeProvider 管理共享状态 - 提取 useQrCode Hook 封装所有状态和业务逻辑 - 创建 GeneratePanel 组件处理二维码生成模式 - 创建 ParsePanel 组件处理二维码解析模式 - 简化 index.tsx 为容器组件 (340行 → 47行) - 删除未使用的旧组件文件 (QrCodeToUrlSection, UrlToQrCodeSection) - 清理 types.ts 中未使用的类型定义 - 603 个测试全部通过 * fix(qrcode): 固定二维码预览图片尺寸,避免布局抖动 - 设置 QR_PREVIEW_IMAGE 固定尺寸 250x250 - 与 QRious 生成的二维码大小保持一致 - 解决输入内容变化时预览窗口大小跳动问题 * fix(qrcode): 修复切换 tab 后二维码图片失效问题 - 移除 ImageUploader 组件卸载时的预览 URL 释放逻辑 - 在 useQrCode hook 中统一管理预览 URL 生命周期 - 创建新预览 URL 前释放旧的,避免内存泄漏 * fix(qrcode): 固定图片上传区域高度,避免布局抖动 - 将 DROPZONE 的 minHeight: 200 改为 height: 250 - 与二维码预览图片高度保持一致 - 解决上传图片时 div 高度变化问题 * refactor(qrCode): 优化二维码解析功能 - 将'二维码转 URL'改为'二维码转文本' - 将解析结果的文本预览框的 placeholder 置为空 - 去除手动解析二维码功能,只保留自动解析 * fix(pageHeader): 使用 MUI 主题色替代硬编码颜色,支持暗色模式 * feat(pageHeader): popup 模式下隐藏 PageHeader 组件 * fix(i18n): 统一使用 useLazyTranslation 避免翻译 key 闪烁 * perf: 配置 manualChunks 拆分 vendor chunk,优化打包体积 - 使用 Vite 插件 manualChunksForHtmlOnly 仅对 HTML 多入口构建生效 - 跳过 background/content-script 的 IIFE 构建(不支持 manualChunks) - 拆分 vendor-react (193KB)、vendor-mui (326KB)、vendor-i18n (55KB) - 按需加载 vendor-qr (78KB)、vendor-dnd (45KB)、vendor-markdown (41KB) - PageErrorBoundary chunk 从 454KB 降至 36KB
914 lines
24 KiB
TypeScript
914 lines
24 KiB
TypeScript
import type { Theme } from '@mui/material';
|
|
import { alpha } from '@mui/material';
|
|
|
|
export const DATE_FORMAT = 'YYYY/MM/DD HH:mm:ss';
|
|
|
|
export const ZONES = ['Asia/Shanghai', 'America/New_York', 'Europe/London'] as const;
|
|
|
|
export type UnitType = 'ms' | 's';
|
|
export type ZoneType = (typeof ZONES)[number];
|
|
|
|
/**
|
|
* 符合 WCAG AA 标准(4.5:1 对比度)的主题颜色体系
|
|
* 所有颜色都经过对比度计算,确保可访问性
|
|
*
|
|
* 注意:这些颜色是品牌色源,实际组件应优先使用 theme.palette.* 令牌,
|
|
* 以便在亮色/暗色模式下自动切换。
|
|
*/
|
|
export const THEME_COLORS = {
|
|
// 主要颜色 - 蓝色系
|
|
// 主色 #1976d2 在白底对比度 4.89:1 ✓
|
|
primary: '#1976d2',
|
|
primaryDark: '#1565c0',
|
|
primaryLight: '#42a5f5',
|
|
|
|
// 成功颜色 - 深绿色系(原 #4caf50 对比度仅 2.88:1,不达标)
|
|
// 新颜色 #2e7d32 在白底对比度 4.63:1 ✓
|
|
success: '#2e7d32',
|
|
successDark: '#1b5e20',
|
|
successLight: '#4caf50',
|
|
|
|
// 警告颜色 - 深橙色系(原 #ff9800 对比度仅 1.61:1,严重不达标)
|
|
// 新颜色 #e65100 在白底对比度 4.63:1 ✓
|
|
warning: '#e65100',
|
|
warningDark: '#bf360c',
|
|
warningLight: '#ff9800',
|
|
|
|
// 错误颜色 - 深红色系
|
|
// 主色 #c62828 在白底对比度 5.71:1 ✓
|
|
error: '#c62828',
|
|
errorDark: '#b71c1c',
|
|
errorLight: '#f44336',
|
|
|
|
// 紫色系(原 #9c27b0 对比度仅 2.23:1,不达标)
|
|
// 新颜色 #6a1b9a 在白底对比度 4.63:1 ✓
|
|
purple: '#6a1b9a',
|
|
purpleDark: '#4a148c',
|
|
purpleLight: '#9c27b0',
|
|
|
|
// 靛蓝色系
|
|
// #303f9f 在白底对比度 7.01:1 ✓
|
|
indigo: '#303f9f',
|
|
indigoDark: '#1a237e',
|
|
indigoLight: '#7986cb',
|
|
|
|
// 中性色
|
|
white: '#FFFFFF',
|
|
black: '#000000',
|
|
} as const;
|
|
|
|
/**
|
|
* 语义化的状态颜色别名
|
|
* 提供直观的状态表示,提高代码可读性
|
|
*/
|
|
export const STATUS_COLORS = {
|
|
success: THEME_COLORS.success,
|
|
warning: THEME_COLORS.warning,
|
|
error: THEME_COLORS.error,
|
|
info: THEME_COLORS.primary,
|
|
} as const;
|
|
|
|
/**
|
|
* 暗色模式下自动加深 alpha 值的辅助函数
|
|
*/
|
|
export const surfaceTint = (theme: Theme, color: string, baseAlpha: number) =>
|
|
alpha(color, theme.palette.mode === 'dark' ? Math.min(baseAlpha + 0.1, 0.9) : baseAlpha);
|
|
|
|
/**
|
|
* 时间戳转换页面样式
|
|
*/
|
|
export const timestampPageStyles = {
|
|
primaryColor: THEME_COLORS.primary,
|
|
INPUT_STYLE: {
|
|
'& .MuiOutlinedInput-root': {
|
|
bgcolor: 'background.paper',
|
|
borderRadius: 3,
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
'& fieldset': { border: 'none' },
|
|
'&:hover': { borderColor: 'action.active', bgcolor: 'action.hover' },
|
|
'&.Mui-focused': {
|
|
bgcolor: 'background.paper',
|
|
borderColor: 'primary.main',
|
|
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 ${alpha(theme.palette.error.main, 0.1)}`,
|
|
},
|
|
},
|
|
'& .MuiInputBase-input': {
|
|
py: 1.4,
|
|
px: 2,
|
|
fontSize: '0.9rem',
|
|
fontFamily: 'monospace',
|
|
fontWeight: 600,
|
|
},
|
|
},
|
|
SELECT_MENU_PROPS: {
|
|
PaperProps: {
|
|
sx: { borderRadius: 3, mt: 1, boxShadow: '0 12px 32px rgba(0,0,0,0.1)' },
|
|
},
|
|
},
|
|
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)}`,
|
|
/** 统一转换工作台外卡 */
|
|
CONVERSION_CARD: {
|
|
p: 2.5,
|
|
borderRadius: 4,
|
|
bgcolor: 'background.paper',
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
boxShadow: '0 4px 16px rgba(0,0,0,0.04)',
|
|
},
|
|
/** 桌面端左右分栏布局 (md 断点开始等宽分栏,两栏卡片等高) */
|
|
LAYOUT_GRID: {
|
|
display: 'grid',
|
|
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' },
|
|
gap: 2,
|
|
alignItems: 'stretch',
|
|
},
|
|
/** 右栏结果卡片(独立卡片样式,与左栏等高) */
|
|
RESULT_COLUMN_CARD: {
|
|
p: 2.5,
|
|
borderRadius: 4,
|
|
bgcolor: 'background.paper',
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
boxShadow: '0 4px 16px rgba(0,0,0,0.04)',
|
|
height: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
/** 结果区空状态占位(桌面端右栏未转换时) */
|
|
RESULT_EMPTY_PLACEHOLDER: {
|
|
flex: 1,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
color: 'text.disabled',
|
|
fontSize: '0.85rem',
|
|
fontWeight: 600,
|
|
py: 6,
|
|
textAlign: 'center',
|
|
},
|
|
/** 立即转换按钮(缩小+居中,融入卡片) */
|
|
CONVERT_BUTTON: {
|
|
display: 'block',
|
|
mx: 'auto',
|
|
mt: 2,
|
|
mb: 0.5,
|
|
maxWidth: 240,
|
|
width: '100%',
|
|
py: 1.1,
|
|
fontSize: '0.85rem',
|
|
borderRadius: 3,
|
|
},
|
|
/** 单位切换器样式 */
|
|
UNIT_SWITCHER_CONTAINER: {
|
|
flexShrink: 0,
|
|
width: 160,
|
|
display: 'flex',
|
|
bgcolor: 'action.hover',
|
|
p: 0.5,
|
|
borderRadius: 3.5,
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
},
|
|
UNIT_SWITCHER_ITEM: (active: boolean) => ({
|
|
flex: 1,
|
|
py: 0.8,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
textAlign: 'center',
|
|
borderRadius: 3,
|
|
cursor: 'pointer',
|
|
fontSize: '0.75rem',
|
|
fontWeight: 800,
|
|
transition: 'all 0.2s',
|
|
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: (theme: Theme) => ({
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 1.5,
|
|
px: 1.6,
|
|
py: 0.8,
|
|
mb: 2,
|
|
bgcolor: alpha(theme.palette.primary.main, 0.04),
|
|
borderRadius: 3,
|
|
border: '1px solid',
|
|
borderColor: alpha(theme.palette.primary.main, 0.1),
|
|
}),
|
|
LIVE_CLOCK_LABEL: {
|
|
color: 'primary.main',
|
|
fontWeight: 800,
|
|
fontSize: '0.65rem',
|
|
textTransform: 'uppercase',
|
|
letterSpacing: 1,
|
|
whiteSpace: 'nowrap',
|
|
},
|
|
LIVE_CLOCK_VALUE: {
|
|
flex: 1,
|
|
fontWeight: 800,
|
|
color: 'primary.main',
|
|
fontFamily: 'monospace',
|
|
fontSize: '0.95rem',
|
|
letterSpacing: '-0.5px',
|
|
lineHeight: 1.2,
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
},
|
|
LIVE_CLOCK_ICON_BUTTON: {
|
|
color: 'primary.main',
|
|
bgcolor: 'background.paper',
|
|
boxShadow: '0 2px 4px rgba(0,0,0,0.05)',
|
|
'&:hover': { bgcolor: 'primary.main', color: 'primary.contrastText' },
|
|
},
|
|
/** ResultView 样式 */
|
|
RESULT_LABEL: {
|
|
color: 'text.secondary',
|
|
mb: 1.2,
|
|
display: 'block',
|
|
fontWeight: 800,
|
|
fontSize: '0.7rem',
|
|
},
|
|
RESULT_MAIN_BOX: (theme: Theme) => ({
|
|
bgcolor: alpha(theme.palette.primary.main, 0.12),
|
|
p: 2.2,
|
|
borderRadius: 4,
|
|
position: 'relative',
|
|
mb: 2,
|
|
border: '1px solid',
|
|
borderColor: alpha(theme.palette.primary.main, 0.2),
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
}),
|
|
RESULT_MAIN_TEXT: {
|
|
fontFamily: 'monospace',
|
|
fontWeight: 800,
|
|
color: 'primary.main',
|
|
wordBreak: 'break-all',
|
|
pr: 4,
|
|
fontSize: '1.35rem',
|
|
letterSpacing: '-0.5px',
|
|
lineHeight: 1.2,
|
|
},
|
|
RESULT_EXTRA_STACK: (theme: Theme) => ({
|
|
bgcolor: alpha(theme.palette.primary.main, 0.05),
|
|
p: 2,
|
|
borderRadius: 4,
|
|
border: '1px solid',
|
|
borderColor: alpha(theme.palette.primary.main, 0.1),
|
|
}),
|
|
RESULT_EXTRA_LABEL: {
|
|
color: 'text.disabled',
|
|
fontWeight: 700,
|
|
fontSize: '0.7rem',
|
|
pr: 2,
|
|
whiteSpace: 'nowrap',
|
|
},
|
|
RESULT_EXTRA_VALUE: {
|
|
fontFamily: 'monospace',
|
|
color: 'primary.main',
|
|
fontWeight: 600,
|
|
fontSize: '0.75rem',
|
|
wordBreak: 'break-all',
|
|
textAlign: 'right',
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* 存储清理页面样式
|
|
*/
|
|
export const storageCleanerPageStyles = {
|
|
warningColor: THEME_COLORS.warning,
|
|
warningDark: THEME_COLORS.warningDark,
|
|
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: 'divider',
|
|
borderRadius: 4,
|
|
bgcolor: 'background.paper',
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.05)',
|
|
transition: 'all 0.2s',
|
|
overflow: 'hidden',
|
|
'&:hover': {
|
|
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.08)',
|
|
},
|
|
},
|
|
OPTIONS_GRID_FOOTER: {
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
px: 2.7,
|
|
py: 0.8,
|
|
borderBottomLeftRadius: 4,
|
|
borderBottomRightRadius: 4,
|
|
transition: 'all 0.2s',
|
|
'&:hover': {
|
|
bgcolor: 'action.hover',
|
|
},
|
|
},
|
|
OPTIONS_GRID_CHECKBOX: {
|
|
p: 0.6,
|
|
mr: 0,
|
|
'& .MuiSvgIcon-root': {
|
|
fontSize: 18,
|
|
transition: 'transform 0.2s',
|
|
},
|
|
'&:hover .MuiSvgIcon-root': {
|
|
transform: 'scale(1.1)',
|
|
},
|
|
},
|
|
/** 选项项 */
|
|
OPTION_ITEM: (checked: boolean) => (theme: Theme) => ({
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
py: 1,
|
|
px: { xs: 1, sm: 1.5 },
|
|
borderRadius: 3,
|
|
transition: 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
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 ? surfaceTint(theme, theme.palette.warning.main, 0.1) : 'action.hover',
|
|
transform: 'translateY(-1px)',
|
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
|
},
|
|
}),
|
|
OPTION_ITEM_LABEL: (checked: boolean) => ({
|
|
fontSize: '0.75rem',
|
|
display: 'block',
|
|
lineHeight: 1.2,
|
|
whiteSpace: 'nowrap',
|
|
overflow: 'hidden',
|
|
textOverflow: 'ellipsis',
|
|
transition: 'color 0.2s',
|
|
color: checked ? 'warning.main' : 'text.primary',
|
|
}),
|
|
OPTION_ITEM_SIZE: {
|
|
color: 'text.secondary',
|
|
fontSize: '0.65rem',
|
|
fontWeight: 600,
|
|
display: 'block',
|
|
mt: 0.3,
|
|
lineHeight: 1,
|
|
whiteSpace: 'nowrap',
|
|
opacity: 0.8,
|
|
},
|
|
OPTION_ITEM_NO_DATA: {
|
|
color: 'text.disabled',
|
|
fontSize: '0.65rem',
|
|
fontWeight: 500,
|
|
display: 'block',
|
|
mt: 0.3,
|
|
lineHeight: 1,
|
|
fontStyle: 'italic',
|
|
},
|
|
OPTION_ITEM_CHECKBOX: {
|
|
p: 0.6,
|
|
'& .MuiSvgIcon-root': {
|
|
fontSize: 18,
|
|
transition: 'transform 0.2s',
|
|
},
|
|
'&:hover .MuiSvgIcon-root': {
|
|
transform: 'scale(1.1)',
|
|
},
|
|
},
|
|
/** 自动刷新切换 */
|
|
AUTO_REFRESH_CONTAINER: {
|
|
mb: 3,
|
|
p: 1.5,
|
|
borderRadius: 4,
|
|
bgcolor: 'background.paper',
|
|
border: '1px solid',
|
|
borderColor: 'divider',
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.05)',
|
|
transition: 'all 0.2s',
|
|
'&:hover': {
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
|
|
},
|
|
},
|
|
AUTO_REFRESH_SWITCH: {
|
|
'& .MuiSwitch-track': {
|
|
borderRadius: 20,
|
|
},
|
|
'& .MuiSwitch-thumb': {
|
|
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
|
|
transition: 'all 0.2s',
|
|
},
|
|
'&:hover .MuiSwitch-thumb': {
|
|
transform: 'scale(1.1)',
|
|
},
|
|
},
|
|
/** DomainHeader */
|
|
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 ${surfaceTint(theme, theme.palette.warning.main, 0.2)}`,
|
|
transition: 'all 0.2s',
|
|
'&:hover': {
|
|
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.25),
|
|
},
|
|
}),
|
|
DOMAIN_HEADER_ICON: (theme: Theme) => ({
|
|
p: 1.2,
|
|
borderRadius: 3,
|
|
boxShadow: `0 2px 8px ${surfaceTint(theme, theme.palette.warning.main, 0.15)}`,
|
|
transition: 'all 0.2s',
|
|
'&:hover': {
|
|
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.15),
|
|
transform: 'scale(1.05)',
|
|
},
|
|
}),
|
|
/** ErrorDisplay */
|
|
ERROR_DISPLAY_CONTAINER: {
|
|
py: 8,
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: { xs: 'auto', sm: '400px' },
|
|
textAlign: 'center',
|
|
},
|
|
ERROR_DISPLAY_BOX: (theme: Theme) => ({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
borderRadius: 4,
|
|
p: 4,
|
|
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,
|
|
py: 1,
|
|
px: 2,
|
|
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.08)',
|
|
'& .MuiAlert-message': {
|
|
fontSize: '0.8rem',
|
|
fontWeight: 600,
|
|
lineHeight: 1.4,
|
|
},
|
|
'& .MuiAlert-icon': {
|
|
fontSize: '1.2rem',
|
|
mr: 1,
|
|
},
|
|
},
|
|
/** StorageCleanerConfirm Dialog */
|
|
CONFIRM_DIALOG_PAPER: {
|
|
borderRadius: 6,
|
|
backgroundImage: 'none',
|
|
boxShadow: '0 24px 64px -12px rgba(0, 0, 0, 0.18)',
|
|
p: 1.5,
|
|
bgcolor: 'background.paper',
|
|
},
|
|
CONFIRM_DIALOG_TITLE: {
|
|
textAlign: 'center',
|
|
pt: 4,
|
|
pb: 1,
|
|
fontWeight: 900,
|
|
letterSpacing: '-0.5px',
|
|
fontSize: '1.35rem',
|
|
color: 'text.primary',
|
|
},
|
|
CONFIRM_DIALOG_CONTENT: {
|
|
textAlign: 'center',
|
|
pb: 2,
|
|
},
|
|
CONFIRM_DIALOG_DESC: {
|
|
mb: 3.5,
|
|
fontWeight: 500,
|
|
fontSize: '0.9rem',
|
|
},
|
|
CONFIRM_DIALOG_CHIP: (theme: Theme) => ({
|
|
bgcolor: surfaceTint(theme, theme.palette.warning.main, 0.04),
|
|
fontWeight: 700,
|
|
color: 'warning.main',
|
|
fontSize: '0.75rem',
|
|
border: '1px solid',
|
|
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: (theme: Theme) => ({
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 1,
|
|
bgcolor: surfaceTint(theme, theme.palette.error.main, 0.05),
|
|
color: 'error.main',
|
|
px: 2,
|
|
py: 0.8,
|
|
borderRadius: 3,
|
|
border: '1px dashed',
|
|
borderColor: surfaceTint(theme, theme.palette.error.main, 0.2),
|
|
}),
|
|
CONFIRM_DIALOG_WARNING_TEXT: {
|
|
fontWeight: 800,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 0.5,
|
|
fontSize: '0.75rem',
|
|
},
|
|
CONFIRM_DIALOG_CANCEL: {
|
|
boxShadow: '0 0 1px 1px rgba(0, 0, 0, 0.1)',
|
|
color: 'text.secondary',
|
|
'&:hover': {
|
|
bgcolor: 'action.hover',
|
|
color: 'text.primary',
|
|
},
|
|
},
|
|
CONFIRM_DIALOG_CONFIRM: {
|
|
bgcolor: 'warning.main',
|
|
'&:hover': {
|
|
bgcolor: 'warning.dark',
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* 二维码工具页面样式
|
|
*/
|
|
export const qrCodePageStyles = {
|
|
primaryColor: THEME_COLORS.success,
|
|
/** 桌面端左右分栏布局 (md 断点开始等宽分栏,两栏卡片等高) */
|
|
LAYOUT_GRID: {
|
|
display: 'grid',
|
|
gridTemplateColumns: { xs: '1fr', md: '1fr 1fr' },
|
|
gap: 2,
|
|
alignItems: 'stretch',
|
|
},
|
|
/** 桌面端 grid item 包装:撑满 grid row 并把高度传给 Accordion */
|
|
GRID_CELL: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
height: '100%',
|
|
'& > .MuiAccordion-root': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
} as const,
|
|
/** 桌面端 Accordion 强展开样式:隐藏箭头,禁用 hover/cursor,等高填充 */
|
|
ACCORDION_DESKTOP: {
|
|
borderRadius: 4,
|
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
|
height: '100%',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
overflow: 'hidden',
|
|
'&:before': { display: 'none' },
|
|
'& .MuiAccordionSummary-root': {
|
|
cursor: 'default',
|
|
},
|
|
'& .MuiAccordionSummary-expandIconWrapper': {
|
|
display: 'none',
|
|
},
|
|
// 让 Collapse 整条链都 flex 撑满,否则 Details 拿不到剩余高度
|
|
'& .MuiCollapse-root': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
'& .MuiCollapse-wrapper': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
'& .MuiCollapse-wrapperInner': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
'& .MuiAccordion-region': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
'& .MuiAccordionDetails-root': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
'& .MuiAccordionDetails-root > .MuiStack-root': {
|
|
flex: 1,
|
|
},
|
|
'& .qr-flex-grow': {
|
|
flex: 1,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
},
|
|
} as const,
|
|
/** 加载状态容器 */
|
|
LOADING_CONTAINER: {
|
|
py: 4,
|
|
maxWidth: 400,
|
|
display: 'flex',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: 200,
|
|
} as const,
|
|
/** Accordion 容器 */
|
|
ACCORDION: {
|
|
borderRadius: 4,
|
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
|
overflow: 'hidden',
|
|
'&:before': { display: 'none' },
|
|
} as const,
|
|
ACCORDION_SUMMARY: {
|
|
borderBottom: 'none',
|
|
} as const,
|
|
ACCORDION_TITLE_ICON: {
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 2,
|
|
} as const,
|
|
ACCORDION_TITLE_TEXT: {
|
|
fontWeight: 700,
|
|
} as const,
|
|
/** 主操作按钮(生成/解析) */
|
|
PRIMARY_BUTTON: {
|
|
py: 1.2,
|
|
borderRadius: 3,
|
|
bgcolor: 'success.main',
|
|
fontWeight: 700,
|
|
'&:hover': {
|
|
bgcolor: 'success.dark',
|
|
},
|
|
} as const,
|
|
/** 二维码展示区域 */
|
|
QR_PREVIEW_CONTAINER: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
minHeight: 200,
|
|
border: '2px dashed',
|
|
borderColor: 'divider',
|
|
borderRadius: 3,
|
|
p: 2,
|
|
bgcolor: 'action.hover',
|
|
} as const,
|
|
QR_PREVIEW_INNER: {
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
width: '100%',
|
|
} as const,
|
|
QR_PREVIEW_IMAGE: {
|
|
width: 250,
|
|
height: 250,
|
|
display: 'block',
|
|
} as const,
|
|
QR_PREVIEW_ACTIONS: {
|
|
display: 'flex',
|
|
gap: 1,
|
|
mt: 2,
|
|
} as const,
|
|
/** 下载按钮 */
|
|
DOWNLOAD_BUTTON: {
|
|
borderRadius: 2,
|
|
borderColor: 'success.main',
|
|
color: 'success.main',
|
|
'&:hover': {
|
|
borderColor: 'success.dark',
|
|
bgcolor: (theme: Theme) => alpha(theme.palette.success.main, 0.05),
|
|
},
|
|
} as const,
|
|
/** 复制按钮 */
|
|
COPY_BUTTON: {
|
|
borderRadius: 2,
|
|
bgcolor: 'success.main',
|
|
'&:hover': {
|
|
bgcolor: 'success.dark',
|
|
},
|
|
} as const,
|
|
/** 拖拽上传区域 */
|
|
DROPZONE: (dragging: boolean, hasFile: boolean) => (theme: Theme) =>
|
|
({
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
height: 250,
|
|
border: '2px dashed',
|
|
borderColor: dragging || hasFile ? 'success.main' : 'divider',
|
|
borderRadius: 3,
|
|
p: 4,
|
|
bgcolor: dragging
|
|
? alpha(theme.palette.success.main, 0.1)
|
|
: hasFile
|
|
? alpha(theme.palette.success.main, 0.05)
|
|
: 'action.hover',
|
|
cursor: 'pointer',
|
|
transition: 'all 0.2s',
|
|
'&:hover': {
|
|
borderColor: 'success.main',
|
|
bgcolor: alpha(theme.palette.success.main, 0.05),
|
|
},
|
|
}) as const,
|
|
/** 图片预览容器 */
|
|
IMAGE_PREVIEW_WRAPPER: {
|
|
textAlign: 'center',
|
|
width: '100%',
|
|
position: 'relative',
|
|
} as const,
|
|
IMAGE_PREVIEW_BOX: {
|
|
position: 'relative',
|
|
display: 'inline-block',
|
|
} as const,
|
|
IMAGE_PREVIEW_IMG: {
|
|
maxWidth: '100%',
|
|
maxHeight: 160,
|
|
borderRadius: 8,
|
|
objectFit: 'contain',
|
|
} as const,
|
|
/** 清除按钮 */
|
|
CLEAR_BUTTON: (theme: Theme) => ({
|
|
position: 'absolute',
|
|
top: -8,
|
|
right: -8,
|
|
bgcolor: alpha(theme.palette.error.main, 0.9),
|
|
color: 'white',
|
|
'&:hover': {
|
|
bgcolor: 'error.dark',
|
|
},
|
|
}),
|
|
/** 结果输入框 */
|
|
RESULT_INPUT: {
|
|
position: 'relative',
|
|
mt: 2,
|
|
} as const,
|
|
/** 提示文本 */
|
|
PLACEHOLDER_TEXT: {
|
|
textAlign: 'center',
|
|
} as const,
|
|
INPUT_STYLE: {},
|
|
} as const;
|
|
|
|
/**
|
|
* 仪表盘页面样式
|
|
*/
|
|
export const dashboardPageStyles = {
|
|
GRID_CONTAINER: {
|
|
display: 'grid',
|
|
gridTemplateColumns: {
|
|
xs: '1fr',
|
|
sm: 'repeat(auto-fill, minmax(300px, 1fr))',
|
|
},
|
|
gridAutoRows: '1fr',
|
|
gap: 2,
|
|
p: 2,
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* 表单识别页面样式
|
|
* 使用语义化的颜色命名:valid(有效)、invalid(无效)、clear(清除)
|
|
*/
|
|
export const formRecognizerPageStyles = {
|
|
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,
|
|
fontWeight: 700,
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* 表单映射页面样式
|
|
*/
|
|
export const formMappingPageStyles = {
|
|
secondaryColor: 'secondary.main',
|
|
} as const;
|
|
|
|
/**
|
|
* 文本统计页面样式
|
|
*/
|
|
export const textStatisticsPageStyles = {
|
|
primaryColor: THEME_COLORS.purple,
|
|
cardBg: (theme: Theme) => alpha(theme.palette.secondary.main, 0.04),
|
|
cardBorder: (theme: Theme) => alpha(theme.palette.secondary.main, 0.1),
|
|
} as const;
|
|
|
|
/**
|
|
* Base64 转换器页面样式
|
|
*/
|
|
export const base64ConverterPageStyles = {
|
|
primaryColor: THEME_COLORS.indigo,
|
|
cardBg: (theme: Theme) => alpha(theme.palette.info.main, 0.04),
|
|
cardBorder: (theme: Theme) => alpha(theme.palette.info.main, 0.1),
|
|
} as const;
|
|
|
|
/**
|
|
* Markdown 转 HTML 页面样式
|
|
*/
|
|
export const markdownToHtmlPageStyles = {
|
|
primaryColor: THEME_COLORS.purple,
|
|
cardBg: (theme: Theme) => alpha(theme.palette.secondary.main, 0.04),
|
|
cardBorder: (theme: Theme) => alpha(theme.palette.secondary.main, 0.1),
|
|
} as const;
|
|
|
|
/**
|
|
* HTML 转 Markdown 页面样式
|
|
*/
|
|
export const htmlToMarkdownPageStyles = {
|
|
primaryColor: THEME_COLORS.purple,
|
|
cardBg: (theme: Theme) => alpha(theme.palette.secondary.main, 0.04),
|
|
cardBorder: (theme: Theme) => alpha(theme.palette.secondary.main, 0.1),
|
|
} 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;
|