diff --git a/CHANGELOG.md b/CHANGELOG.md index 68d4711..c379208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - 首页三栏分隔条视觉对齐:移除右侧多余 margin,左右完全对称 - 轮播图新增关闭按钮:session 级别隐藏,每次启动重新展示 - 确立 QT 多窗口模型 → Web 架构映射原则:叠加层走 Modal/Drawer + 事件总线,不走路由 +- 修复首页三栏布局溢出:拖拽分隔条增加最大宽度约束 + 视口缩小时自动收窄面板,防止右侧面板被挤出视口 --- diff --git a/TODO.md b/TODO.md index a316c44..42a1de1 100644 --- a/TODO.md +++ b/TODO.md @@ -66,6 +66,7 @@ | localStorage 依赖 | 所有持久化数据依赖 localStorage | 清理浏览器数据会丢失所有配置 | | IPC 监听器 | useUpdater 已改为单例,其他 IPC hook 可能也有类似问题 | 长期运行可能内存泄漏 | | ~~主题切换状态重置~~ | ✅ 已修复 — 根因:设置页 `navigate('/settings')` 导致路由 `/*` 仅匹配 index,HomePage 被卸载重挂载。修复:SettingsPage 改为事件总线驱动的 Modal 居中叠加,不走路由 | ~~用户体验差~~ | +| ~~三栏布局拖拽溢出~~ | ✅ 已修复 — 拖拽分隔条增加最大宽度约束(`maxLeft = containerWidth - 36 - 300 - rightWidth`)+ 视口缩小 ResizeObserver 自动收窄 | ~~布局可用性~~ | | `test.py` | 仓库根目录残留测试文件 | 无实际作用,应清理 | | `null` 文件 | 仓库根目录名为 null 的文件 | 构建产物或误操作残留 | diff --git a/src/pages/home/HomeContent.tsx b/src/pages/home/HomeContent.tsx index f528f0f..10ebb48 100644 --- a/src/pages/home/HomeContent.tsx +++ b/src/pages/home/HomeContent.tsx @@ -25,8 +25,12 @@ const LEFT_DEFAULT = 750; /** 右列最小/默认宽度 */ const RIGHT_MIN = 300; const RIGHT_DEFAULT = 480; +/** 中列最小宽度 */ +const CENTER_MIN = 300; /** 拖拽分隔条宽度 */ const DIVIDER_WIDTH = 6; +/** 布局固定消耗:左右 padding(12*2) + 两个分隔条(6*2) */ +const LAYOUT_OVERHEAD = 36; // 24 + 12 // ---------- 组件 ---------- @@ -98,6 +102,14 @@ export function HomeContent() { startWidth: number; }>({ startX: 0, startWidth: 0 }); + // 用 ref 追踪最新列宽,避免拖拽 mousemove 闭包中读到过期值 + const leftWidthRef = useRef(leftWidth); + leftWidthRef.current = leftWidth; + const rightWidthRef = useRef(rightWidth); + rightWidthRef.current = rightWidth; + const draggingRef = useRef(dragging); + draggingRef.current = dragging; + const handleDividerMouseDown = (side: 'left' | 'right') => (e: React.MouseEvent) => { e.preventDefault(); // eslint-disable-next-line react-hooks/refs @@ -109,14 +121,22 @@ export function HomeContent() { const handleMouseMove = (e: MouseEvent) => { if (!dragging) return; + const containerEl = containerRef.current; + if (!containerEl) return; + const containerWidth = containerEl.clientWidth; const ds = dragState.current; const delta = e.clientX - ds.startX; if (dragging === 'left') { - const newWidth = Math.max(LEFT_MIN, ds.startWidth + delta); + // 左列最大宽度 = 容器宽 - 固定消耗 - 中列最小宽 - 右列当前宽 + // 确保中列至少保留 CENTER_MIN、右列不被挤出视口 + const maxLeft = containerWidth - LAYOUT_OVERHEAD - CENTER_MIN - rightWidthRef.current; + const newWidth = Math.max(LEFT_MIN, Math.min(maxLeft, ds.startWidth + delta)); setLeftWidth(newWidth); } else { - const newWidth = Math.max(RIGHT_MIN, ds.startWidth - delta); + // 右列最大宽度同理:不能把中列挤到 CENTER_MIN 以下 + const maxRight = containerWidth - LAYOUT_OVERHEAD - CENTER_MIN - leftWidthRef.current; + const newWidth = Math.max(RIGHT_MIN, Math.min(maxRight, ds.startWidth - delta)); setRightWidth(newWidth); } }; @@ -134,6 +154,41 @@ export function HomeContent() { }; }, [dragging]); + // ======== 视口缩小自动收窄面板 ======== + + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + const observer = new ResizeObserver(() => { + // 拖拽中由 mousemove handler 处理约束,避免冲突 + if (draggingRef.current) return; + + const containerWidth = el.clientWidth; + const currentLeft = leftWidthRef.current; + const currentRight = rightWidthRef.current; + + // 检查当前总宽度是否超出容器 + const totalNeeded = currentLeft + LAYOUT_OVERHEAD + CENTER_MIN + currentRight; + if (totalNeeded <= containerWidth) return; + + // 超出容器 → 优先收缩右侧面板,再收左侧 + const deficit = totalNeeded - containerWidth; + const rightSlack = currentRight - RIGHT_MIN; + + if (rightSlack >= deficit) { + setRightWidth(currentRight - deficit); + } else { + setRightWidth(RIGHT_MIN); + const remainingDeficit = deficit - rightSlack; + setLeftWidth(Math.max(LEFT_MIN, currentLeft - remainingDeficit)); + } + }); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + // ======== 分隔条样式工厂 ======== const dividerStyle = (side: 'left' | 'right'): React.CSSProperties => { @@ -232,7 +287,7 @@ export function HomeContent() { )} {/* 中列 */} -
+