86 lines
2.7 KiB
TypeScript
86 lines
2.7 KiB
TypeScript
function formatCompactNumber(value: number, fractionDigits: number): string {
|
||
return value.toFixed(fractionDigits).replace(/\.0$/, '')
|
||
}
|
||
|
||
function getFractionDigits(value: number, unitIndex: number): number {
|
||
if (unitIndex === 0 || value >= 100) return 0
|
||
return 1
|
||
}
|
||
|
||
export function formatHumanBytes(bytes: number): string {
|
||
if (!Number.isFinite(bytes)) return '--'
|
||
|
||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||
const sign = bytes < 0 ? '-' : ''
|
||
let value = Math.abs(bytes)
|
||
let unitIndex = 0
|
||
|
||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||
value /= 1024
|
||
unitIndex += 1
|
||
}
|
||
|
||
return `${sign}${formatCompactNumber(value, getFractionDigits(value, unitIndex))} ${units[unitIndex]}`
|
||
}
|
||
|
||
export function formatHumanSpeed(bytesPerSecond: number): string {
|
||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond < 0) return '--'
|
||
return `${formatHumanBytes(bytesPerSecond)}/s`
|
||
}
|
||
|
||
export function formatDurationSeconds(seconds: number): string {
|
||
if (!Number.isFinite(seconds) || seconds < 0) return '计算中'
|
||
|
||
const wholeSeconds = Math.max(1, Math.round(seconds))
|
||
if (wholeSeconds < 60) {
|
||
return `${wholeSeconds} 秒`
|
||
}
|
||
|
||
const minutes = Math.floor(wholeSeconds / 60)
|
||
const remainSeconds = wholeSeconds % 60
|
||
if (minutes < 60) {
|
||
return remainSeconds > 0 ? `${minutes} 分 ${remainSeconds} 秒` : `${minutes} 分`
|
||
}
|
||
|
||
const hours = Math.floor(minutes / 60)
|
||
const remainMinutes = minutes % 60
|
||
return remainMinutes > 0 ? `${hours} 小时 ${remainMinutes} 分` : `${hours} 小时`
|
||
}
|
||
|
||
export function formatRemainingTime(remainingBytes: number, bytesPerSecond: number): string {
|
||
if (!Number.isFinite(remainingBytes) || remainingBytes <= 0) return '剩余 0 秒'
|
||
if (!Number.isFinite(bytesPerSecond) || bytesPerSecond <= 0) return '剩余时间计算中'
|
||
return `剩余 ${formatDurationSeconds(remainingBytes / bytesPerSecond)}`
|
||
}
|
||
|
||
export function formatTransferProgressSummary(params: {
|
||
speedBytesPerSecond: number
|
||
loadedBytes?: number
|
||
totalBytes?: number
|
||
includeRemaining?: boolean
|
||
}): string {
|
||
const {
|
||
speedBytesPerSecond,
|
||
loadedBytes,
|
||
totalBytes,
|
||
includeRemaining = true,
|
||
} = params
|
||
|
||
const speedText = formatHumanSpeed(speedBytesPerSecond)
|
||
const hasTotal = Number.isFinite(totalBytes) && (totalBytes || 0) > 0
|
||
const safeLoaded = Math.max(0, loadedBytes || 0)
|
||
const safeTotal = hasTotal ? Math.max(0, totalBytes || 0) : 0
|
||
|
||
if (!hasTotal) {
|
||
return speedText
|
||
}
|
||
|
||
const progressText = `${formatHumanBytes(Math.min(safeLoaded, safeTotal))}/${formatHumanBytes(safeTotal)}`
|
||
if (!includeRemaining) {
|
||
return `${speedText} - ${progressText}`
|
||
}
|
||
|
||
const remainingText = formatRemainingTime(safeTotal - safeLoaded, speedBytesPerSecond)
|
||
return `${speedText} - ${progressText},${remainingText}`
|
||
}
|