import React, { useState, useRef, useEffect, ReactNode } from 'react'; import { GripVertical } from 'lucide-react'; interface DraggablePanelProps { title: string | ReactNode; icon?: ReactNode; isVisible: boolean; onClose: () => void; width?: number; maxHeight?: number; initialPosition?: { x: number; y: number }; headerActions?: ReactNode; children: ReactNode; footer?: ReactNode | false; } /** * 可拖动面板通用组件 * 提供标题栏拖动、关闭按钮等基础功能 */ export const DraggablePanel: React.FC = ({ title, icon, isVisible, onClose, width = 400, maxHeight = 600, initialPosition = { x: 20, y: 100 }, headerActions, children, footer }) => { const [position, setPosition] = useState(initialPosition); const [isDragging, setIsDragging] = useState(false); const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 }); const panelRef = useRef(null); useEffect(() => { if (!isVisible) return; const handleMouseMove = (e: MouseEvent) => { if (!isDragging) return; const newX = e.clientX - dragOffset.x; const newY = e.clientY - dragOffset.y; // 限制面板在视口内 const maxX = window.innerWidth - width; const maxY = window.innerHeight - 100; setPosition({ x: Math.max(0, Math.min(newX, maxX)), y: Math.max(0, Math.min(newY, maxY)) }); }; const handleMouseUp = () => { setIsDragging(false); }; if (isDragging) { document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); } return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; }, [isDragging, dragOffset, width]); const handleMouseDown = (e: React.MouseEvent) => { if (!panelRef.current) return; const rect = panelRef.current.getBoundingClientRect(); setDragOffset({ x: e.clientX - rect.left, y: e.clientY - rect.top }); setIsDragging(true); }; if (!isVisible) return null; return (
{/* 可拖动标题栏 */}
{icon} {typeof title === 'string' ? ( {title} ) : ( title )}
{headerActions}
{/* 内容区域 */}
{children}
{/* 页脚 */} {footer && (
{footer}
)}
); };