feat: 为所有数值显示添加千分位分隔符

- 重构 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
This commit is contained in:
WrBug
2026-01-31 00:52:42 +08:00
parent e8fd1b503b
commit 40081c2464
5 changed files with 510 additions and 501 deletions
+8 -8
View File
@@ -3,7 +3,7 @@ import { useParams, useNavigate } from 'react-router-dom'
import { Card, Row, Col, Statistic, Tag, Button, message, Spin } from 'antd' import { Card, Row, Col, Statistic, Tag, Button, message, Spin } from 'antd'
import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined } from '@ant-design/icons' import { ArrowUpOutlined, ArrowDownOutlined, LeftOutlined } from '@ant-design/icons'
import { apiService } from '../services/api' import { apiService } from '../services/api'
import { formatUSDC } from '../utils' import { formatUSDC, formatNumber } from '../utils'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import type { CopyTradingStatistics } from '../types' import type { CopyTradingStatistics } from '../types'
@@ -137,7 +137,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="总买入数量" title="总买入数量"
value={formatUSDC(statistics.totalBuyQuantity)} value={formatNumber(statistics.totalBuyQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
@@ -151,14 +151,14 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="总买入订单数" title="总买入订单数"
value={statistics.totalBuyOrders} value={formatNumber(statistics.totalBuyOrders)}
suffix="笔" suffix="笔"
/> />
</Col> </Col>
<Col xs={24} sm={12} md={6}> <Col xs={24} sm={12} md={6}>
<Statistic <Statistic
title="平均买入价格" title="平均买入价格"
value={formatUSDC(statistics.avgBuyPrice)} value={formatNumber(statistics.avgBuyPrice, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
@@ -171,7 +171,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Statistic <Statistic
title="总卖出数量" title="总卖出数量"
value={formatUSDC(statistics.totalSellQuantity)} value={formatNumber(statistics.totalSellQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
@@ -185,7 +185,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={8}> <Col xs={24} sm={12} md={8}>
<Statistic <Statistic
title="总卖出订单数" title="总卖出订单数"
value={statistics.totalSellOrders} value={formatNumber(statistics.totalSellOrders)}
suffix="笔" suffix="笔"
/> />
</Col> </Col>
@@ -198,14 +198,14 @@ const CopyTradingStatisticsPage: React.FC = () => {
<Col xs={24} sm={12} md={12}> <Col xs={24} sm={12} md={12}>
<Statistic <Statistic
title="当前持仓数量" title="当前持仓数量"
value={formatUSDC(statistics.currentPositionQuantity)} value={formatNumber(statistics.currentPositionQuantity, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
<Col xs={24} sm={12} md={12}> <Col xs={24} sm={12} md={12}>
<Statistic <Statistic
title="平均买入价格" title="平均买入价格"
value={formatUSDC(statistics.avgBuyPrice)} value={formatNumber(statistics.avgBuyPrice, 4)}
suffix="" suffix=""
/> />
</Col> </Col>
+2 -2
View File
@@ -8,7 +8,7 @@ import { getPositionKey } from '../types'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
import { useWebSocketSubscription } from '../hooks/useWebSocket' import { useWebSocketSubscription } from '../hooks/useWebSocket'
import { wsManager } from '../services/websocket' import { wsManager } from '../services/websocket'
import { formatUSDC } from '../utils' import { formatUSDC, formatNumber as formatNumberUtil } from '../utils'
type PositionFilter = 'current' | 'historical' type PositionFilter = 'current' | 'historical'
type ViewMode = 'card' | 'list' type ViewMode = 'card' | 'list'
@@ -328,7 +328,7 @@ const PositionList: React.FC = () => {
if (!value) return '-' if (!value) return '-'
const num = parseFloat(value) const num = parseFloat(value)
if (isNaN(num)) return value if (isNaN(num)) return value
return num.toFixed(decimals) return formatNumberUtil(value, decimals)
} }
const formatPercent = (value: string | undefined) => { const formatPercent = (value: string | undefined) => {
+2 -2
View File
@@ -5,7 +5,7 @@ import { useTranslation } from 'react-i18next'
import type { Dayjs } from 'dayjs' import type { Dayjs } from 'dayjs'
import { apiService } from '../services/api' import { apiService } from '../services/api'
import type { Statistics as StatisticsType } from '../types' import type { Statistics as StatisticsType } from '../types'
import { formatUSDC } from '../utils' import { formatUSDC, formatNumber } from '../utils'
import { useMediaQuery } from 'react-responsive' import { useMediaQuery } from 'react-responsive'
const { RangePicker } = DatePicker const { RangePicker } = DatePicker
@@ -91,7 +91,7 @@ const Statistics: React.FC = () => {
<Card> <Card>
<Statistic <Statistic
title={t('statistics.totalOrders') || '总订单数'} title={t('statistics.totalOrders') || '总订单数'}
value={stats?.totalOrders || 0} value={formatNumber(stats?.totalOrders || 0)}
loading={loading} loading={loading}
/> />
</Card> </Card>
+27 -18
View File
@@ -1,12 +1,14 @@
/** /**
* 格式化数字,自动去除尾随零 * 格式化数字,添加千分位分隔符,自动去除尾随零
* @param value - 数字值(字符串或数字) * @param value - 数字值(字符串或数字)
* @param maxDecimals - 最大小数位数(默认不限制) * @param maxDecimals - 最大小数位数(默认不限制)
* @returns 格式化后的字符串,如果值为空或无效则返回 '' * @returns 格式化后的字符串,如 "123,456.78",如果值为空或无效则返回 ''
* @example * @example
* formatNumber(1234567.89) => "1,234,567.89"
* formatNumber(1234567.00) => "1,234,567"
* formatNumber(1234.5678, 2) => "1,234.56"
* formatNumber(100.00) => "100" * formatNumber(100.00) => "100"
* formatNumber(100.50) => "100.5" * formatNumber(100.50) => "100.5"
* formatNumber(100.55) => "100.55"
*/ */
export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => { export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
@@ -18,26 +20,38 @@ export const formatNumber = (value: string | number | undefined | null, maxDecim
return '' return ''
} }
// 如果有最大小数位数限制,先截断 // 处理小数位数
let numStr: string
if (maxDecimals !== undefined) { if (maxDecimals !== undefined) {
const multiplier = Math.pow(10, maxDecimals) const multiplier = Math.pow(10, maxDecimals)
const truncated = Math.floor(num * multiplier) / multiplier const truncated = Math.floor(num * multiplier) / multiplier
return truncated.toFixed(maxDecimals).replace(/\.?0+$/, '') numStr = truncated.toFixed(maxDecimals).replace(/\.?0+$/, '')
} else {
numStr = num.toString().replace(/\.?0+$/, '')
} }
// 直接转换为字符串,然后去除尾随零 // 分离整数和小数部分
return num.toString().replace(/\.?0+$/, '') const parts = numStr.split('.')
const integerPart = parts[0]
const decimalPart = parts[1]
// 为整数部分添加千分位分隔符
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
// 组合结果
return decimalPart ? `${formattedInteger}.${decimalPart}` : formattedInteger
} }
/** /**
* 格式化 USDC 金额 * 格式化 USDC 金额,带千分位分隔符
* 最多显示 4 位小数,自动去除尾随零(截断,不四舍五入) * 最多显示 4 位小数,自动去除尾随零(截断,不四舍五入)
* @param value - 金额值(字符串或数字) * @param value - 金额值(字符串或数字)
* @returns 格式化后的字符串,如果值为空或无效则返回 '-' * @returns 格式化后的字符串,如 "1,234.56",如果值为空或无效则返回 '-'
* @example * @example
* formatUSDC(1234.56) => "1,234.56"
* formatUSDC(1234567.8901) => "1,234,567.8901"
* formatUSDC(1234.00) => "1,234"
* formatUSDC(1.23) => "1.23" * formatUSDC(1.23) => "1.23"
* formatUSDC(1.23456) => "1.2345"
* formatUSDC(1.2) => "1.2"
* formatUSDC(1) => "1" * formatUSDC(1) => "1"
*/ */
export const formatUSDC = (value: string | number | undefined | null): string => { export const formatUSDC = (value: string | number | undefined | null): string => {
@@ -50,12 +64,7 @@ export const formatUSDC = (value: string | number | undefined | null): string =>
return '-' return '-'
} }
// 使用 Math.floor 截断到4位小数(不四舍五入) return formatNumber(num, 4)
const multiplier = Math.pow(10, 4)
const truncated = Math.floor(num * multiplier) / multiplier
// 使用 toFixed(4) 确保格式一致,然后去除尾随零和小数点
return truncated.toFixed(4).replace(/\.?0+$/, '')
} }
// 统一导出 ethers 相关工具函数 // 统一导出 ethers 相关工具函数
@@ -97,7 +106,7 @@ export const isAutoGeneratedOrderId = (orderId: string | undefined | null): bool
/** /**
* 构建 Polymarket 市场 URL * 构建 Polymarket 市场 URL
* 对于 moneyline 市场,跳转到 moneyline 页面 * 对于 moneyline 市场,跳转到 moneyline 页面
* 注意:目前无法自动判断市场是否为 moneyline需要后端提供标识 * 注意:目前无法自动判断市场是否为 moneyline,需要后端提供标识
* @param marketSlug - 市场 slug(用于显示) * @param marketSlug - 市场 slug(用于显示)
* @param eventSlug - 跳转用的 slug(从 events[0].slug 获取,优先使用) * @param eventSlug - 跳转用的 slug(从 events[0].slug 获取,优先使用)
* @param marketCategory - 市场分类(sports, crypto 等) * @param marketCategory - 市场分类(sports, crypto 等)