From 57d6ace2f8b208f4d4f5d8741c55fb7e06f8ddb3 Mon Sep 17 00:00:00 2001 From: tianqijiuyun-latiao <69459608+tianqijiuyun-latiao@users.noreply.github.com> Date: Fri, 6 Mar 2026 22:39:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(data-grid-scroll):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=8C=BA=E8=A7=A6=E6=91=B8=E6=9D=BF=E6=A8=AA?= =?UTF-8?q?=E5=90=91=E6=BB=9A=E5=8A=A8=E5=A4=B1=E6=95=88=20refs=20#175?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/DataGrid.tsx | 60 ++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/frontend/src/components/DataGrid.tsx b/frontend/src/components/DataGrid.tsx index 60d8e5c..088bc14 100644 --- a/frontend/src/components/DataGrid.tsx +++ b/frontend/src/components/DataGrid.tsx @@ -2861,6 +2861,66 @@ const DataGrid: React.FC = ({ }; }, [horizontalScrollVisible]); + // 非虚拟模式:支持在数据区直接使用触摸板/Shift+滚轮进行横向滚动。 + // 某些平台在表格内容未铺满一页时,不会把水平手势正确路由到表格 body,导致只能在表头/底部滚动条区域滚动。 + useEffect(() => { + if (viewMode !== 'table' || enableVirtual) return; + const container = tableContainerRef.current; + if (!(container instanceof HTMLElement)) return; + + const resolveHorizontalDelta = (event: WheelEvent) => { + if (Math.abs(event.deltaX) > 0.5) { + return event.deltaX; + } + if (event.shiftKey && Math.abs(event.deltaY) > 0.5) { + return event.deltaY; + } + return 0; + }; + + const isTableDataAreaTarget = (target: EventTarget | null) => { + const element = target instanceof HTMLElement ? target : null; + if (!element) return false; + if (element.closest('.data-grid-external-hscroll')) return false; + return !!element.closest('.ant-table-body, .ant-table-content, .ant-table-cell, .ant-table-row, .ant-table-tbody'); + }; + + const handleContainerHorizontalWheel = (event: WheelEvent) => { + const horizontalDelta = resolveHorizontalDelta(event); + if (!Number.isFinite(horizontalDelta) || Math.abs(horizontalDelta) < 0.5) return; + if (!isTableDataAreaTarget(event.target)) return; + + const targets = pickHorizontalScrollTargets(container); + const activeTarget = targets.find((target) => target.scrollWidth > target.clientWidth + 1) || targets[0]; + if (!(activeTarget instanceof HTMLElement)) return; + + const maxScrollLeft = Math.max(0, activeTarget.scrollWidth - activeTarget.clientWidth); + if (maxScrollLeft <= 0) return; + + const nextScrollLeft = Math.max(0, Math.min(maxScrollLeft, activeTarget.scrollLeft + horizontalDelta)); + if (Math.abs(nextScrollLeft - activeTarget.scrollLeft) < 1) return; + + event.preventDefault(); + event.stopPropagation(); + + horizontalSyncSourceRef.current = 'table'; + activeTarget.scrollLeft = nextScrollLeft; + lastTableScrollLeftRef.current = nextScrollLeft; + + const externalScroll = externalHScrollRef.current; + if (externalScroll && Math.abs(externalScroll.scrollLeft - nextScrollLeft) > 1) { + externalScroll.scrollLeft = nextScrollLeft; + lastExternalScrollLeftRef.current = nextScrollLeft; + } + horizontalSyncSourceRef.current = ''; + }; + + container.addEventListener('wheel', handleContainerHorizontalWheel, { passive: false, capture: true }); + return () => { + container.removeEventListener('wheel', handleContainerHorizontalWheel, { capture: true } as EventListenerOptions); + }; + }, [viewMode, enableVirtual, pickHorizontalScrollTargets]); + useEffect(() => { if (viewMode !== 'table') return; const rafId = requestAnimationFrame(() => recalculateTableMetrics(containerRef.current));