/** 转义 HTML 并保留换行 */ function escapeWithBreaks(text: string): string { return text .replace(/\r\n/g, '\n') .replace(/\r/g, '\n') .replace(/&/g, '&') .replace(//g, '>') .replace(/\n/g, '
'); } /** @用户名 高亮(data-name 供点击跳转用户主页) */ export function highlightMentions(text: string): string { return escapeWithBreaks(text) .replace( /@([\w\u4e00-\u9fa5_-]+)/g, '@$1', ); } /** 相对时间:刚刚 / N分钟前 / N小时前 / N天前;更早用具体日期 */ export function formatTime(iso: string) { const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; const now = new Date(); const diffSec = Math.max(0, (now.getTime() - d.getTime()) / 1000); if (diffSec < 60) return '刚刚'; if (diffSec < 3600) return `${Math.floor(diffSec / 60)}分钟前`; if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}小时前`; const diffDay = Math.floor(diffSec / 86400); if (diffDay < 30) return `${diffDay}天前`; if (d.getFullYear() === now.getFullYear()) { return `${d.getMonth() + 1}月${d.getDate()}日`; } return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日`; } /** 完整日期时间(用于帖子发布/修改时间展示) */ export function formatDateTime(iso: string) { const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}年${d.getMonth() + 1}月${d.getDate()}日 ${pad(d.getHours())}:${pad(d.getMinutes())}`; } /** 短日期时间(本地时区):MM-DD HH:mm,用于右栏最新评论等 */ export function formatShortDateTime(iso: string) { const d = new Date(iso); if (Number.isNaN(d.getTime())) return iso; const pad = (n: number) => String(n).padStart(2, '0'); return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`; } /** 判断两个 ISO 时间是否相差超过 1 分钟 */ export function isTimeDiffSignificant(a: string, b: string) { const da = new Date(a).getTime(); const db = new Date(b).getTime(); if (Number.isNaN(da) || Number.isNaN(db)) return false; return Math.abs(da - db) > 60_000; }