diff --git a/src/components/ui/ConfirmDialog.tsx b/src/components/ui/ConfirmDialog.tsx new file mode 100644 index 0000000..70f5393 --- /dev/null +++ b/src/components/ui/ConfirmDialog.tsx @@ -0,0 +1,175 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { X, AlertTriangle } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +interface ConfirmDialogProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + description?: string; + confirmText?: string; + cancelText?: string; + variant?: 'default' | 'danger'; + loading?: boolean; + children?: React.ReactNode; +} + +export function ConfirmDialog({ + isOpen, + onClose, + onConfirm, + title, + description, + confirmText = '确认', + cancelText = '取消', + variant = 'default', + loading = false, + children, +}: ConfirmDialogProps) { + const dialogRef = useRef(null); + + // ESC 关闭 + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape' && isOpen && !loading) { + onClose(); + } + }; + + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [isOpen, onClose, loading]); + + // 点击外部关闭 + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if ( + dialogRef.current && + !dialogRef.current.contains(e.target as Node) && + !loading + ) { + onClose(); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => document.removeEventListener('mousedown', handleClickOutside); + }, [isOpen, onClose, loading]); + + // 禁止背景滚动 + useEffect(() => { + if (isOpen) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } + return () => { + document.body.style.overflow = ''; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+ {/* 背景遮罩 */} +
+ + {/* 对话框 */} +
+ {/* 头部 */} +
+
+ {variant === 'danger' && ( +
+ +
+ )} +

+ {title} +

+
+ +
+ + {/* 内容 */} +
+ {description && ( +

+ {description} +

+ )} + {children} +
+ + {/* 底部按钮 */} +
+ + +
+
+
+ ); +}