- 重构 formatNumber 和 formatUSDC 函数,默认添加千分位分隔符 - 更新 Statistics.tsx、CopyTradingStatistics.tsx 等统计页面 - 更新 PositionList.tsx 持仓列表的数值显示 - 更新 AccountList.tsx 账户列表的余额显示 - 所有数值(金额、数量、价格等)现在默认显示千分位 - 示例:1234567.89 显示为 1,234,567.89 影响范围: - 工具函数:utils/index.ts - 统计页面:Statistics.tsx, CopyTradingStatistics.tsx - 业务页面:PositionList.tsx, AccountList.tsx Changes: - Refactored formatNumber and formatUSDC to include thousand separators by default - Updated statistics pages to display numbers with commas - Updated position list and account list for better readability - Example: 1234567.89 now displays as 1,234,567.89
165 lines
5.6 KiB
TypeScript
165 lines
5.6 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import { Card, Row, Col, Statistic, message, DatePicker, Space, Button, Typography } from 'antd'
|
|
import { ArrowUpOutlined, ArrowDownOutlined, ReloadOutlined } from '@ant-design/icons'
|
|
import { useTranslation } from 'react-i18next'
|
|
import type { Dayjs } from 'dayjs'
|
|
import { apiService } from '../services/api'
|
|
import type { Statistics as StatisticsType } from '../types'
|
|
import { formatUSDC, formatNumber } from '../utils'
|
|
import { useMediaQuery } from 'react-responsive'
|
|
|
|
const { RangePicker } = DatePicker
|
|
const { Title } = Typography
|
|
|
|
const Statistics: React.FC = () => {
|
|
const { t } = useTranslation()
|
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
|
const [stats, setStats] = useState<StatisticsType | null>(null)
|
|
const [loading, setLoading] = useState(false)
|
|
const [dateRange, setDateRange] = useState<[Dayjs | null, Dayjs | null]>([null, null])
|
|
|
|
useEffect(() => {
|
|
fetchStatistics()
|
|
}, [])
|
|
|
|
const fetchStatistics = async () => {
|
|
setLoading(true)
|
|
try {
|
|
const startTime = dateRange[0] ? dateRange[0].valueOf() : undefined
|
|
const endTime = dateRange[1] ? dateRange[1].valueOf() : undefined
|
|
|
|
const response = await apiService.statistics.global({ startTime, endTime })
|
|
if (response.data.code === 0 && response.data.data) {
|
|
setStats(response.data.data)
|
|
} else {
|
|
message.error(response.data.msg || t('statistics.fetchFailed') || '获取统计信息失败')
|
|
}
|
|
} catch (error: any) {
|
|
message.error(error.message || t('statistics.fetchFailed') || '获取统计信息失败')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleDateRangeChange = (dates: [Dayjs | null, Dayjs | null] | null) => {
|
|
setDateRange(dates || [null, null])
|
|
}
|
|
|
|
const handleReset = () => {
|
|
setDateRange([null, null])
|
|
// 重置后自动刷新
|
|
setTimeout(() => {
|
|
fetchStatistics()
|
|
}, 100)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div style={{ marginBottom: '16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: '12px' }}>
|
|
<Title level={2} style={{ margin: 0 }}>{t('statistics.title') || '统计信息'}</Title>
|
|
<Space size="middle" wrap>
|
|
<RangePicker
|
|
value={dateRange}
|
|
onChange={handleDateRangeChange}
|
|
format="YYYY-MM-DD"
|
|
placeholder={[t('statistics.startDate') || '开始日期', t('statistics.endDate') || '结束日期']}
|
|
size={isMobile ? 'middle' : 'large'}
|
|
allowClear
|
|
/>
|
|
<Button
|
|
type="primary"
|
|
icon={<ReloadOutlined />}
|
|
onClick={fetchStatistics}
|
|
loading={loading}
|
|
size={isMobile ? 'middle' : 'large'}
|
|
>
|
|
{t('statistics.refresh') || '刷新'}
|
|
</Button>
|
|
{(dateRange[0] || dateRange[1]) && (
|
|
<Button
|
|
onClick={handleReset}
|
|
size={isMobile ? 'middle' : 'large'}
|
|
>
|
|
{t('statistics.reset') || '重置'}
|
|
</Button>
|
|
)}
|
|
</Space>
|
|
</div>
|
|
|
|
<Row gutter={[16, 16]}>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.totalOrders') || '总订单数'}
|
|
value={formatNumber(stats?.totalOrders || 0)}
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.totalPnl') || '总盈亏'}
|
|
value={formatUSDC(stats?.totalPnl || '0')}
|
|
prefix={stats?.totalPnl && parseFloat(stats.totalPnl) >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
|
valueStyle={{ color: stats?.totalPnl && parseFloat(stats.totalPnl || '0') >= 0 ? '#3f8600' : '#cf1322' }}
|
|
suffix="USDC"
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.winRate') || '胜率'}
|
|
value={stats?.winRate || '0'}
|
|
precision={2}
|
|
suffix="%"
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.avgPnl') || '平均盈亏'}
|
|
value={formatUSDC(stats?.avgPnl || '0')}
|
|
prefix={stats?.avgPnl && parseFloat(stats.avgPnl || '0') >= 0 ? <ArrowUpOutlined /> : <ArrowDownOutlined />}
|
|
valueStyle={{ color: stats?.avgPnl && parseFloat(stats.avgPnl || '0') >= 0 ? '#3f8600' : '#cf1322' }}
|
|
suffix="USDC"
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.maxProfit') || '最大盈利'}
|
|
value={formatUSDC(stats?.maxProfit || '0')}
|
|
prefix={<ArrowUpOutlined />}
|
|
valueStyle={{ color: '#3f8600' }}
|
|
suffix="USDC"
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col xs={24} sm={12} md={8}>
|
|
<Card>
|
|
<Statistic
|
|
title={t('statistics.maxLoss') || '最大亏损'}
|
|
value={formatUSDC(stats?.maxLoss || '0')}
|
|
prefix={<ArrowDownOutlined />}
|
|
valueStyle={{ color: '#cf1322' }}
|
|
suffix="USDC"
|
|
loading={loading}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default Statistics
|
|
|