import React, { useEffect, useRef } from 'react'; import ReactDOM from 'react-dom'; interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; footer?: React.ReactNode; size?: 'sm' | 'md' | 'lg'; closeOnClickOutside?: boolean; } const Modal: React.FC = ({ isOpen, onClose, title, children, footer, size = 'md', closeOnClickOutside = true }) => { const modalRef = useRef(null); // 处理ESC按键关闭模态框 useEffect(() => { const handleEscKey = (event: KeyboardEvent) => { if (event.key === 'Escape' && isOpen) { onClose(); } }; window.addEventListener('keydown', handleEscKey); return () => { window.removeEventListener('keydown', handleEscKey); }; }, [isOpen, onClose]); // 阻止点击模态框内部时传播到外部背景 const handleModalClick = (e: React.MouseEvent) => { e.stopPropagation(); }; // 处理点击背景关闭模态框 const handleBackdropClick = (e: React.MouseEvent) => { if (closeOnClickOutside && modalRef.current && !modalRef.current.contains(e.target as Node)) { onClose(); } }; // 防止模态框打开时页面滚动 useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isOpen]); // 获取模态框大小样式 const getSizeClass = () => { switch (size) { case 'sm': return 'max-w-md'; case 'md': return 'max-w-lg'; case 'lg': return 'max-w-2xl'; default: return 'max-w-lg'; } }; if (!isOpen) return null; // 使用React Portal将模态框渲染到DOM树的最顶层 return ReactDOM.createPortal(
{title && (

{title}

)}
{children}
{footer && (
{footer}
)}
, document.body ); }; // 预定义的模态框按钮布局 export const ModalFooter: React.FC<{ children: React.ReactNode; className?: string; }> = ({ children, className = '' }) => { return (
{children}
); }; // 预定义的模态框按钮 export const ModalButton: React.FC<{ children: React.ReactNode; onClick: () => void; variant?: 'primary' | 'secondary' | 'danger'; className?: string; }> = ({ children, onClick, variant = 'secondary', className = '' }) => { const getVariantClass = () => { switch (variant) { case 'primary': return 'text-blue-600 hover:bg-blue-50'; case 'secondary': return 'text-gray-600 hover:bg-gray-50'; case 'danger': return 'text-red-600 hover:bg-red-50'; default: return 'text-gray-600 hover:bg-gray-50'; } }; return ( ); }; export default Modal;