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:
@@ -10,6 +10,7 @@
|
||||
- 首页三栏分隔条视觉对齐:移除右侧多余 margin,左右完全对称
|
||||
- 轮播图新增关闭按钮:session 级别隐藏,每次启动重新展示
|
||||
- 确立 QT 多窗口模型 → Web 架构映射原则:叠加层走 Modal/Drawer + 事件总线,不走路由
|
||||
- 修复首页三栏布局溢出:拖拽分隔条增加最大宽度约束 + 视口缩小时自动收窄面板,防止右侧面板被挤出视口
|
||||
|
||||
---
|
||||
|
||||
|
||||
1
TODO.md
1
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 的文件 | 构建产物或误操作残留 |
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user