Files
re-xxm/reaction_board/web/static/format.js
T
bot-ci 8a9cb21408 feat: EOD 23:15 seal + midnight seed shell for video/fans
Move daily snapshot/fans to 23:15 and add 00:01 seed that clones
last_completed ok=1 rows into today without advancing completed.
Evening runs force through seed markers; growth previous is per-aid
adjacent. Home shows short-update clock + 全站数据 date, and 收录视频
counts.
2026-11-02 12:38:55 +08:00

152 lines
4.5 KiB
JavaScript

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) >= 1e6) return `${trimZero((number / 1e4).toFixed(2))}万`;
// 小于一百万时直接显示精确值,避免 59,798 与 59,800 都被写成“6万”。
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 formatClock(timestamp) {
if (!timestamp) return "—";
const date = new Date(Number(timestamp) * 1000);
if (Number.isNaN(date.getTime())) return "—";
return new Intl.DateTimeFormat("zh-CN", {
hour: "2-digit", minute: "2-digit", hour12: false,
timeZone: "Asia/Shanghai",
}).format(date);
}
export function formatDataDate(value) {
if (!value) return "—";
return String(value).replace(/-/g, "/");
}
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
export function escapeAttr(value) {
return escapeHtml(value).replace(/`/g, "&#96;");
}
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);
};
}