fix: 修复首页三栏布局拖拽溢出 — 分隔条最大宽度约束 + 视口缩小自动收窄

问题:切换视口大小或拖拽分隔条时,右侧预览面板甚至中右两栏被挤出视口。

  根因:flex 布局左/右列 flexShrink:0,拖拽无上限约束,视口缩小时不自动收窄,
  总宽度超出容器后 overflow:hidden 裁剪。

  修复(HomeContent.tsx):
  - 拖拽时计算 maxLeft / maxRight 动态上限,确保中列≥300px、三栏均在视口内
  - ResizeObserver 监听容器缩小,优先收右侧面板→不够再收左侧,拖拽中自动跳过
  - leftWidthRef / rightWidthRef / draggingRef 避免闭包读到过期值

  变更:CHANGELOG.md / TODO.md / WORKLOG.md 同步更新

  影响文件:3 个(+60 -3)
  - src/pages/home/HomeContent.tsx
  - CHANGELOG.md
  - TODO.md
This commit is contained in:
2026-06-07 05:56:00 +08:00
parent a01b3f259c
commit 952a723448
3 changed files with 60 additions and 3 deletions

View File

@@ -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() {
)}
{/* 中列 */}
<div style={{ flex: 1, overflow: 'hidden', minWidth: 300 }}>
<div style={{ flex: 1, overflow: 'hidden', minWidth: CENTER_MIN }}>
<CenterPanel />
</div>