3c9c9e740e
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(主题): 添加暗色模式支持并重构主题系统
370 lines
10 KiB
TypeScript
370 lines
10 KiB
TypeScript
/**
|
||
* GlobalSnackbar - 全局 Snackbar 消息提示组件及 Provider
|
||
*
|
||
* 提供可复用的 Toast 消息提示功能,支持三种使用方式:
|
||
* 1. 作为受控组件使用:通过 props 控制显示状态
|
||
* 2. 通过 useSnackbarState Hook 使用:在组件内部自动管理状态
|
||
* 3. 通过 SnackbarProvider 和 useSnackbar Hook 使用:全局单例模式
|
||
*
|
||
* @module GlobalSnackbar
|
||
* @version 1.1.0
|
||
*
|
||
* @example
|
||
* ```tsx
|
||
* // 方式一:受控组件
|
||
* <GlobalSnackbar
|
||
* message="操作成功"
|
||
* open={isOpen}
|
||
* onClose={() => setIsOpen(false)}
|
||
* severity="success"
|
||
* />
|
||
*
|
||
* // 方式二:Hook 方式 (局部状态)
|
||
* const { snackbarProps, showMessage } = useSnackbarState();
|
||
* showMessage('Hello!', { severity: 'info' });
|
||
*
|
||
* // 方式三:Context 方式 (全局状态)
|
||
* // 在根组件包裹 Provider
|
||
* <SnackbarProvider>
|
||
* <App />
|
||
* </SnackbarProvider>
|
||
*
|
||
* // 在子组件中使用
|
||
* const { showMessage } = useSnackbar();
|
||
* showMessage('Global Message');
|
||
* ```
|
||
*/
|
||
|
||
import {
|
||
JSX,
|
||
useState,
|
||
createContext,
|
||
useContext,
|
||
type ReactNode,
|
||
type SyntheticEvent,
|
||
} from 'react';
|
||
import { Snackbar, Alert, type SxProps, type Theme, alpha, Portal } from '@mui/material';
|
||
|
||
/**
|
||
* Snackbar 消息严重程度类型
|
||
* @description 决定 Alert 组件的颜色和图标
|
||
* - success: 绿色,成功提示
|
||
* - info: 蓝色,信息提示
|
||
* - warning: 橙色,警告提示
|
||
* - error: 红色,错误提示
|
||
*/
|
||
export type SnackbarSeverity = 'success' | 'info' | 'warning' | 'error';
|
||
|
||
/**
|
||
* GlobalSnackbar 组件的属性接口
|
||
* @interface GlobalSnackbarProps
|
||
*/
|
||
export interface GlobalSnackbarProps {
|
||
/** 消息内容,要显示的提示文本 */
|
||
message: string;
|
||
/** 是否显示 Snackbar */
|
||
open: boolean;
|
||
/** 关闭回调函数 */
|
||
onClose: () => void;
|
||
/** 消息级别,影响颜色和图标样式,默认 'info' */
|
||
severity?: SnackbarSeverity;
|
||
/** 自动隐藏时间(毫秒),设为 0 则不自动关闭,默认 2000 */
|
||
autoHideDuration?: number;
|
||
/** Snackbar 弹出位置,默认 { vertical: 'bottom', horizontal: 'center' } */
|
||
anchorOrigin?: {
|
||
vertical: 'top' | 'bottom';
|
||
horizontal: 'left' | 'center' | 'right';
|
||
};
|
||
/** 是否使用 Alert 组件包裹,false 则使用原生 Snackbar message,默认 true */
|
||
showAlert?: boolean;
|
||
/** 是否隐藏 Alert 图标,默认 false */
|
||
hideIcon?: boolean;
|
||
/** 自定义样式,透传给外层 Snackbar 组件 */
|
||
sx?: SxProps<Theme>;
|
||
/** 自定义样式,透传给内层 Alert 组件(仅 showAlert=true 时生效) */
|
||
alertSx?: SxProps<Theme>;
|
||
}
|
||
|
||
/**
|
||
* showMessage 方法的选项配置
|
||
* @interface SnackbarOptions
|
||
*/
|
||
export interface SnackbarOptions {
|
||
/** 消息级别:success | info | warning | error */
|
||
severity?: SnackbarSeverity;
|
||
/** 自动隐藏时间(毫秒),设为 0 则不自动关闭 */
|
||
autoHideDuration?: number;
|
||
/** 是否隐藏 Alert 图标 */
|
||
hideIcon?: boolean;
|
||
/** 是否使用 Alert 组件包裹 */
|
||
showAlert?: boolean;
|
||
}
|
||
|
||
/**
|
||
* useSnackbarState Hook 的返回值类型
|
||
* @interface UseSnackbarStateResult
|
||
*/
|
||
export interface UseSnackbarStateResult {
|
||
/** 传递给 GlobalSnackbar 组件的属性对象 */
|
||
snackbarProps: GlobalSnackbarProps;
|
||
/** 显示消息的方法 */
|
||
showMessage: (message: string, options?: SnackbarOptions) => void;
|
||
/** 关闭消息的方法 */
|
||
closeMessage: () => void;
|
||
}
|
||
|
||
/**
|
||
* GlobalSnackbar 组件的默认属性配置
|
||
* @description 提供类型安全的默认值选择
|
||
*/
|
||
const defaultProps: Required<
|
||
Pick<
|
||
GlobalSnackbarProps,
|
||
'severity' | 'autoHideDuration' | 'anchorOrigin' | 'showAlert' | 'hideIcon'
|
||
>
|
||
> = {
|
||
severity: 'info',
|
||
autoHideDuration: 2000,
|
||
anchorOrigin: { vertical: 'bottom', horizontal: 'center' },
|
||
showAlert: true,
|
||
hideIcon: false,
|
||
};
|
||
|
||
/**
|
||
* GlobalSnackbar 组件
|
||
*
|
||
* 全局消息提示的展示组件,支持受控和非受控两种使用模式。
|
||
* 使用 MUI Snackbar 和 Alert 组件实现消息提示功能。
|
||
*
|
||
* @param {GlobalSnackbarProps} props - 组件属性
|
||
* @returns {JSX.Element}
|
||
*/
|
||
export function GlobalSnackbar({
|
||
message,
|
||
open,
|
||
onClose,
|
||
severity = defaultProps.severity,
|
||
autoHideDuration = defaultProps.autoHideDuration,
|
||
showAlert = defaultProps.showAlert,
|
||
hideIcon = defaultProps.hideIcon,
|
||
}: GlobalSnackbarProps): JSX.Element {
|
||
return (
|
||
<Portal>
|
||
<Snackbar
|
||
open={open}
|
||
autoHideDuration={autoHideDuration}
|
||
onClose={onClose}
|
||
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
||
disableWindowBlurListener
|
||
sx={{
|
||
zIndex: 999999,
|
||
bottom: { xs: '24px', sm: '24px' },
|
||
left: '50%',
|
||
transform: 'translateX(-50%)',
|
||
minWidth: '140px',
|
||
}}
|
||
>
|
||
{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' },
|
||
'& .MuiAlert-message': { padding: '6px 0' },
|
||
}}
|
||
>
|
||
{message}
|
||
</Alert>
|
||
) : (
|
||
<div>{message}</div>
|
||
)}
|
||
</Snackbar>
|
||
</Portal>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* useSnackbarState - 消息提示的 Hook 方式
|
||
*
|
||
* 提供状态管理的 Snackbar 功能,自动处理 open、message 等状态。
|
||
* 适合在组件内部使用,无需额外的状态管理代码。
|
||
*
|
||
* @param {SnackbarOptions} [initialOptions] - 初始配置选项
|
||
* @returns {UseSnackbarStateResult} 包含 snackbarProps 和操作方法的对象
|
||
*
|
||
* @description
|
||
* - 自动管理 Snackbar 的显示/隐藏状态
|
||
* - 支持链式调用 showMessage
|
||
* - 合并初始选项和调用时选项
|
||
*
|
||
* @example
|
||
* ```tsx
|
||
* function MyComponent() {
|
||
* const { snackbarProps, showMessage, closeMessage } = useSnackbarState({
|
||
* severity: 'info',
|
||
* autoHideDuration: 3000,
|
||
* });
|
||
*
|
||
* const handleSave = () => {
|
||
* // 业务逻辑...
|
||
* showMessage('保存成功!', { severity: 'success' });
|
||
* };
|
||
*
|
||
* return (
|
||
* <>
|
||
* <button onClick={handleSave}>保存</button>
|
||
* <GlobalSnackbar {...snackbarProps} />
|
||
* </>
|
||
* );
|
||
* }
|
||
* ```
|
||
*/
|
||
export function useSnackbarState(initialOptions?: SnackbarOptions): UseSnackbarStateResult {
|
||
// Snackbar 显示状态
|
||
const [open, setOpen] = useState(false);
|
||
// 当前显示的消息内容
|
||
const [message, setMessage] = useState('');
|
||
// 消息配置选项
|
||
const [options, setOptions] = useState<SnackbarOptions>(initialOptions || {});
|
||
|
||
/**
|
||
* 显示消息
|
||
*
|
||
* @param {string} newMessage - 要显示的消息文本
|
||
* @param {SnackbarOptions} [newOptions={}] - 新的配置选项
|
||
*
|
||
* @description
|
||
* - 合并初始选项和新的调用选项
|
||
* - 新选项会覆盖初始选项
|
||
*/
|
||
const showMessage = (newMessage: string, newOptions: SnackbarOptions = {}) => {
|
||
setMessage(newMessage);
|
||
setOptions({ ...initialOptions, ...newOptions });
|
||
setOpen(true);
|
||
};
|
||
|
||
/**
|
||
* 关闭消息
|
||
*
|
||
* @description
|
||
* - 直接将 open 状态设置为 false
|
||
*/
|
||
const closeMessage = () => {
|
||
setOpen(false);
|
||
};
|
||
|
||
const handleClose = (_event?: SyntheticEvent | Event, reason?: string) => {
|
||
if (reason === 'clickaway') return;
|
||
closeMessage();
|
||
};
|
||
|
||
/**
|
||
* 传递给 GlobalSnackbar 组件的属性
|
||
*
|
||
* @description
|
||
* - 组合当前状态和选项为完整的组件 props
|
||
* - onClose 使用 handleClose 包装后的版本
|
||
*/
|
||
const snackbarProps: GlobalSnackbarProps = {
|
||
message,
|
||
open,
|
||
onClose: handleClose,
|
||
severity: options.severity,
|
||
autoHideDuration: options.autoHideDuration,
|
||
hideIcon: options.hideIcon,
|
||
};
|
||
|
||
return {
|
||
snackbarProps,
|
||
showMessage,
|
||
closeMessage,
|
||
};
|
||
}
|
||
|
||
// --- Context & Provider ---
|
||
|
||
/**
|
||
* Snackbar Context 的值类型定义
|
||
*/
|
||
interface SnackbarContextValue {
|
||
showMessage: (message: string, options?: SnackbarOptions) => void;
|
||
closeMessage: () => void;
|
||
}
|
||
|
||
const SnackbarContext = createContext<SnackbarContextValue | null>(null);
|
||
|
||
/**
|
||
* SnackbarProvider 组件的 props 类型
|
||
*/
|
||
interface SnackbarProviderProps {
|
||
children: ReactNode;
|
||
initialOptions?: SnackbarOptions;
|
||
}
|
||
|
||
/**
|
||
* SnackbarProvider 组件
|
||
*
|
||
* 全局消息提示的 Provider 组件,需要包裹在应用根组件外层。
|
||
*/
|
||
export function SnackbarProvider({ children, initialOptions }: SnackbarProviderProps): JSX.Element {
|
||
const { snackbarProps, showMessage, closeMessage } = useSnackbarState(initialOptions);
|
||
|
||
return (
|
||
<SnackbarContext.Provider value={{ showMessage, closeMessage }}>
|
||
{children}
|
||
<GlobalSnackbar {...snackbarProps} />
|
||
</SnackbarContext.Provider>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* useSnackbar - 在子组件中获取 Snackbar 上下文的 Hook
|
||
*
|
||
* @param {SnackbarOptions} [options] - 钩子级别的默认配置(如 autoHideDuration)
|
||
* @returns {SnackbarContextValue} - 包含 showMessage 和 closeMessage 的对象
|
||
* @throws {Error} - 如果不在 SnackbarProvider 内部调用,抛出错误
|
||
*
|
||
* @description
|
||
* 选项合并策略:
|
||
* 1. 调用 showMessage 时传入的 callOptions 优先级最高
|
||
* 2. useSnackbar(options) 传入的 Hook 级别配置次之
|
||
* 3. SnackbarProvider(initialOptions) 传入的全局配置优先级最低
|
||
*/
|
||
export function useSnackbar(options?: SnackbarOptions): SnackbarContextValue {
|
||
const context = useContext(SnackbarContext);
|
||
if (!context) {
|
||
throw new Error('useSnackbar must be used within SnackbarProvider');
|
||
}
|
||
|
||
// 包装 showMessage 以支持 Hook 级别的 initialOptions
|
||
const wrappedShowMessage = (message: string, callOptions?: SnackbarOptions) => {
|
||
// 采用防御性编程,确保 options 和 callOptions 为空时也能正常工作
|
||
// 优先级:callOptions > options
|
||
const mergedOptions: SnackbarOptions = {
|
||
...(options || {}),
|
||
...(callOptions || {}),
|
||
};
|
||
context.showMessage(message, mergedOptions);
|
||
};
|
||
|
||
return {
|
||
...context,
|
||
showMessage: wrappedShowMessage,
|
||
};
|
||
}
|
||
|
||
export default GlobalSnackbar;
|