fix: 优化仓位列表分页和统计功能
- 添加完整的分页功能,支持移动端和桌面端 - 移动端:自定义分页控件,支持切换每页 10/20/50 条 - 桌面端:使用 Ant Design Table 分页,支持切换每页 10/20/50 条 - 优化仓位统计逻辑,区分当前仓位和历史仓位 - 当前仓位:显示浮动盈亏和已实现盈亏 - 历史仓位:移除盈亏统计显示 - 筛选条件变化时自动重置到第一页
This commit is contained in:
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 出售模态框 */}
|
{/* 出售模态框 */}
|
||||||
|
|||||||
Reference in New Issue
Block a user