diff --git a/app/components/MobileFundTable.jsx b/app/components/MobileFundTable.jsx index 8ae7e10..9b356a5 100644 --- a/app/components/MobileFundTable.jsx +++ b/app/components/MobileFundTable.jsx @@ -1,6 +1,7 @@ 'use client'; import { useEffect, useMemo, useRef, useState } from 'react'; +import ReactDOM from 'react-dom'; import { AnimatePresence, motion } from 'framer-motion'; import { flexRender, @@ -275,8 +276,10 @@ export default function MobileFundTable({ const [settingModalOpen, setSettingModalOpen] = useState(false); const tableContainerRef = useRef(null); + const portalHeaderRef = useRef(null); const [tableContainerWidth, setTableContainerWidth] = useState(0); const [isScrolled, setIsScrolled] = useState(false); + const [showPortalHeader, setShowPortalHeader] = useState(false); useEffect(() => { const el = tableContainerRef.current; @@ -289,16 +292,63 @@ export default function MobileFundTable({ }, []); useEffect(() => { - const el = tableContainerRef.current; - if (!el) return; - const handleScroll = () => { - setIsScrolled(el.scrollLeft > 0); + if (typeof window === 'undefined') return; + + const handleVerticalScroll = () => { + console.log('scrollY', window.scrollY); + setShowPortalHeader(window.scrollY >= 100); }; - handleScroll(); - el.addEventListener('scroll', handleScroll, { passive: true }); - return () => el.removeEventListener('scroll', handleScroll); + + handleVerticalScroll(); + window.addEventListener('scroll', handleVerticalScroll, { passive: true }); + return () => window.removeEventListener('scroll', handleVerticalScroll); }, []); + useEffect(() => { + const tableEl = tableContainerRef.current; + if (!tableEl) return; + + const handleScroll = () => { + setIsScrolled(tableEl.scrollLeft > 0); + }; + + handleScroll(); + tableEl.addEventListener('scroll', handleScroll, { passive: true }); + + return () => { + tableEl.removeEventListener('scroll', handleScroll); + }; + }, []); + + useEffect(() => { + const tableEl = tableContainerRef.current; + const portalEl = portalHeaderRef.current; + console.log('portalEl', portalEl) + if (!tableEl || !portalEl) return; + + const syncScrollToPortal = () => { + console.log('tableEl.scrollLeft', tableEl.scrollLeft) + portalEl.scrollLeft = tableEl.scrollLeft; + }; + + const syncScrollToTable = () => { + tableEl.scrollLeft = portalEl.scrollLeft; + }; + + syncScrollToPortal(); + + const handleTableScroll = () => syncScrollToPortal(); + const handlePortalScroll = () => syncScrollToTable(); + + tableEl.addEventListener('scroll', handleTableScroll, { passive: true }); + // portalEl.addEventListener('scroll', handlePortalScroll, { passive: true }); + + return () => { + tableEl.removeEventListener('scroll', handleTableScroll); + // portalEl.removeEventListener('scroll', handlePortalScroll); + }; + }, [showPortalHeader]); + const NAME_CELL_WIDTH = 140; const GAP = 12; const LAST_COLUMN_EXTRA = 12; @@ -742,116 +792,149 @@ export default function MobileFundTable({ return 'text-right'; }; - return ( -
+ const renderTableHeader = ()=>{ + if(!headerGroup) return null; + return (
- {headerGroup && ( + {headerGroup.headers.map((header, headerIndex) => { + const columnId = header.column.id; + const pinClass = getPinClass(columnId, true); + const alignClass = getAlignClass(columnId); + const isLastColumn = headerIndex === headerGroup.headers.length - 1; + return ( +
+ {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} +
+ ); + })} +
+ ) + } + + const renderContent = (onlyShowHeader) => { + if (onlyShowHeader) { + return ( +
- {headerGroup.headers.map((header, headerIndex) => { - const columnId = header.column.id; - const pinClass = getPinClass(columnId, true); - const alignClass = getAlignClass(columnId); - const isLastColumn = headerIndex === headerGroup.headers.length - 1; - return ( -
- {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} -
- ); - })} + {renderTableHeader()} +
+
+ ); + } + + return ( +
+
+ {renderTableHeader()} + + {!onlyShowHeader && ( + + item.code)} + strategy={verticalListSortingStrategy} + > + + {table.getRowModel().rows.map((row) => ( + + {(setActivatorNodeRef, listeners) => ( +
+ {row.getVisibleCells().map((cell, cellIndex) => { + const columnId = cell.column.id; + const pinClass = getPinClass(columnId, false); + const alignClass = getAlignClass(columnId); + const cellClassName = cell.column.columnDef.meta?.cellClassName || ''; + const isLastColumn = cellIndex === row.getVisibleCells().length - 1; + return ( +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+ ); + })} +
+ )} +
+ ))} +
+
+
+ + )} +
+ + {table.getRowModel().rows.length === 0 && !onlyShowHeader && ( +
+
+ 暂无数据 +
)} - - item.code)} - strategy={verticalListSortingStrategy} - > - - {table.getRowModel().rows.map((row) => ( - - {(setActivatorNodeRef, listeners) => ( -
- {row.getVisibleCells().map((cell, cellIndex) => { - const columnId = cell.column.id; - const pinClass = getPinClass(columnId, false); - const alignClass = getAlignClass(columnId); - const cellClassName = cell.column.columnDef.meta?.cellClassName || ''; - const isLastColumn = cellIndex === row.getVisibleCells().length - 1; - return ( -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
- ); - })} -
- )} -
- ))} -
-
-
+ {!onlyShowHeader && ( + setSettingModalOpen(false)} + columns={mobileColumnOrder.map((id) => ({ id, header: MOBILE_COLUMN_HEADERS[id] ?? id }))} + columnVisibility={mobileColumnVisibility} + onColumnReorder={(newOrder) => { + setMobileColumnOrder(newOrder); + }} + onToggleColumnVisibility={handleToggleMobileColumnVisibility} + onResetColumnOrder={handleResetMobileColumnOrder} + onResetColumnVisibility={handleResetMobileColumnVisibility} + showFullFundName={showFullFundName} + onToggleShowFullFundName={handleToggleShowFullFundName} + /> + )} + + {!onlyShowHeader && showPortalHeader && ReactDOM.createPortal(renderContent(true), document.body)}
+ ); + }; - {table.getRowModel().rows.length === 0 && ( -
-
- 暂无数据 -
-
- )} - - setSettingModalOpen(false)} - columns={mobileColumnOrder.map((id) => ({ id, header: MOBILE_COLUMN_HEADERS[id] ?? id }))} - columnVisibility={mobileColumnVisibility} - onColumnReorder={(newOrder) => { - setMobileColumnOrder(newOrder); - }} - onToggleColumnVisibility={handleToggleMobileColumnVisibility} - onResetColumnOrder={handleResetMobileColumnOrder} - onResetColumnVisibility={handleResetMobileColumnVisibility} - showFullFundName={showFullFundName} - onToggleShowFullFundName={handleToggleShowFullFundName} - /> -
+ return ( + <> + {renderContent()} + ); } diff --git a/app/globals.css b/app/globals.css index f6ffa4a..d900084 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1535,6 +1535,22 @@ input[type="number"] { /* min-width 由 MobileFundTable 根据 columns meta.width 动态设置 */ } + .mobile-fund-table-portal-header { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + scrollbar-width: none; + -ms-overflow-style: none; + left: 12px; + right: 12px; + box-sizing: border-box; + } + .mobile-fund-table-portal-header::-webkit-scrollbar { + display: none; + } + .mobile-fund-table-portal-header .mobile-fund-table-scroll { + padding: 0; + } + .mobile-fund-table .table-header-row { display: grid; /* grid-template-columns 由 MobileFundTable 根据当前列顺序动态设置 */ diff --git a/app/page.jsx b/app/page.jsx index 318af00..8f79557 100644 --- a/app/page.jsx +++ b/app/page.jsx @@ -427,6 +427,7 @@ export default function HomePage() { // 动态计算 Navbar 和 FilterBar 高度 const navbarRef = useRef(null); const filterBarRef = useRef(null); + const containerRef = useRef(null); const [navbarHeight, setNavbarHeight] = useState(0); const [filterBarHeight, setFilterBarHeight] = useState(0); // 主题初始固定为 dark,避免 SSR 与客户端首屏不一致导致 hydration 报错;真实偏好由 useLayoutEffect 在首帧前恢复 @@ -3598,7 +3599,7 @@ export default function HomePage() { }; return ( -
+
{showThemeTransition && (