import type { ResultType } from './types' /** * 成绩 / 时间的展示与录入互转。 * * 库里同时存 result_text(给人看)和 result_value(给排序用), * 两者必须在同一处换算,否则录入页和导出会出现「看起来一样、排出来不一样」。 */ const DATE_PAD = (value: number): string => String(value).padStart(2, '0') /** 秒 → `1:06.33`;不足一分钟用 `6.33`。 */ export function formatSeconds(seconds: number, fractionDigits = 2): string { if (!Number.isFinite(seconds)) return '—' const sign = seconds < 0 ? '-' : '' const abs = Math.abs(seconds) let minutes = Math.floor(abs / 60) let rest = (abs - minutes * 60).toFixed(fractionDigits) // 59.999 会四舍五入成 "60.00",进位到分钟,避免出现 "1:60.00"。 if (Number(rest) >= 60) { minutes += 1 rest = (0).toFixed(fractionDigits) } const body = minutes > 0 ? `${minutes}:${rest.padStart(3 + fractionDigits, '0')}` : rest return `${sign}${body}` } /** `1:06.33` / `66.33` → 66.33;无法解析返回 null。 */ export function parseSeconds(text: string): number | null { const trimmed = text.trim() if (trimmed === '') return null const parts = trimmed.split(':') if (parts.length > 2) return null const numbers: number[] = [] for (const part of parts) { const value = Number(part) if (!Number.isFinite(value)) return null numbers.push(value) } if (numbers.length === 1) return numbers[0] ?? null const minutes = numbers[0] ?? 0 const seconds = numbers[1] ?? 0 if (seconds < 0 || seconds >= 60) return null return minutes * 60 + seconds } /** 按单位把比较值渲染成展示文本,如 `66.33` + `s` → `1:06.33`。 */ export function formatResultText( value: number | null, unit: string, type: ResultType = 'none', ): string { if (value === null || !Number.isFinite(value)) return '—' if (type === 'time' || unit === 's') return formatSeconds(value) if (unit === 'm') return `${value.toFixed(2)}米` if (unit === 'pts') return `${formatPoints(value)}分` if (unit === '次') return `${Math.round(value)}次` return `${formatPoints(value)}${unit}` } /** 展示文本 → 比较值,与 formatResultText 互逆。 */ export function parseResultValue(text: string, unit: string): number | null { const trimmed = text.trim() if (trimmed === '') return null if (unit === 's') return parseSeconds(trimmed) const numeric = Number(trimmed.replace(/[^\d.+-]/g, '')) return Number.isFinite(numeric) ? numeric : null } /** 积分去掉无意义的 `.0`,同时避免 `12.25` 被截成 `12.3`。 */ export function formatPoints(points: number): string { if (!Number.isFinite(points)) return '—' return Number.isInteger(points) ? String(points) : String(Number(points.toFixed(2))) } /** 名次升降:正数为上升。无变化返回空串,交给调用方决定是否占位。 */ export function formatRankChange(delta: number | null): string { if (delta === null || delta === 0) return '' return delta > 0 ? `↑${delta}` : `↓${Math.abs(delta)}` } /** ISO → `YYYY-MM-DD HH:mm`(本地时区)。 */ export function formatDateTime(iso: string | null): string { const date = toDate(iso) if (date === null) return '—' return ( `${date.getFullYear()}-${DATE_PAD(date.getMonth() + 1)}-${DATE_PAD(date.getDate())}` + ` ${DATE_PAD(date.getHours())}:${DATE_PAD(date.getMinutes())}` ) } /** ISO → `HH:mm`,大屏顶栏时钟用。 */ export function formatClock(iso: string | null): string { const date = toDate(iso) if (date === null) return '--:--' return `${DATE_PAD(date.getHours())}:${DATE_PAD(date.getMinutes())}` } function toDate(iso: string | null): Date | null { if (iso === null || iso === '') return null const date = new Date(iso) return Number.isNaN(date.getTime()) ? null : date }