export function cn(...parts: Array): string { return parts.filter(Boolean).join(' ') } export function formatDate(unix: number): string { return new Date(unix * 1000).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric', }) } export function formatDateTime(unix: number): string { return new Date(unix * 1000).toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) } // 设计稿统一使用 YYYY-MM-DD 等宽日期 export function formatDateISO(unix: number): string { const d = new Date(unix * 1000) const p = (n: number) => String(n).padStart(2, '0') return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}` } // 阅读时长估算:中文约 400 字/分钟 export function readingMinutes(content: string): number { return Math.max(1, Math.round(content.length / 400)) } // 回收站剩余保留天数(30 天保留期) export function daysRemaining(deletedAt: number): number { const elapsedDays = Math.floor((Date.now() / 1000 - deletedAt) / 86400) return Math.max(0, 30 - elapsedDays) } // 前端 slug 预览:与服务端 slugify 同规则(ASCII 部分),纯非 ASCII 时提示将自动生成 export function slugPreview(title: string, date: Date): string { const t = title.trim().toLowerCase().replace(/_/g, '-').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').replace(/^-+|-+$/g, '') if (t === '') return `post-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}` return t.slice(0, 80).replace(/-+$/g, '') }