## 修复内容 ### 1. AccountsView 功能恢复 - 恢复3个缺失的模态框组件: - ReAuthAccountModal.vue - 重新授权功能 - AccountTestModal.vue - 测试连接功能 - AccountStatsModal.vue - 查看统计功能 - 恢复 handleTest/handleViewStats/handleReAuth 调用模态框 - 修复 UpdateAccountRequest 类型定义(添加 schedulable 字段) ### 2. DashboardView 修复 - 恢复 formatBalance 函数(支持千位分隔符显示) - 为 UserDashboardStats 添加完整 Props 类型定义 - 为 UserDashboardRecentUsage 添加完整 Props 类型定义 - 优化格式化函数到共享 utils/format.ts ### 3. 类型安全增强 - 修复 UserAttributeOption 索引签名兼容性 - 移除未使用的类型导入 - 所有组件 Props 类型完整 ## 验证结果 - ✅ TypeScript 类型检查通过(0 errors) - ✅ vue-tsc 检查通过(0 errors) - ✅ 所有样式与重构前100%一致 - ✅ 所有功能完整恢复 ## 影响范围 - AccountsView: 代码行数从974行优化到189行(提升80.6%可维护性) - DashboardView: 保持组件化同时恢复所有原有功能 - 深色模式支持完整 - 所有颜色方案和 SVG 图标保持一致 Closes #149
184 lines
5.2 KiB
TypeScript
184 lines
5.2 KiB
TypeScript
/**
|
||
* 格式化工具函数
|
||
* 参考 CRS 项目的 format.js 实现
|
||
*/
|
||
|
||
import { i18n, getLocale } from '@/i18n'
|
||
|
||
/**
|
||
* 格式化相对时间
|
||
* @param date 日期字符串或 Date 对象
|
||
* @returns 相对时间字符串,如 "5m ago", "2h ago", "3d ago"
|
||
*/
|
||
export function formatRelativeTime(date: string | Date | null | undefined): string {
|
||
if (!date) return i18n.global.t('common.time.never')
|
||
|
||
const now = new Date()
|
||
const past = new Date(date)
|
||
const diffMs = now.getTime() - past.getTime()
|
||
|
||
// 处理未来时间或无效日期
|
||
if (diffMs < 0 || isNaN(diffMs)) return i18n.global.t('common.time.never')
|
||
|
||
const diffSecs = Math.floor(diffMs / 1000)
|
||
const diffMins = Math.floor(diffSecs / 60)
|
||
const diffHours = Math.floor(diffMins / 60)
|
||
const diffDays = Math.floor(diffHours / 24)
|
||
|
||
if (diffDays > 0) return i18n.global.t('common.time.daysAgo', { n: diffDays })
|
||
if (diffHours > 0) return i18n.global.t('common.time.hoursAgo', { n: diffHours })
|
||
if (diffMins > 0) return i18n.global.t('common.time.minutesAgo', { n: diffMins })
|
||
return i18n.global.t('common.time.justNow')
|
||
}
|
||
|
||
/**
|
||
* 格式化数字(支持 K/M/B 单位)
|
||
* @param num 数字
|
||
* @returns 格式化后的字符串,如 "1.2K", "3.5M"
|
||
*/
|
||
export function formatNumber(num: number | null | undefined): string {
|
||
if (num === null || num === undefined) return '0'
|
||
|
||
const locale = getLocale()
|
||
const absNum = Math.abs(num)
|
||
|
||
// Use Intl.NumberFormat for compact notation if supported and needed
|
||
// Note: Compact notation in 'zh' uses '万/亿', which is appropriate for Chinese
|
||
const formatter = new Intl.NumberFormat(locale, {
|
||
notation: absNum >= 10000 ? 'compact' : 'standard',
|
||
maximumFractionDigits: 1
|
||
})
|
||
|
||
return formatter.format(num)
|
||
}
|
||
|
||
/**
|
||
* 格式化货币金额
|
||
* @param amount 金额
|
||
* @param currency 货币代码,默认 USD
|
||
* @returns 格式化后的字符串,如 "$1.25"
|
||
*/
|
||
export function formatCurrency(amount: number | null | undefined, currency: string = 'USD'): string {
|
||
if (amount === null || amount === undefined) return '$0.00'
|
||
|
||
const locale = getLocale()
|
||
|
||
// For very small amounts, show more decimals
|
||
const fractionDigits = amount > 0 && amount < 0.01 ? 6 : 2
|
||
|
||
return new Intl.NumberFormat(locale, {
|
||
style: 'currency',
|
||
currency: currency,
|
||
minimumFractionDigits: fractionDigits,
|
||
maximumFractionDigits: fractionDigits
|
||
}).format(amount)
|
||
}
|
||
|
||
/**
|
||
* 格式化字节大小
|
||
* @param bytes 字节数
|
||
* @param decimals 小数位数
|
||
* @returns 格式化后的字符串,如 "1.5 MB"
|
||
*/
|
||
export function formatBytes(bytes: number, decimals: number = 2): string {
|
||
if (bytes === 0) return '0 Bytes'
|
||
|
||
const k = 1024
|
||
const dm = decimals < 0 ? 0 : decimals
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
|
||
}
|
||
|
||
/**
|
||
* 格式化日期
|
||
* @param date 日期字符串或 Date 对象
|
||
* @param options Intl.DateTimeFormatOptions
|
||
* @returns 格式化后的日期字符串
|
||
*/
|
||
export function formatDate(
|
||
date: string | Date | null | undefined,
|
||
options: Intl.DateTimeFormatOptions = {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit',
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit',
|
||
hour12: false
|
||
}
|
||
): string {
|
||
if (!date) return ''
|
||
|
||
const d = new Date(date)
|
||
if (isNaN(d.getTime())) return ''
|
||
|
||
const locale = getLocale()
|
||
return new Intl.DateTimeFormat(locale, options).format(d)
|
||
}
|
||
|
||
/**
|
||
* 格式化日期(只显示日期部分)
|
||
* @param date 日期字符串或 Date 对象
|
||
* @returns 格式化后的日期字符串
|
||
*/
|
||
export function formatDateOnly(date: string | Date | null | undefined): string {
|
||
return formatDate(date, {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit'
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 格式化日期时间(完整格式)
|
||
* @param date 日期字符串或 Date 对象
|
||
* @returns 格式化后的日期时间字符串
|
||
*/
|
||
export function formatDateTime(date: string | Date | null | undefined): string {
|
||
return formatDate(date)
|
||
}
|
||
|
||
/**
|
||
* 格式化时间(只显示时分)
|
||
* @param date 日期字符串或 Date 对象
|
||
* @returns 格式化后的时间字符串
|
||
*/
|
||
export function formatTime(date: string | Date | null | undefined): string {
|
||
return formatDate(date, {
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
hour12: false
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 格式化数字(千分位分隔,不使用紧凑单位)
|
||
* @param num 数字
|
||
* @returns 格式化后的字符串,如 "12,345"
|
||
*/
|
||
export function formatNumberLocaleString(num: number): string {
|
||
return num.toLocaleString()
|
||
}
|
||
|
||
/**
|
||
* 格式化金额(固定小数位,不带货币符号)
|
||
* @param amount 金额
|
||
* @param fractionDigits 小数位数,默认 4
|
||
* @returns 格式化后的字符串,如 "1.2345"
|
||
*/
|
||
export function formatCostFixed(amount: number, fractionDigits: number = 4): string {
|
||
return amount.toFixed(fractionDigits)
|
||
}
|
||
|
||
/**
|
||
* 格式化 token 数量(>=1000 显示为 K,保留 1 位小数)
|
||
* @param tokens token 数量
|
||
* @returns 格式化后的字符串,如 "950", "1.2K"
|
||
*/
|
||
export function formatTokensK(tokens: number): string {
|
||
return tokens >= 1000 ? `${(tokens / 1000).toFixed(1)}K` : tokens.toString()
|
||
}
|