Files
PolyHermes/frontend/src/pages/CopyTradingStatistics.tsx
T
WrBugandClaude Opus 4.7 8ab8ae5205 style: 全局将 USDC 展示替换为 $ 符号
前端所有金额展示从 "100.00 USDC" 改为 "$100.00" 格式,
后端 Telegram 通知模板和错误提示同步更新。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 22:23:49 +08:00

258 lines
8.5 KiB
TypeScript

import { useEffect, useState } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { Card, Row, Col, Statistic, Tag, Button, message, Spin } from 'antd'
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined } from '@ant-design/icons'
import { apiService } from '../services/api'
import { formatUSDC, formatNumber } from '../utils'
import { useMediaQuery } from 'react-responsive'
import type { CopyTradingStatistics } from '../types'
const CopyTradingStatisticsPage: React.FC = () => {
const { copyTradingId } = useParams<{ copyTradingId: string }>()
const navigate = useNavigate()
useMediaQuery({ maxWidth: 768 }) // 用于响应式布局,但当前页面未使用
const [loading, setLoading] = useState(false)
const [statistics, setStatistics] = useState<CopyTradingStatistics | null>(null)
useEffect(() => {
if (copyTradingId) {
fetchStatistics()
}
}, [copyTradingId])
const fetchStatistics = async () => {
if (!copyTradingId) return
setLoading(true)
try {
const response = await apiService.statistics.detail({ copyTradingId: parseInt(copyTradingId) })
if (response.data.code === 0 && response.data.data) {
setStatistics(response.data.data)
} else {
message.error(response.data.msg || '获取统计信息失败')
}
} catch (error: any) {
message.error(error.message || '获取统计信息失败')
} finally {
setLoading(false)
}
}
const getPnlColor = (value: string): string => {
const num = parseFloat(value)
if (isNaN(num)) return '#666'
return num >= 0 ? '#3f8600' : '#cf1322'
}
const getPnlIcon = (value: string) => {
const num = parseFloat(value)
if (isNaN(num)) return null
return num >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />
}
const formatPercent = (value: string): string => {
const num = parseFloat(value)
if (isNaN(num)) return '-'
return `${num >= 0 ? '+' : ''}${num.toFixed(2)}%`
}
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '50px' }}>
<Spin size="large" />
</div>
)
}
if (!statistics) {
return (
<Card>
<div style={{ textAlign: 'center', padding: '50px' }}>
<p>暂无统计数据</p>
<Button onClick={() => navigate('/copy-trading')}>返回列表</Button>
</div>
</Card>
)
}
return (
<div>
<Card style={{ marginBottom: 16 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
<Button icon={<LeftOutlined />} onClick={() => navigate('/copy-trading')}>
返回
</Button>
<h2 style={{ margin: 0 }}>跟单关系统计</h2>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<Button onClick={() => navigate(`/copy-trading/orders/buy/${copyTradingId}`)}>
买入订单
</Button>
<Button onClick={() => navigate(`/copy-trading/orders/sell/${copyTradingId}`)}>
卖出订单
</Button>
<Button onClick={() => navigate(`/copy-trading/orders/matched/${copyTradingId}`)}>
匹配关系
</Button>
</div>
</div>
</Card>
{/* 基本信息卡片 */}
<Card title="基本信息" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}>
<div>
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>账户名称</div>
<div style={{ fontSize: 16, fontWeight: 500 }}>
{statistics.accountName || `账户 ${statistics.accountId}`}
</div>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div>
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>Leader 名称</div>
<div style={{ fontSize: 16, fontWeight: 500 }}>
{statistics.leaderName || `Leader ${statistics.leaderId}`}
</div>
</div>
</Col>
<Col xs={24} sm={12} md={8}>
<div>
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>跟单状态</div>
<div>
<Tag color={statistics.enabled ? 'green' : 'red'}>
{statistics.enabled ? '启用' : '禁用'}
</Tag>
</div>
</div>
</Col>
</Row>
</Card>
{/* 买入统计卡片 */}
<Card title="买入统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总买入数量"
value={formatNumber(statistics.totalBuyQuantity, 4)}
suffix=""
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总买入金额"
value={formatUSDC(statistics.totalBuyAmount)}
prefix="$"
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总买入订单数"
value={formatNumber(statistics.totalBuyOrders)}
suffix="笔"
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="平均买入价格"
value={formatNumber(statistics.avgBuyPrice, 4)}
suffix=""
/>
</Col>
</Row>
</Card>
{/* 卖出统计卡片 */}
<Card title="卖出统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={8}>
<Statistic
title="总卖出数量"
value={formatNumber(statistics.totalSellQuantity, 4)}
suffix=""
/>
</Col>
<Col xs={24} sm={12} md={8}>
<Statistic
title="总卖出金额"
value={formatUSDC(statistics.totalSellAmount)}
prefix="$"
/>
</Col>
<Col xs={24} sm={12} md={8}>
<Statistic
title="总卖出订单数"
value={formatNumber(statistics.totalSellOrders)}
suffix="笔"
/>
</Col>
</Row>
</Card>
{/* 持仓统计卡片 */}
<Card title="持仓统计" style={{ marginBottom: 16 }}>
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={12}>
<Statistic
title="当前持仓数量"
value={formatNumber(statistics.currentPositionQuantity, 4)}
suffix=""
/>
</Col>
<Col xs={24} sm={12} md={12}>
<Statistic
title="平均买入价格"
value={formatNumber(statistics.avgBuyPrice, 4)}
suffix=""
/>
</Col>
</Row>
</Card>
{/* 盈亏统计卡片 */}
<Card title="盈亏统计">
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总已实现盈亏"
value={formatUSDC(statistics.totalRealizedPnl)}
valueStyle={{ color: getPnlColor(statistics.totalRealizedPnl) }}
prefix={<>{getPnlIcon(statistics.totalRealizedPnl)} $</>}
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总未实现盈亏"
value={formatUSDC(statistics.totalUnrealizedPnl)}
valueStyle={{ color: getPnlColor(statistics.totalUnrealizedPnl) }}
prefix={<>{getPnlIcon(statistics.totalUnrealizedPnl)} $</>}
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总盈亏"
value={formatUSDC(statistics.totalPnl)}
valueStyle={{ color: getPnlColor(statistics.totalPnl) }}
prefix={<>{getPnlIcon(statistics.totalPnl)} $</>}
/>
</Col>
<Col xs={24} sm={12} md={6}>
<Statistic
title="总盈亏百分比"
value={formatPercent(statistics.totalPnlPercent)}
valueStyle={{ color: getPnlColor(statistics.totalPnlPercent) }}
prefix={getPnlIcon(statistics.totalPnlPercent)}
/>
</Col>
</Row>
</Card>
</div>
)
}
export default CopyTradingStatisticsPage