fix: 优化仓位列表分页和统计功能

- 添加完整的分页功能,支持移动端和桌面端
- 移动端:自定义分页控件,支持切换每页 10/20/50 条
- 桌面端:使用 Ant Design Table 分页,支持切换每页 10/20/50 条
- 优化仓位统计逻辑,区分当前仓位和历史仓位
- 当前仓位:显示浮动盈亏和已实现盈亏
- 历史仓位:移除盈亏统计显示
- 筛选条件变化时自动重置到第一页
This commit is contained in:
WrBug
2026-01-16 10:14:54 +08:00
parent deea59fdbf
commit ae68a33c1e
+125 -40
View File
@@ -40,6 +40,8 @@ const PositionList: React.FC = () => {
const [redeemableSummary, setRedeemableSummary] = useState<RedeemablePositionsSummary | null>(null) const [redeemableSummary, setRedeemableSummary] = useState<RedeemablePositionsSummary | null>(null)
const [loadingRedeemableSummary, setLoadingRedeemableSummary] = useState(false) const [loadingRedeemableSummary, setLoadingRedeemableSummary] = useState(false)
const [redeeming, setRedeeming] = useState(false) const [redeeming, setRedeeming] = useState(false)
const [currentPage, setCurrentPage] = useState(1)
const [pageSize, setPageSize] = useState(20)
useEffect(() => { useEffect(() => {
fetchAccounts() fetchAccounts()
@@ -66,6 +68,11 @@ const PositionList: React.FC = () => {
fetchRedeemableSummary() fetchRedeemableSummary()
} }
}, [currentPositions, selectedAccountId]) }, [currentPositions, selectedAccountId])
// 当筛选条件或搜索关键词变化时,重置分页到第一页
useEffect(() => {
setCurrentPage(1)
}, [positionFilter, selectedAccountId, searchKeyword])
// 获取可赎回仓位统计 // 获取可赎回仓位统计
const fetchRedeemableSummary = async () => { const fetchRedeemableSummary = async () => {
@@ -265,12 +272,12 @@ const PositionList: React.FC = () => {
// 本地搜索和筛选过滤 // 本地搜索和筛选过滤
const filteredPositions = useMemo(() => { const filteredPositions = useMemo(() => {
let filtered = basePositions let filtered = basePositions
// 1. 先按账户筛选 // 1. 先按账户筛选
if (selectedAccountId !== undefined) { if (selectedAccountId !== undefined) {
filtered = filtered.filter(p => p.accountId === selectedAccountId) filtered = filtered.filter(p => p.accountId === selectedAccountId)
} }
// 2. 最后按关键词搜索 // 2. 最后按关键词搜索
if (searchKeyword.trim()) { if (searchKeyword.trim()) {
const keyword = searchKeyword.trim().toLowerCase() const keyword = searchKeyword.trim().toLowerCase()
@@ -302,9 +309,16 @@ const PositionList: React.FC = () => {
return false return false
}) })
} }
return filtered return filtered
}, [basePositions, searchKeyword, selectedAccountId]) }, [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) => { const getSideColor = (side: string) => {
return side === 'YES' ? 'green' : 'red' return side === 'YES' ? 'green' : 'red'
@@ -349,14 +363,32 @@ const PositionList: React.FC = () => {
if (!isNaN(initialValue)) { if (!isNaN(initialValue)) {
totalInitialValue += initialValue totalInitialValue += initialValue
} }
// 当前仓位:统计持仓价值
// 历史仓位:currentValue 应该为 0(已平仓)
if (!isNaN(currentValue)) { if (!isNaN(currentValue)) {
totalCurrentValue += currentValue totalCurrentValue += currentValue
} }
if (!isNaN(pnl)) {
totalPnl += pnl // 对于当前仓位:
} // - pnl:未实现盈亏(浮动盈亏)
if (!isNaN(realizedPnl)) { // - realizedPnl:已实现盈亏(部分平仓时产生)
totalRealizedPnl += 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 = () => { const renderCardView = () => {
if (filteredPositions.length === 0) { if (paginatedPositions.length === 0) {
return ( return (
<Empty <Empty
description="暂无仓位数据" description="暂无仓位数据"
style={{ padding: '60px 0' }} style={{ padding: '60px 0' }}
/> />
) )
@@ -527,7 +559,7 @@ const PositionList: React.FC = () => {
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{filteredPositions.map((position, index) => { {paginatedPositions.map((position, index) => {
const pnlNum = parseFloat(position.pnl || '0') const pnlNum = parseFloat(position.pnl || '0')
const isProfit = pnlNum >= 0 const isProfit = pnlNum >= 0
// 只有当前仓位才根据盈亏显示边框颜色 // 只有当前仓位才根据盈亏显示边框颜色
@@ -1235,8 +1267,8 @@ const PositionList: React.FC = () => {
)} )}
</div> </div>
</div> </div>
{/* 合计信息:开仓价值、当前价值、盈亏、已实现盈亏(基于当前筛选后的仓位 */} {/* 合计信息:开仓价值、当前价值、盈亏、已实现盈亏(仅当前仓位显示 */}
{filteredPositions.length > 0 && ( {filteredPositions.length > 0 && positionFilter === 'current' && (
<div <div
style={{ style={{
marginTop: '12px', marginTop: '12px',
@@ -1259,13 +1291,11 @@ const PositionList: React.FC = () => {
<span> <span>
{' '} {' '}
<span style={{ fontWeight: 600 }}> <span style={{ fontWeight: 600 }}>
{positionFilter === 'current' {formatUSDC(positionTotals.totalCurrentValue.toString())} USDC
? `${formatUSDC(positionTotals.totalCurrentValue.toString())} USDC`
: '-'}
</span> </span>
</span> </span>
<span> <span>
{' '} {' '}
<span <span
style={{ style={{
fontWeight: 600, fontWeight: 600,
@@ -1295,32 +1325,87 @@ const PositionList: React.FC = () => {
{(isMobile || viewMode === 'card') ? ( {(isMobile || viewMode === 'card') ? (
<Card loading={loading}> <Card loading={loading}>
{renderCardView()} {renderCardView()}
{/* 移动端分页 */}
{filteredPositions.length > 0 && ( {filteredPositions.length > 0 && (
<div style={{ <>
marginTop: '24px', <div style={{
textAlign: 'center', marginTop: '16px',
color: '#999', display: 'flex',
fontSize: '14px' justifyContent: 'space-between',
}}> alignItems: 'center',
{filteredPositions.length} {searchKeyword ? `(已过滤)` : ''} flexWrap: 'wrap',
</div> gap: '8px'
}}>
<div style={{ fontSize: '14px', color: '#666' }}>
{filteredPositions.length} {searchKeyword ? `(已过滤)` : ''}
</div>
<div style={{ display: 'flex', gap: '8px' }}>
<Button
size="small"
disabled={currentPage === 1}
onClick={() => setCurrentPage(currentPage - 1)}
>
</Button>
<span style={{ lineHeight: '32px', fontSize: '14px' }}>
{currentPage} / {Math.ceil(filteredPositions.length / pageSize)}
</span>
<Button
size="small"
disabled={currentPage >= Math.ceil(filteredPositions.length / pageSize)}
onClick={() => setCurrentPage(currentPage + 1)}
>
</Button>
</div>
</div>
{/* 每页条数选择器 */}
<div style={{
marginTop: '8px',
textAlign: 'right',
fontSize: '14px'
}}>
<Select
value={pageSize}
onChange={(value) => {
setPageSize(value)
setCurrentPage(1)
}}
size="small"
style={{ width: '100px' }}
>
<Select.Option value={10}>10 /</Select.Option>
<Select.Option value={20}>20 /</Select.Option>
<Select.Option value={50}>50 /</Select.Option>
</Select>
</div>
</>
)} )}
</Card> </Card>
) : ( ) : (
<Card> <Card>
<Table <Table
dataSource={filteredPositions} dataSource={filteredPositions}
columns={columns} columns={columns}
rowKey={(record, index) => `${record.accountId}-${record.marketId}-${index}`} rowKey={(record, index) => `${record.accountId}-${record.marketId}-${index}`}
loading={loading} loading={loading}
pagination={{ pagination={{
pageSize: 20, current: currentPage,
showSizeChanger: !isMobile, pageSize: pageSize,
showTotal: (total) => `${total} 个仓位${searchKeyword ? `(已过滤)` : ''}` total: filteredPositions.length,
}} showSizeChanger: true,
scroll={isMobile ? { x: 1500 } : undefined} pageSizeOptions: ['10', '20', '50'],
/> showTotal: (total) => `${total} 个仓位${searchKeyword ? `(已过滤)` : ''}`,
</Card> onChange: (page, size) => {
setCurrentPage(page)
if (size !== pageSize) {
setPageSize(size)
}
}
}}
scroll={isMobile ? { x: 1500 } : undefined}
/>
</Card>
)} )}
{/* 出售模态框 */} {/* 出售模态框 */}