/** * 全局提示模态框组件 * * 用于显示成功、错误、警告和信息提示消息 * 支持自动关闭、手动关闭、自定义图标和操作按钮 */ import { useState, useEffect, useCallback } from 'react'; import { createPortal } from 'react-dom'; import messageModalStyles from '~/styles/components/message-modal.css?url'; // 消息类型 export type MessageType = 'success' | 'error' | 'warning' | 'info'; // 组件属性 interface MessageModalProps { // 是否显示模态框 isOpen: boolean; // 关闭模态框的回调 onClose: () => void; // 模态框标题 title?: string; // 模态框消息内容 message: string; // 消息类型 type?: MessageType; // 是否自动关闭 autoClose?: boolean; // 自动关闭的延迟时间(毫秒) autoCloseDelay?: number; // 确认按钮文本 confirmText?: string; // 确认按钮回调 onConfirm?: () => void; // 取消按钮文本 cancelText?: string; // 是否显示关闭按钮 showCloseButton?: boolean; // 自定义图标 customIcon?: React.ReactNode; // 自定义内容 children?: React.ReactNode; } // 默认自动关闭延迟 const DEFAULT_AUTO_CLOSE_DELAY = 3000; // 导出样式 export function links() { return [{ rel: 'stylesheet', href: messageModalStyles }]; } export function MessageModal({ isOpen, onClose, title, message, type = 'info', autoClose = false, autoCloseDelay = DEFAULT_AUTO_CLOSE_DELAY, confirmText = '确定', onConfirm, cancelText = '取消', showCloseButton = true, customIcon, children }: MessageModalProps) { const [isClosing, setIsClosing] = useState(false); const [portalElement, setPortalElement] = useState(null); // 在客户端渲染时获取 portal 容器 useEffect(() => { if (typeof document !== 'undefined') { let element = document.getElementById('message-modal-portal'); if (!element) { element = document.createElement('div'); element.id = 'message-modal-portal'; document.body.appendChild(element); } setPortalElement(element); } }, []); // 处理关闭动画 const handleClose = useCallback(() => { setIsClosing(true); setTimeout(() => { setIsClosing(false); onClose(); }, 300); // 动画持续时间 }, [onClose]); // 处理确认 const handleConfirm = useCallback(() => { if (onConfirm) { onConfirm(); } handleClose(); }, [onConfirm, handleClose]); // 自动关闭 useEffect(() => { if (isOpen && autoClose) { const timer = setTimeout(() => { handleClose(); }, autoCloseDelay); return () => clearTimeout(timer); } }, [isOpen, autoClose, autoCloseDelay, handleClose]); // 关闭按钮键盘交互 const handleKeyDown = useCallback((e: React.KeyboardEvent) => { if (e.key === 'Escape') { handleClose(); } }, [handleClose]); // 渲染图标 const renderIcon = () => { if (customIcon) { return customIcon; } switch (type) { case 'success': return ; case 'error': return ; case 'warning': return ; case 'info': default: return ; } }; // 如果模态框未打开,不渲染 if (!isOpen || !portalElement) { return null; } // 使用 Portal 渲染模态框 return createPortal(
e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby="message-modal-title" aria-describedby="message-modal-content" > {showCloseButton && ( )}
{renderIcon()}
{title && (

{title}

)}
{message}
{children && (
{children}
)}
{onConfirm && ( <> )} {!onConfirm && ( )}
, portalElement ); } // 创建全局消息服务 type ShowMessageOptions = Omit; class MessageService { private static instance: MessageService; private showModal: ((options: ShowMessageOptions) => void) | null = null; private constructor() {} static getInstance(): MessageService { if (!MessageService.instance) { MessageService.instance = new MessageService(); } return MessageService.instance; } registerShowModal(showFn: (options: ShowMessageOptions) => void): void { this.showModal = showFn; } success(message: string, options?: Partial): void { this.show({ ...options, message, type: 'success' }); } error(message: string, options?: Partial): void { this.show({ ...options, message, type: 'error' }); } warning(message: string, options?: Partial): void { this.show({ ...options, message, type: 'warning' }); } info(message: string, options?: Partial): void { this.show({ ...options, message, type: 'info' }); } show(options: ShowMessageOptions): void { if (this.showModal) { this.showModal(options); } else { console.error('MessageService: showModal is not registered'); } } } export const messageService = MessageService.getInstance(); /** * 全局消息模态框容器 * 用于在应用根组件中挂载消息模态框服务 */ export function MessageModalProvider({ children }: { children: React.ReactNode }) { const [messageOptions, setMessageOptions] = useState(null); const [isOpen, setIsOpen] = useState(false); useEffect(() => { messageService.registerShowModal((options) => { setMessageOptions(options); setIsOpen(true); }); }, []); const handleClose = () => { setIsOpen(false); }; return ( <> {children} {messageOptions && ( )} ); }