From ae68a33c1ee22e354873e20dc56ee6d8e98da5fc Mon Sep 17 00:00:00 2001 From: WrBug Date: Fri, 16 Jan 2026 10:14:54 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=BB=93=E4=BD=8D?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E5=88=86=E9=A1=B5=E5=92=8C=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加完整的分页功能,支持移动端和桌面端 - 移动端:自定义分页控件,支持切换每页 10/20/50 条 - 桌面端:使用 Ant Design Table 分页,支持切换每页 10/20/50 条 - 优化仓位统计逻辑,区分当前仓位和历史仓位 - 当前仓位:显示浮动盈亏和已实现盈亏 - 历史仓位:移除盈亏统计显示 - 筛选条件变化时自动重置到第一页 --- frontend/src/pages/PositionList.tsx | 165 +++++++++++++++++++++------- 1 file changed, 125 insertions(+), 40 deletions(-) diff --git a/frontend/src/pages/PositionList.tsx b/frontend/src/pages/PositionList.tsx index c0e075f..64f8295 100644 --- a/frontend/src/pages/PositionList.tsx +++ b/frontend/src/pages/PositionList.tsx @@ -40,6 +40,8 @@ const PositionList: React.FC = () => { const [redeemableSummary, setRedeemableSummary] = useState(null) const [loadingRedeemableSummary, setLoadingRedeemableSummary] = useState(false) const [redeeming, setRedeeming] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [pageSize, setPageSize] = useState(20) useEffect(() => { fetchAccounts() @@ -66,6 +68,11 @@ const PositionList: React.FC = () => { fetchRedeemableSummary() } }, [currentPositions, selectedAccountId]) + + // 当筛选条件或搜索关键词变化时,重置分页到第一页 + useEffect(() => { + setCurrentPage(1) + }, [positionFilter, selectedAccountId, searchKeyword]) // 获取可赎回仓位统计 const fetchRedeemableSummary = async () => { @@ -265,12 +272,12 @@ const PositionList: React.FC = () => { // 本地搜索和筛选过滤 const filteredPositions = useMemo(() => { let filtered = basePositions - + // 1. 先按账户筛选 if (selectedAccountId !== undefined) { filtered = filtered.filter(p => p.accountId === selectedAccountId) } - + // 2. 最后按关键词搜索 if (searchKeyword.trim()) { const keyword = searchKeyword.trim().toLowerCase() @@ -302,9 +309,16 @@ const PositionList: React.FC = () => { return false }) } - + return filtered }, [basePositions, searchKeyword, selectedAccountId]) + + // 分页后的数据 + const paginatedPositions = useMemo(() => { + const startIndex = (currentPage - 1) * pageSize + const endIndex = startIndex + pageSize + return filteredPositions.slice(startIndex, endIndex) + }, [filteredPositions, currentPage, pageSize]) const getSideColor = (side: string) => { return side === 'YES' ? 'green' : 'red' @@ -349,14 +363,32 @@ const PositionList: React.FC = () => { if (!isNaN(initialValue)) { totalInitialValue += initialValue } + + // 当前仓位:统计持仓价值 + // 历史仓位:currentValue 应该为 0(已平仓) if (!isNaN(currentValue)) { totalCurrentValue += currentValue } - if (!isNaN(pnl)) { - totalPnl += pnl - } - if (!isNaN(realizedPnl)) { - totalRealizedPnl += realizedPnl + + // 对于当前仓位: + // - pnl:未实现盈亏(浮动盈亏) + // - realizedPnl:已实现盈亏(部分平仓时产生) + // 对于历史仓位: + // - pnl:总已实现盈亏(包含部分平仓 + 完全平仓) + // - realizedPnl:部分平仓的已实现盈亏(可能与 pnl 重复) + if (pos.isCurrent) { + // 当前仓位:未实现盈亏 + 已实现盈亏 + if (!isNaN(pnl)) { + totalPnl += pnl + } + if (!isNaN(realizedPnl)) { + totalRealizedPnl += realizedPnl + } + } else { + // 历史仓位:pnl 是总已实现盈亏,realizedPnl 可能重复,所以只统计 pnl + if (!isNaN(pnl)) { + totalRealizedPnl += pnl + } } }) @@ -516,10 +548,10 @@ const PositionList: React.FC = () => { // 渲染卡片视图 const renderCardView = () => { - if (filteredPositions.length === 0) { + if (paginatedPositions.length === 0) { return ( - ) @@ -527,7 +559,7 @@ const PositionList: React.FC = () => { return ( - {filteredPositions.map((position, index) => { + {paginatedPositions.map((position, index) => { const pnlNum = parseFloat(position.pnl || '0') const isProfit = pnlNum >= 0 // 只有当前仓位才根据盈亏显示边框颜色 @@ -1235,8 +1267,8 @@ const PositionList: React.FC = () => { )} - {/* 合计信息:开仓价值、当前价值、盈亏、已实现盈亏(基于当前筛选后的仓位) */} - {filteredPositions.length > 0 && ( + {/* 合计信息:开仓价值、当前价值、盈亏、已实现盈亏(仅当前仓位显示) */} + {filteredPositions.length > 0 && positionFilter === 'current' && (
{ 当前价值合计:{' '} - {positionFilter === 'current' - ? `${formatUSDC(positionTotals.totalCurrentValue.toString())} USDC` - : '-'} + {formatUSDC(positionTotals.totalCurrentValue.toString())} USDC - 盈亏合计:{' '} + 浮动盈亏合计:{' '} { {(isMobile || viewMode === 'card') ? ( {renderCardView()} + {/* 移动端分页 */} {filteredPositions.length > 0 && ( -
- 共 {filteredPositions.length} 个仓位{searchKeyword ? `(已过滤)` : ''} -
+ <> +
+
+ 共 {filteredPositions.length} 个仓位{searchKeyword ? `(已过滤)` : ''} +
+
+ + + {currentPage} / {Math.ceil(filteredPositions.length / pageSize)} + + +
+
+ {/* 每页条数选择器 */} +
+ +
+ )}
) : ( - - `${record.accountId}-${record.marketId}-${index}`} - loading={loading} - pagination={{ - pageSize: 20, - showSizeChanger: !isMobile, - showTotal: (total) => `共 ${total} 个仓位${searchKeyword ? `(已过滤)` : ''}` - }} - scroll={isMobile ? { x: 1500 } : undefined} - /> - + +
`${record.accountId}-${record.marketId}-${index}`} + loading={loading} + pagination={{ + current: currentPage, + pageSize: pageSize, + total: filteredPositions.length, + showSizeChanger: true, + pageSizeOptions: ['10', '20', '50'], + showTotal: (total) => `共 ${total} 个仓位${searchKeyword ? `(已过滤)` : ''}`, + onChange: (page, size) => { + setCurrentPage(page) + if (size !== pageSize) { + setPageSize(size) + } + } + }} + scroll={isMobile ? { x: 1500 } : undefined} + /> + )} {/* 出售模态框 */}