From 836cc40d4314a0334dcd784d61f73542c8fef54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E7=8B=82=E7=9A=84=E7=8B=AE=E5=AD=90Li?= <15040126243@163.com> Date: Tue, 30 Jun 2026 11:41:04 +0800 Subject: [PATCH] =?UTF-8?q?update=20=E8=A1=A5=E5=85=85=E6=BC=8F=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useTableScroll.ts | 126 ++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 src/hooks/useTableScroll.ts diff --git a/src/hooks/useTableScroll.ts b/src/hooks/useTableScroll.ts new file mode 100644 index 00000000..5dee4334 --- /dev/null +++ b/src/hooks/useTableScroll.ts @@ -0,0 +1,126 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { useAppStore } from '@/stores/appStore'; + +type TableScrollX = number | string | true; + +interface TableScrollOptions { + x: TableScrollX; + bottomOffset?: number; + fallbackY?: number; + disabled?: boolean; +} + +function getViewportHeight() { + if (typeof window === 'undefined') return 0; + + return window.visualViewport?.height || window.innerHeight || document.documentElement.clientHeight; +} + +export function useTableScroll(options: TableScrollX | TableScrollOptions) { + const fullHeightTable = useAppStore(state => state.layoutSettings.fullHeightTable); + const normalized = typeof options === 'object' ? options : { x: options }; + const { x, bottomOffset = 40, fallbackY = 300, disabled = false } = normalized; + const tableWrapperRef = useRef(null); + const frameRef = useRef(undefined); + const tableBodyRef = useRef(undefined); + const [tableY, setTableY] = useState(); + + const clearTableBodyHeight = useCallback(() => { + if (!tableBodyRef.current) return; + + tableBodyRef.current.style.removeProperty('height'); + tableBodyRef.current = undefined; + }, []); + + const refreshTableScroll = useCallback(() => { + if (!fullHeightTable || disabled || typeof window === 'undefined') { + clearTableBodyHeight(); + setTableY(undefined); + return; + } + + if (frameRef.current) { + window.cancelAnimationFrame(frameRef.current); + } + + frameRef.current = window.requestAnimationFrame(() => { + const wrapper = + tableWrapperRef.current || + document.querySelector('.ant-pro-page-container') || + document.body; + + const tableBody = + wrapper.querySelector('.ant-table-body') || + wrapper.querySelector('.ant-table-content') || + wrapper; + const pagination = wrapper.querySelector('.ant-pagination'); + const paginationHeight = pagination ? Math.ceil(pagination.getBoundingClientRect().height) + 16 : 0; + const availableY = Math.floor( + getViewportHeight() - tableBody.getBoundingClientRect().top - paginationHeight - bottomOffset + ); + const nextY = availableY > 0 ? availableY : fallbackY; + + if (tableBodyRef.current && tableBodyRef.current !== tableBody) { + tableBodyRef.current.style.removeProperty('height'); + } + const nextHeight = `${nextY}px`; + if (tableBody.style.height !== nextHeight) { + tableBody.style.height = nextHeight; + } + tableBodyRef.current = tableBody; + setTableY(current => (current === nextY ? current : nextY)); + }); + }, [bottomOffset, clearTableBodyHeight, disabled, fallbackY, fullHeightTable]); + + useEffect(() => { + refreshTableScroll(); + + if (!fullHeightTable || disabled || typeof window === 'undefined') return undefined; + + const wrapper = + tableWrapperRef.current || document.querySelector('.ant-pro-page-container') || document.body; + const resizeObserver = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(refreshTableScroll); + const mutationObserver = + typeof MutationObserver === 'undefined' + ? undefined + : new MutationObserver(() => { + refreshTableScroll(); + }); + + resizeObserver?.observe(wrapper); + mutationObserver?.observe(wrapper, { + attributes: true, + childList: true, + subtree: true, + attributeFilter: ['class', 'style'] + }); + window.addEventListener('resize', refreshTableScroll); + window.visualViewport?.addEventListener('resize', refreshTableScroll); + + return () => { + if (frameRef.current) { + window.cancelAnimationFrame(frameRef.current); + } + clearTableBodyHeight(); + resizeObserver?.disconnect(); + mutationObserver?.disconnect(); + window.removeEventListener('resize', refreshTableScroll); + window.visualViewport?.removeEventListener('resize', refreshTableScroll); + }; + }, [clearTableBodyHeight, disabled, fullHeightTable, refreshTableScroll]); + + const tableScroll = useMemo(() => { + if (!fullHeightTable || disabled || !tableY) return { x }; + + return { + x, + y: tableY + }; + }, [disabled, fullHeightTable, tableY, x]); + + return { + tableScroll, + tableWrapperRef, + refreshTableScroll + }; +}