40081c2464
- 重构 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
174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
/**
|
||
* 格式化数字,添加千分位分隔符,自动去除尾随零
|
||
* @param value - 数字值(字符串或数字)
|
||
* @param maxDecimals - 最大小数位数(默认不限制)
|
||
* @returns 格式化后的字符串,如 "123,456.78",如果值为空或无效则返回 ''
|
||
* @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.50) => "100.5"
|
||
*/
|
||
export const formatNumber = (value: string | number | undefined | null, maxDecimals?: number): string => {
|
||
if (value === undefined || value === null || value === '') {
|
||
return ''
|
||
}
|
||
|
||
const num = typeof value === 'string' ? parseFloat(value) : value
|
||
if (isNaN(num)) {
|
||
return ''
|
||
}
|
||
|
||
// 处理小数位数
|
||
let numStr: string
|
||
if (maxDecimals !== undefined) {
|
||
const multiplier = Math.pow(10, maxDecimals)
|
||
const truncated = Math.floor(num * multiplier) / multiplier
|
||
numStr = truncated.toFixed(maxDecimals).replace(/\.?0+$/, '')
|
||
} else {
|
||
numStr = 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 金额,带千分位分隔符
|
||
* 最多显示 4 位小数,自动去除尾随零(截断,不四舍五入)
|
||
* @param value - 金额值(字符串或数字)
|
||
* @returns 格式化后的字符串,如 "1,234.56",如果值为空或无效则返回 '-'
|
||
* @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) => "1"
|
||
*/
|
||
export const formatUSDC = (value: string | number | undefined | null): string => {
|
||
if (value === undefined || value === null || value === '') {
|
||
return '-'
|
||
}
|
||
|
||
const num = typeof value === 'string' ? parseFloat(value) : value
|
||
if (isNaN(num)) {
|
||
return '-'
|
||
}
|
||
|
||
return formatNumber(num, 4)
|
||
}
|
||
|
||
// 统一导出 ethers 相关工具函数
|
||
export {
|
||
getAddressFromPrivateKey,
|
||
getAddressFromMnemonic,
|
||
getPrivateKeyFromMnemonic,
|
||
isValidMnemonic,
|
||
isValidWalletAddress,
|
||
isValidPrivateKey
|
||
} from './ethers'
|
||
|
||
// 统一导出 auth 相关工具函数
|
||
export {
|
||
getToken,
|
||
setToken,
|
||
removeToken,
|
||
hasToken
|
||
} from './auth'
|
||
|
||
// 统一导出 version 相关工具函数
|
||
export {
|
||
getVersionInfo,
|
||
getVersionText,
|
||
getGitHubTagUrl
|
||
} from './version'
|
||
|
||
/**
|
||
* 检查订单ID是否为自动生成的
|
||
* 自动生成的订单ID通常以 "AUTO_" 或 "AUTO_FIFO_" 开头
|
||
* @param orderId - 订单ID
|
||
* @returns 如果是自动生成的订单ID,返回 true,否则返回 false
|
||
*/
|
||
export const isAutoGeneratedOrderId = (orderId: string | undefined | null): boolean => {
|
||
if (!orderId) return false
|
||
return orderId.startsWith('AUTO_') || orderId.startsWith('AUTO_FIFO_')
|
||
}
|
||
|
||
/**
|
||
* 构建 Polymarket 市场 URL
|
||
* 对于 moneyline 市场,跳转到 moneyline 页面
|
||
* 注意:目前无法自动判断市场是否为 moneyline,需要后端提供标识
|
||
* @param marketSlug - 市场 slug(用于显示)
|
||
* @param eventSlug - 跳转用的 slug(从 events[0].slug 获取,优先使用)
|
||
* @param marketCategory - 市场分类(sports, crypto 等)
|
||
* @param marketId - 市场ID(作为后备)
|
||
* @param isMoneyline - 是否为 moneyline 市场(需要后端提供)
|
||
* @returns Polymarket URL,如果无法构建则返回 null
|
||
*/
|
||
export const getPolymarketUrl = (
|
||
marketSlug?: string | null,
|
||
eventSlug?: string | null, // 跳转用的 slug(优先使用)
|
||
_marketCategory?: string | null, // 保留参数以便未来使用
|
||
marketId?: string | null,
|
||
isMoneyline?: boolean
|
||
): string | null => {
|
||
// 优先使用 eventSlug(跳转用的 slug)
|
||
const slug = eventSlug || marketSlug
|
||
|
||
if (slug) {
|
||
// 如果是 moneyline 市场,跳转到 moneyline 页面
|
||
if (isMoneyline === true) {
|
||
return `https://polymarket.com/event/${slug}/moneyline`
|
||
}
|
||
// 其他市场跳转到普通市场页面
|
||
return `https://polymarket.com/event/${slug}`
|
||
}
|
||
|
||
// 如果没有 slug,使用 marketId(作为后备)
|
||
if (marketId && marketId.startsWith('0x')) {
|
||
return `https://polymarket.com/condition/${marketId}`
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
/**
|
||
* 复制文本到剪贴板
|
||
* @param text - 要复制的文本
|
||
* @returns Promise<boolean> - 复制成功返回 true,失败返回 false
|
||
*/
|
||
export const copyToClipboard = async (text: string): Promise<boolean> => {
|
||
try {
|
||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||
await navigator.clipboard.writeText(text)
|
||
return true
|
||
} else {
|
||
// 降级方案:使用传统的 document.execCommand
|
||
const textArea = document.createElement('textarea')
|
||
textArea.value = text
|
||
textArea.style.position = 'fixed'
|
||
textArea.style.left = '-999999px'
|
||
textArea.style.top = '-999999px'
|
||
document.body.appendChild(textArea)
|
||
textArea.focus()
|
||
textArea.select()
|
||
const successful = document.execCommand('copy')
|
||
document.body.removeChild(textArea)
|
||
return successful
|
||
}
|
||
} catch (error) {
|
||
console.error('复制到剪贴板失败:', error)
|
||
return false
|
||
}
|
||
}
|
||
|