const COUNT_UNITS = [ { value: 1e8, suffix: "亿" }, { value: 1e4, suffix: "万" }, ]; export function formatCount(value) { if (value === null || value === undefined || Number.isNaN(Number(value))) return "—"; const number = Number(value); for (const unit of COUNT_UNITS) { if (Math.abs(number) >= unit.value) { const scaled = number / unit.value; return `${trimZero(scaled.toFixed(Math.abs(scaled) >= 1000 ? 0 : 1))}${unit.suffix}`; } } return Math.round(number).toLocaleString("zh-CN"); } export function formatAxis(value) { if (value === null || value === undefined || Number.isNaN(Number(value))) return ""; const number = Number(value); if (Math.abs(number) >= 1e8) return `${trimZero((number / 1e8).toFixed(2))}亿`; if (Math.abs(number) >= 1e4) { const scaled = number / 1e4; return `${trimZero(scaled.toFixed(Math.abs(scaled) >= 100 ? 1 : 2))}万`; } return Math.round(number).toLocaleString("zh-CN"); } export function formatNumber(value) { if (value === null || value === undefined || Number.isNaN(Number(value))) return "—"; return Number(value).toLocaleString("zh-CN"); } export function formatDelta(value) { if (value === null || value === undefined || Number.isNaN(Number(value))) return "—"; const number = Number(value); const sign = number > 0 ? "+" : ""; return `${sign}${formatCount(number)}`; } export function formatScore(value, digits = 2) { if (value === null || value === undefined || Number.isNaN(Number(value))) return "—"; return Number(value).toFixed(digits).replace(/\.?0+$/, ""); } export function formatPercent(value, digits = 2) { if (value === null || value === undefined || Number.isNaN(Number(value))) return "—"; return `${(Number(value) * 100).toFixed(digits)}%`; } export function formatDate(timestamp) { if (!timestamp) return "—"; const date = new Date(Number(timestamp) * 1000); if (Number.isNaN(date.getTime())) return "—"; return new Intl.DateTimeFormat("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", }).format(date); } export function formatDateTime(timestamp) { if (!timestamp) return "—"; const date = new Date(Number(timestamp) * 1000); if (Number.isNaN(date.getTime())) return "—"; return new Intl.DateTimeFormat("zh-CN", { month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", }).format(date); } export function formatDuration(seconds) { if (seconds === null || seconds === undefined || Number(seconds) <= 0) return "—"; let total = Math.round(Number(seconds)); const hours = Math.floor(total / 3600); total -= hours * 3600; const minutes = Math.floor(total / 60); const rest = total - minutes * 60; if (hours > 0) { return `${hours}:${String(minutes).padStart(2, "0")}:${String(rest).padStart(2, "0")}`; } return `${minutes}:${String(rest).padStart(2, "0")}`; } export function formatDay(value) { if (!value) return "—"; return String(value).slice(5); } export function trimZero(text) { return String(text).replace(/\.0$/, "").replace(/(\.\d)0$/, "$1"); } export function escapeHtml(value) { if (value === null || value === undefined) return ""; return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } export function escapeAttr(value) { return escapeHtml(value).replace(/`/g, "`"); } export function coverUrl(value) { if (!value) return ""; return value.startsWith("//") ? `https:${value}` : value; } export function metricLabel(metric) { return ({ view: "播放", like: "点赞", coin: "投币", favorite: "收藏", share: "转发", reply: "评论", danmaku: "弹幕", })[metric] || metric; } export function statusLabel(status) { return ({ active: "在库", unfaved: "已取消收藏", removed: "已失效", dropped: "已移出库", })[status] || status || "—"; } export function debounce(fn, wait = 280) { let timer = null; return (...args) => { window.clearTimeout(timer); timer = window.setTimeout(() => fn(...args), wait); }; }