/** * 格式化数字,添加千分位分隔符,自动去除尾随零 * @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 - 复制成功返回 true,失败返回 false */ export const copyToClipboard = async (text: string): Promise => { 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 } }