/** 纯格式化函数。界面里所有"给人看的数字"都从这里过一遍。 */ /** `0x3D4D8A9A`。与 Rust 侧的 `snapshot::id_text` 输出一致。 */ export function idText(id: number | null | undefined): string { if (id === null || id === undefined) return "—"; return `0x${id.toString(16).toUpperCase().padStart(8, "0")}`; } /** 22000 → `21.5 kB`。 */ export function bytes(value: number): string { if (value <= 0) return "—"; if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} kB`; return `${(value / 1024 / 1024).toFixed(2)} MB`; } /** 毫秒 → 人话。 */ export function elapsed(ms: number | null | undefined): string { if (ms === null || ms === undefined) return "—"; if (ms < 1000) return `${Math.round(ms)} ms`; if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`; const totalSeconds = Math.round(ms / 1000); const minutes = Math.floor(totalSeconds / 60); const seconds = totalSeconds % 60; if (minutes < 60) return `${minutes} 分 ${seconds} 秒`; const hours = Math.floor(minutes / 60); return `${hours} 小时 ${minutes % 60} 分`; } /** 录制时长:从开始到现在的墙钟。 */ export function since(ms: number | null | undefined, now = Date.now()): string { if (!ms) return "—"; return elapsed(now - ms); } /** Unix 毫秒 → `14:03:22`。 */ export function clockTime(ms: number | null | undefined): string { if (!ms) return "—"; const date = new Date(ms); const pad = (value: number) => value.toString().padStart(2, "0"); return `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`; } /** 小数位固定的帧率。0 表示"还没读数",显示成 `—` 而不是 `0.0`。 */ export function fps(value: number): string { if (!Number.isFinite(value) || value <= 0) return "—"; return value.toFixed(1); } /** 队伍名的兜底值。与 Rust 侧的 `config::DEFAULT_TEAM_NAMES` 一致。 */ export const DEFAULT_TEAM_NAMES = ["队伍一", "队伍二"]; /** * 队伍名。 * * 名字是**配置里可以改的**(裁判端能直接编辑),所以显示的地方一律从这里取:散在各处的 * `team === 0 ? "队伍一" : "队伍二"` 会在改名之后留下一半旧名字,而且不会报错。 * * `names` 传配置里的两条;缺省或留空时回落到默认名。 */ export function teamName(team: number, names?: readonly string[] | null): string { const custom = names?.[team]?.trim(); if (custom) return custom; return DEFAULT_TEAM_NAMES[team] ?? "队伍"; } /** 队伍配色:与装甲板灯色一致(队伍一红、队伍二蓝)。 */ export function teamColor(team: number): string { return team === 0 ? "#ff5f5f" : "#5aa9ff"; } /** 把 `#RRGGBB` 变成 `rgba(...)`。瞄准器颜色要带透明度时用。 */ export function withAlpha(hex: string, alpha: number): string { const match = /^#?([0-9a-f]{6})$/i.exec(hex.trim()); if (!match) return hex; const value = parseInt(match[1], 16); const r = (value >> 16) & 0xff; const g = (value >> 8) & 0xff; const b = value & 0xff; return `rgba(${r}, ${g}, ${b}, ${alpha})`; }