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 && (