Files
testing-tool/components/GlobalSnackbar.tsx
T
LingandRX be5e2f02ee Develop (#9)
* feat: optimize popup standalone window layout and enhance storage cleaner synchronization

* docs: 更新README文档并删除过时文件

- 更新README文档,添加项目结构、功能特性和路由系统等详细信息
- 删除不再使用的文档文件,包括CLAUDE.md、GEMINI.md和多个设计规范文档
- 清理项目中的过时配置文件和计划文档

* feat: 添加 Vitest 测试框架和组件测试

- 添加 Vitest 配置 (vitest.config.ts, vitest.setup.ts)
- 创建组件测试: Button, ToolCard, GlobalSnackbar, TopBar, RouterContainer, StorageCleanerConfirm
- 创建工具测试: routes, storageCleaner
- 修复 background.ts 监听器参数问题
- 修复 options/App.tsx 硬编码默认值
- 更新 lint-staged.config.mjs (添加 .mjs 支持, 添加 --no-warn-ignored)
- 更新 tsconfig.json (添加测试类型支持, 移除测试文件排除)
- 更新 package.json (添加测试脚本和依赖)

* fix: 修复 StorageCleanerPage Chrome API 监听器内存泄漏

使用 useRef 模式存储 loadInfo 函数引用,避免依赖数组变化导致的监听器重复注册问题

* refactor(popup): 优化 OpenUrl 页面样式和导航逻辑

重构 OpenUrl 页面输入框样式,改进聚焦状态效果
移除 RouterProvider 依赖,直接通过存储设置侧边栏路由
在 OpenUrlViewer 页面添加加载状态指示器和错误处理
监听存储变化实现 URL 自动更新

* feat(ui): 优化存储清理页面UI和交互效果

重构存储清理页面组件,增强视觉层次和交互体验:
- 使用新的错误提示样式和布局
- 改进选项卡片样式,增加悬停动画和选中状态
- 调整整体间距和排版,提升视觉一致性
- 添加微交互效果如悬停缩放和阴影
- 优化颜色方案和过渡动画
- 统一组件尺寸和字体层级

* feat: 添加二维码工具页面,支持URL转二维码和二维码解析功能

* chore: update package-lock.json (npm audit fix)

* refactor(主题): 将页面样式抽离到统一配置文件

将各页面的颜色和样式配置抽离到config/pageTheme.ts中统一管理
优化测试用例中使用each替代forEach
更新路由测试以包含新的qrCode页面

* feat(二维码页面): 添加复制二维码功能并优化样式

添加复制二维码到剪贴板的功能,并调整按钮布局和样式。同时将 ContentCopyIcon 导入位置调整到其他图标导入之后,并修复缩进问题。在 tsconfig.json 中添加 vitest/globals 类型支持。

* feat(theme): 为所有页面添加统一的背景色和卡片背景色

为应用中的所有页面添加了统一的浅灰色背景(#f5f5f5)和白色卡片背景(#ffffff),以保持视觉一致性。修改了ToolCard组件以支持自定义卡片背景色,并更新了所有相关页面使用新的主题配置。

* feat: 添加复制按钮组件并优化现有复制功能

refactor(utils): 创建剪贴板工具函数
feat(components): 新增可复用的CopyButton组件
refactor(pages): 在QrCodePage和TimestampPage中使用CopyButton
style: 格式化代码并调整部分样式

* refactor(存储): 统一qrCode相关存储键名

将'qrCode/expanded'重命名为'qrCode/qrExpanded'以保持命名一致性

* feat: 添加二维码工具功能并更新项目配置

- 新增二维码工具页面及相关组件和工具函数
- 添加 MIT 许可证文件
- 更新 package.json 配置为公开项目
- 更新 README 文档说明新功能
2026-04-19 15:31:14 +08:00

149 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react';
import { Snackbar, Alert, type SxProps, type Theme, alpha, Portal } from '@mui/material';
export type SnackbarSeverity = 'success' | 'info' | 'warning' | 'error';
export interface GlobalSnackbarProps {
/** 消息内容 */
message: string;
/** 是否显示 */
open: boolean;
/** 关闭回调 */
onClose: () => void;
/** 消息级别:影响颜色 */
severity?: SnackbarSeverity;
/** 自动隐藏时间,毫秒,0 不自动关闭 */
autoHideDuration?: number;
/** 弹出位置 */
anchorOrigin?: {
vertical: 'top' | 'bottom';
horizontal: 'left' | 'center' | 'right';
};
/** 是否使用 Alert 包裹(false 则使用原生 Snackbar message */
showAlert?: boolean;
/** Alert 是否隐藏图标 */
hideIcon?: boolean;
/** 自定义样式,透传给 Snackbar */
sx?: SxProps<Theme>;
/** 自定义样式,透传给 Alert(仅当 showAlert=true 时生效) */
alertSx?: SxProps<Theme>;
}
export interface SnackbarOptions {
severity?: SnackbarSeverity;
autoHideDuration?: number;
hideIcon?: boolean;
showAlert?: boolean;
}
export interface UseSnackbarResult {
snackbarProps: GlobalSnackbarProps;
showMessage: (message: string, options?: SnackbarOptions) => void;
closeMessage: () => void;
}
const defaultProps: Required<
Pick<
GlobalSnackbarProps,
'severity' | 'autoHideDuration' | 'anchorOrigin' | 'showAlert' | 'hideIcon'
>
> = {
severity: 'info',
autoHideDuration: 2000,
anchorOrigin: { vertical: 'bottom', horizontal: 'center' },
showAlert: true,
hideIcon: false,
};
export function GlobalSnackbar({
message,
open,
onClose,
severity = defaultProps.severity,
autoHideDuration = defaultProps.autoHideDuration,
showAlert = defaultProps.showAlert,
hideIcon = defaultProps.hideIcon,
}: GlobalSnackbarProps) {
// 使用 Portal 将 Snackbar 传送到 DOM 顶层 (body 标签下)
return (
<Portal>
<Snackbar
open={open}
autoHideDuration={autoHideDuration}
onClose={onClose}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
disableWindowBlurListener
sx={{
zIndex: 999999,
// 确保距离底部的间距
bottom: { xs: '24px', sm: '24px' },
}}
>
{showAlert ? (
<Alert
severity={severity}
variant="filled"
icon={hideIcon ? false : undefined}
sx={{
borderRadius: '50px',
px: 2.5,
py: 0.2,
minWidth: '140px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 800,
fontSize: '0.75rem',
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' },
}}
>
{message}
</Alert>
) : undefined}
</Snackbar>
</Portal>
);
}
export function useSnackbar(initialOptions?: SnackbarOptions): UseSnackbarResult {
const [open, setOpen] = useState(false);
const [message, setMessage] = useState('');
const [options, setOptions] = useState<SnackbarOptions>(initialOptions || {});
const showMessage = (newMessage: string, newOptions: SnackbarOptions = {}) => {
setMessage(newMessage);
setOptions({ ...initialOptions, ...newOptions });
setOpen(true);
};
const closeMessage = () => {
setOpen(false);
};
const handleClose = (_event?: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') return;
closeMessage();
};
const snackbarProps: GlobalSnackbarProps = {
message,
open,
onClose: handleClose,
severity: options.severity,
autoHideDuration: options.autoHideDuration,
hideIcon: options.hideIcon,
};
return {
snackbarProps,
showMessage,
closeMessage,
};
}
export default GlobalSnackbar;