// ============================================================ // LeftPanel — 左列容器(模型选择 + 可拖拽分割线 + 任务搜索 + 任务记录) // 纵向拖拽分隔线支持鼠标调整上下区域高度比例 // // 职责:布局 + edition 分发,子组件自行管理数据和搜索状态 // ============================================================ import { useState, useRef, useEffect, useCallback } from 'react'; import { theme as antTheme } from 'antd'; import { ModelSelector } from '../features/ModelSelector'; import { TaskHistory } from '../features/TaskHistory'; import { useModelList } from '@/hooks/use-api'; // ---------- 常量 ---------- /** 模型选择区最小高度 */ const TOP_MIN = 120; /** 任务列表区最小高度 */ const BOTTOM_MIN = 200; /** 默认模型选择区百分比 */ const TOP_DEFAULT_PCT = 0.40; /** 拖拽分隔条高度 */ const DIVIDER_HEIGHT = 6; // ---------- 组件 ---------- export function LeftPanel() { const { token } = antTheme.useToken(); // 面板容器引用 const containerRef = useRef(null); // ======== 模型数据(提升到父组件,避免两个子组件重复请求) ======== const { data: models, loading, error, groupedModels, refetch } = useModelList(); // 模型选择区高度(像素) const [topHeight, setTopHeight] = useState(null); // 拖拽状态 const [dragging, setDragging] = useState(false); // hover 状态 const [hovered, setHovered] = useState(false); const dragState = useRef<{ startY: number; startHeight: number; }>({ startY: 0, startHeight: 0 }); // 初始化:按百分比计算顶部高度 const initHeight = useCallback(() => { const el = containerRef.current; if (!el) return; const total = el.clientHeight; setTopHeight(Math.max(TOP_MIN, Math.round(total * TOP_DEFAULT_PCT))); }, []); useEffect(() => { initHeight(); }, [initHeight]); // 响应容器 resize(窗口大小变化时重新按比例计算) useEffect(() => { const el = containerRef.current; if (!el) return; const observer = new ResizeObserver(() => { if (!dragging) { const total = el.clientHeight; setTopHeight((prev) => { if (prev === null) return Math.max(TOP_MIN, Math.round(total * TOP_DEFAULT_PCT)); // 保持原有比例 const ratio = prev / total; return Math.max(TOP_MIN, Math.round(total * Math.min(ratio, 0.8))); }); } }); observer.observe(el); return () => observer.disconnect(); }, [dragging]); // 拖拽事件 const handleDividerMouseDown = (e: React.MouseEvent) => { e.preventDefault(); dragState.current = { startY: e.clientY, startHeight: topHeight ?? 200 }; setDragging(true); }; useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!dragging) return; const el = containerRef.current; if (!el) return; const total = el.clientHeight; const ds = dragState.current; const delta = e.clientY - ds.startY; const newTop = Math.max(TOP_MIN, Math.min(total - BOTTOM_MIN, ds.startHeight + delta)); setTopHeight(newTop); }; const handleMouseUp = () => { setDragging(false); }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; }, [dragging]); // 分隔条样式 const isActive = dragging; const isHovered = hovered; return (
{/* 上方:模型选择(高度由拖拽控制) */}
{/* 纵向拖拽分隔条 */}
setHovered(true)} onMouseLeave={() => setHovered(false)} > {/* 拖拽手柄横线 */}
{/* 下方:任务记录(剩余高度) */}
); }