refactor: 重组 components/ 和 pages/ 文件结构,按职责分层
components/ 拆为三层:
- providers/ — 状态管理(App/Theme/SettingsProvider)
- shell/ — 应用外壳(Layout/NavBar/PageDispatcher)
- ui/ — 通用 UI 原子(FloatingPanel/LegalTextModal)
pages/ 重命名 + 重组:
- headers/ → panels/ NavBar 触发的面板页
- login/ → auth/ 认证模块(含注册/忘记密码)
- settings/blocks/ → sections/
- home/components/ → panels/ features/ controls/ hooks/
按职责分:panels(布局壳) + features(功能面板) + controls(表单控件) + hooks
其他清理:
- 删除空占位 Test3.tsx
- HomeContext 从 HomeContent 拆出独立文件
- uploadValidation 内联到 ImageInput
- 修复过时注释(useUI → controls)
This commit is contained in:
189
src/pages/home/panels/LeftPanel.tsx
Normal file
189
src/pages/home/panels/LeftPanel.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
// ============================================================
|
||||
// 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<HTMLDivElement>(null);
|
||||
|
||||
// ======== 模型数据(提升到父组件,避免两个子组件重复请求) ========
|
||||
const { data: models, loading, error, groupedModels, refetch } = useModelList();
|
||||
|
||||
// 模型选择区高度(像素)
|
||||
const [topHeight, setTopHeight] = useState<number | null>(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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
borderRadius: 8,
|
||||
border: `1px solid ${token.colorBorderSecondary}`,
|
||||
}}
|
||||
>
|
||||
{/* 上方:模型选择(高度由拖拽控制) */}
|
||||
<div
|
||||
style={{
|
||||
height: topHeight ?? '40%',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<ModelSelector
|
||||
models={models}
|
||||
loading={loading}
|
||||
error={error}
|
||||
groupedModels={groupedModels}
|
||||
refetch={refetch}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 纵向拖拽分隔条 */}
|
||||
<div
|
||||
onMouseDown={handleDividerMouseDown}
|
||||
style={{
|
||||
height: DIVIDER_HEIGHT,
|
||||
flexShrink: 0,
|
||||
cursor: 'row-resize',
|
||||
background: isActive
|
||||
? token.colorPrimary
|
||||
: isHovered
|
||||
? `${token.colorPrimary}60`
|
||||
: token.colorBorderSecondary,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transition: 'background 0.15s linear',
|
||||
zIndex: 10,
|
||||
}}
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
>
|
||||
{/* 拖拽手柄横线 */}
|
||||
<div style={{
|
||||
height: 2,
|
||||
width: 32,
|
||||
borderRadius: 1,
|
||||
background: isActive
|
||||
? '#fff'
|
||||
: isHovered
|
||||
? token.colorPrimary
|
||||
: token.colorBorder,
|
||||
transition: 'background 0.15s linear',
|
||||
}} />
|
||||
</div>
|
||||
|
||||
{/* 下方:任务记录(剩余高度) */}
|
||||
<div style={{ flex: 1, overflow: 'hidden', display: 'flex', flexDirection: 'column', minHeight: 0 }}>
|
||||
<TaskHistory allModels={models} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user