fix: PC 端基金详情弹框滚动问题
This commit is contained in:
@@ -1077,8 +1077,9 @@ export default function PcFundTable({
|
|||||||
const totalHeaderWidth = headerGroup?.headers?.reduce((acc, h) => acc + h.column.getSize(), 0) ?? 0;
|
const totalHeaderWidth = headerGroup?.headers?.reduce((acc, h) => acc + h.column.getSize(), 0) ?? 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pc-fund-table" ref={tableContainerRef}>
|
<>
|
||||||
<style>{`
|
<div className="pc-fund-table" ref={tableContainerRef}>
|
||||||
|
<style>{`
|
||||||
.table-row-scroll {
|
.table-row-scroll {
|
||||||
--row-bg: var(--bg);
|
--row-bg: var(--bg);
|
||||||
background-color: var(--row-bg) !important;
|
background-color: var(--row-bg) !important;
|
||||||
@@ -1175,87 +1176,134 @@ export default function PcFundTable({
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
`}</style>
|
`}</style>
|
||||||
{/* 表头 */}
|
{/* 表头 */}
|
||||||
{renderTableHeader(false)}
|
{renderTableHeader(false)}
|
||||||
|
|
||||||
{/* 表体 */}
|
{/* 表体 */}
|
||||||
<DndContext
|
<DndContext
|
||||||
sensors={sensors}
|
sensors={sensors}
|
||||||
collisionDetection={closestCenter}
|
collisionDetection={closestCenter}
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
onDragCancel={handleDragCancel}
|
onDragCancel={handleDragCancel}
|
||||||
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
|
modifiers={[restrictToVerticalAxis, restrictToParentElement]}
|
||||||
>
|
|
||||||
<SortableContext
|
|
||||||
items={data.map((item) => item.code)}
|
|
||||||
strategy={verticalListSortingStrategy}
|
|
||||||
>
|
>
|
||||||
<AnimatePresence mode="popLayout">
|
<SortableContext
|
||||||
{table.getRowModel().rows.map((row, index) => (
|
items={data.map((item) => item.code)}
|
||||||
<SortableRow key={row.original.code || row.id} row={row} isTableDragging={!!activeId} disabled={sortBy !== 'default'}>
|
strategy={verticalListSortingStrategy}
|
||||||
<div
|
>
|
||||||
className={`table-row table-row-scroll ${index % 2 === 1 ? 'row-even' : ''}`}
|
<AnimatePresence mode="popLayout">
|
||||||
>
|
{table.getRowModel().rows.map((row, index) => (
|
||||||
{row.getVisibleCells().map((cell) => {
|
<SortableRow key={row.original.code || row.id} row={row} isTableDragging={!!activeId} disabled={sortBy !== 'default'}>
|
||||||
const columnId = cell.column.id || cell.column.columnDef?.accessorKey;
|
<div
|
||||||
const isNameColumn = columnId === 'fundName';
|
className={`table-row table-row-scroll ${index % 2 === 1 ? 'row-even' : ''}`}
|
||||||
const rightAlignedColumns = new Set([
|
>
|
||||||
'latestNav',
|
{row.getVisibleCells().map((cell) => {
|
||||||
'estimateNav',
|
const columnId = cell.column.id || cell.column.columnDef?.accessorKey;
|
||||||
'yesterdayChangePercent',
|
const isNameColumn = columnId === 'fundName';
|
||||||
'estimateChangePercent',
|
const rightAlignedColumns = new Set([
|
||||||
'totalChangePercent',
|
'latestNav',
|
||||||
'holdingAmount',
|
'estimateNav',
|
||||||
'todayProfit',
|
'yesterdayChangePercent',
|
||||||
'holdingProfit',
|
'estimateChangePercent',
|
||||||
]);
|
'totalChangePercent',
|
||||||
const align = isNameColumn
|
'holdingAmount',
|
||||||
? ''
|
'todayProfit',
|
||||||
: rightAlignedColumns.has(columnId)
|
'holdingProfit',
|
||||||
? 'text-right'
|
]);
|
||||||
: 'text-center';
|
const align = isNameColumn
|
||||||
const cellClassName =
|
? ''
|
||||||
(cell.column.columnDef.meta && cell.column.columnDef.meta.cellClassName) || '';
|
: rightAlignedColumns.has(columnId)
|
||||||
const style = getCommonPinningStyles(cell.column, false);
|
? 'text-right'
|
||||||
const isPinned = cell.column.getIsPinned();
|
: 'text-center';
|
||||||
return (
|
const cellClassName =
|
||||||
<div
|
(cell.column.columnDef.meta && cell.column.columnDef.meta.cellClassName) || '';
|
||||||
key={cell.id}
|
const style = getCommonPinningStyles(cell.column, false);
|
||||||
className={`table-cell ${align} ${cellClassName} ${isPinned ? 'pinned-cell' : ''}`}
|
const isPinned = cell.column.getIsPinned();
|
||||||
style={style}
|
return (
|
||||||
>
|
<div
|
||||||
{flexRender(
|
key={cell.id}
|
||||||
cell.column.columnDef.cell,
|
className={`table-cell ${align} ${cellClassName} ${isPinned ? 'pinned-cell' : ''}`}
|
||||||
cell.getContext(),
|
style={style}
|
||||||
)}
|
>
|
||||||
</div>
|
{flexRender(
|
||||||
);
|
cell.column.columnDef.cell,
|
||||||
})}
|
cell.getContext(),
|
||||||
</div>
|
)}
|
||||||
</SortableRow>
|
</div>
|
||||||
))}
|
);
|
||||||
</AnimatePresence>
|
})}
|
||||||
</SortableContext>
|
</div>
|
||||||
</DndContext>
|
</SortableRow>
|
||||||
|
))}
|
||||||
|
</AnimatePresence>
|
||||||
|
</SortableContext>
|
||||||
|
</DndContext>
|
||||||
|
|
||||||
{table.getRowModel().rows.length === 0 && (
|
{table.getRowModel().rows.length === 0 && (
|
||||||
<div className="table-row empty-row">
|
<div className="table-row empty-row">
|
||||||
<div className="table-cell" style={{ textAlign: 'center' }}>
|
<div className="table-cell" style={{ textAlign: 'center' }}>
|
||||||
<span className="muted">暂无数据</span>
|
<span className="muted">暂无数据</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
{resetConfirmOpen && (
|
||||||
{resetConfirmOpen && (
|
<ConfirmModal
|
||||||
<ConfirmModal
|
title="重置列宽"
|
||||||
title="重置列宽"
|
message="是否重置表格列宽为默认值?"
|
||||||
message="是否重置表格列宽为默认值?"
|
icon={<ResetIcon width="20" height="20" className="shrink-0 text-[var(--primary)]" />}
|
||||||
icon={<ResetIcon width="20" height="20" className="shrink-0 text-[var(--primary)]" />}
|
confirmVariant="primary"
|
||||||
confirmVariant="primary"
|
onConfirm={handleResetSizing}
|
||||||
onConfirm={handleResetSizing}
|
onCancel={() => setResetConfirmOpen(false)}
|
||||||
onCancel={() => setResetConfirmOpen(false)}
|
confirmText="重置"
|
||||||
confirmText="重置"
|
/>
|
||||||
/>
|
)}
|
||||||
|
{showPortalHeader && ReactDOM.createPortal(
|
||||||
|
<div
|
||||||
|
className="pc-fund-table pc-fund-table-portal-header"
|
||||||
|
ref={portalHeaderRef}
|
||||||
|
style={{
|
||||||
|
position: 'fixed',
|
||||||
|
top: effectiveStickyTop,
|
||||||
|
left: portalHorizontal.left,
|
||||||
|
right: portalHorizontal.right,
|
||||||
|
zIndex: 10,
|
||||||
|
overflowX: 'auto',
|
||||||
|
scrollbarWidth: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="table-header-row table-header-row-scroll"
|
||||||
|
style={{ minWidth: totalHeaderWidth, width: 'fit-content' }}
|
||||||
|
>
|
||||||
|
{headerGroup?.headers.map((header) => {
|
||||||
|
const style = getCommonPinningStyles(header.column, true);
|
||||||
|
const isNameColumn =
|
||||||
|
header.column.id === 'fundName' ||
|
||||||
|
header.column.columnDef?.accessorKey === 'fundName';
|
||||||
|
const align = isNameColumn ? '' : 'text-center';
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={header.id}
|
||||||
|
className={`table-header-cell ${align}`}
|
||||||
|
style={style}
|
||||||
|
>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!!(cardDialogRow && getFundCardProps) && (
|
||||||
|
<FundDetailDialog blockDialogClose={blockDialogClose} cardDialogRow={cardDialogRow} getFundCardProps={getFundCardProps} setCardDialogRow={setCardDialogRow} />
|
||||||
)}
|
)}
|
||||||
<PcTableSettingModal
|
<PcTableSettingModal
|
||||||
open={settingModalOpen}
|
open={settingModalOpen}
|
||||||
@@ -1272,74 +1320,36 @@ export default function PcFundTable({
|
|||||||
showFullFundName={showFullFundName}
|
showFullFundName={showFullFundName}
|
||||||
onToggleShowFullFundName={handleToggleShowFullFundName}
|
onToggleShowFullFundName={handleToggleShowFullFundName}
|
||||||
/>
|
/>
|
||||||
<Dialog
|
</>
|
||||||
open={!!(cardDialogRow && getFundCardProps)}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
if (!open && !blockDialogClose) setCardDialogRow(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent
|
|
||||||
className="sm:max-w-2xl max-h-[88vh] flex flex-col p-0 overflow-hidden"
|
|
||||||
onPointerDownOutside={blockDialogClose ? (e) => e.preventDefault() : undefined}
|
|
||||||
>
|
|
||||||
<DialogHeader className="flex-shrink-0 flex flex-row items-center justify-between gap-2 space-y-0 px-6 pb-4 pt-6 text-left border-b border-[var(--border)]">
|
|
||||||
<DialogTitle className="text-base font-semibold text-[var(--text)]">
|
|
||||||
基金详情
|
|
||||||
</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div
|
|
||||||
className="flex-1 min-h-0 overflow-y-auto px-6 py-4 scrollbar-y-styled"
|
|
||||||
>
|
|
||||||
{cardDialogRow && getFundCardProps ? (
|
|
||||||
<FundCard {...getFundCardProps(cardDialogRow)} layoutMode="drawer" />
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{showPortalHeader && ReactDOM.createPortal(
|
|
||||||
<div
|
|
||||||
className="pc-fund-table pc-fund-table-portal-header"
|
|
||||||
ref={portalHeaderRef}
|
|
||||||
style={{
|
|
||||||
position: 'fixed',
|
|
||||||
top: effectiveStickyTop,
|
|
||||||
left: portalHorizontal.left,
|
|
||||||
right: portalHorizontal.right,
|
|
||||||
zIndex: 10,
|
|
||||||
overflowX: 'auto',
|
|
||||||
scrollbarWidth: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="table-header-row table-header-row-scroll"
|
|
||||||
style={{ minWidth: totalHeaderWidth, width: 'fit-content' }}
|
|
||||||
>
|
|
||||||
{headerGroup?.headers.map((header) => {
|
|
||||||
const style = getCommonPinningStyles(header.column, true);
|
|
||||||
const isNameColumn =
|
|
||||||
header.column.id === 'fundName' ||
|
|
||||||
header.column.columnDef?.accessorKey === 'fundName';
|
|
||||||
const align = isNameColumn ? '' : 'text-center';
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={header.id}
|
|
||||||
className={`table-header-cell ${align}`}
|
|
||||||
style={style}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header.column.columnDef.header,
|
|
||||||
header.getContext(),
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function FundDetailDialog({ blockDialogClose, cardDialogRow, getFundCardProps, setCardDialogRow}) {
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !blockDialogClose) setCardDialogRow(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent
|
||||||
|
className="sm:max-w-2xl max-h-[88vh] flex flex-col p-0 overflow-hidden"
|
||||||
|
onPointerDownOutside={blockDialogClose ? (e) => e.preventDefault() : undefined}
|
||||||
|
>
|
||||||
|
<DialogHeader className="flex-shrink-0 flex flex-row items-center justify-between gap-2 space-y-0 px-6 pb-4 pt-6 text-left border-b border-[var(--border)]">
|
||||||
|
<DialogTitle className="text-base font-semibold text-[var(--text)]">
|
||||||
|
基金详情
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div
|
||||||
|
className="flex-1 min-h-0 overflow-y-auto px-6 py-4 scrollbar-y-styled"
|
||||||
|
>
|
||||||
|
{cardDialogRow && getFundCardProps ? (
|
||||||
|
<FundCard {...getFundCardProps(cardDialogRow)} layoutMode="drawer" />
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ function lockBodyScroll() {
|
|||||||
originalBodyPosition = document.body.style.position || "";
|
originalBodyPosition = document.body.style.position || "";
|
||||||
originalBodyTop = document.body.style.top || "";
|
originalBodyTop = document.body.style.top || "";
|
||||||
|
|
||||||
document.body.style.position = "fixed";
|
requestAnimationFrame(() => {
|
||||||
document.body.style.top = `-${lockedScrollY}px`;
|
document.body.style.top = `-${lockedScrollY}px`;
|
||||||
document.body.style.width = "100%";
|
document.body.style.width = "100%";
|
||||||
|
document.body.style.position = "fixed";
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,12 +30,15 @@ function unlockBodyScroll() {
|
|||||||
|
|
||||||
// 只有全部弹框都关闭时才恢复滚动位置
|
// 只有全部弹框都关闭时才恢复滚动位置
|
||||||
if (scrollLockCount === 0) {
|
if (scrollLockCount === 0) {
|
||||||
|
const scrollY = lockedScrollY;
|
||||||
|
|
||||||
document.body.style.position = originalBodyPosition;
|
document.body.style.position = originalBodyPosition;
|
||||||
document.body.style.top = originalBodyTop;
|
document.body.style.top = originalBodyTop;
|
||||||
document.body.style.width = "";
|
document.body.style.width = "";
|
||||||
|
|
||||||
// 恢复到锁定前的滚动位置,而不是跳到顶部
|
requestAnimationFrame(() => {
|
||||||
window.scrollTo(0, lockedScrollY);
|
window.scrollTo(0, scrollY);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,4 +62,4 @@ export function useBodyScrollLock(open) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [open]);
|
}, [open]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { XIcon } from "lucide-react"
|
|
||||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
import { Dialog as DialogPrimitive } from "radix-ui"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
@@ -86,6 +85,8 @@ function DialogContent({
|
|||||||
<DialogOverlay className={overlayClassName} style={overlayStyle} />
|
<DialogOverlay className={overlayClassName} style={overlayStyle} />
|
||||||
<DialogPrimitive.Content
|
<DialogPrimitive.Content
|
||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
|
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||||
|
onCloseAutoFocus={(e) => e.preventDefault()}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-[50%] left-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-[16px] border border-[var(--border)] text-[var(--foreground)] p-6 dialog-content-shadow outline-none duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
"fixed top-[50%] left-[50%] z-50 w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-[16px] border border-[var(--border)] text-[var(--foreground)] p-6 dialog-content-shadow outline-none duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg",
|
||||||
"mobile-dialog-glass",
|
"mobile-dialog-glass",
|
||||||
|
|||||||
Reference in New Issue
Block a user