/** * 后台统一按北京时间展示。 * * 后端返回的是 ISO 字符串(UTC,带 Z),直接切片会显示成 UTC—— * 中午 13:23 会显示成 05:23。所有时间展示都走这里。不要再用 slice。 */ const TIME_ZONE = 'Asia/Shanghai'; function partsOf(value: unknown) { const date = new Date(String(value)); if (Number.isNaN(date.getTime())) return null; const parts = new Intl.DateTimeFormat('zh-CN', { timeZone: TIME_ZONE, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hourCycle: 'h23', }).formatToParts(date); const pick = (type: string) => parts.find((p) => p.type === type)?.value ?? ''; return { y: pick('year'), m: pick('month'), d: pick('day'), h: pick('hour'), mi: pick('minute') }; } /** ISO → 2026-09-30 13:23 */ export function formatDateTime(value: unknown): string { if (value === null || value === undefined || value === '') return '—'; const p = partsOf(value); return p ? `${p.y}-${p.m}-${p.d} ${p.h}:${p.mi}` : String(value).slice(0, 16).replace('T', ' '); } /** ISO → 2026-09-30 */ export function formatDate(value: unknown): string { if (value === null || value === undefined || value === '') return '—'; const p = partsOf(value); return p ? `${p.y}-${p.m}-${p.d}` : String(value).slice(0, 10); }