diff --git a/frontend/src/components/RelativeTime.vue b/frontend/src/components/RelativeTime.vue new file mode 100644 index 0000000..7a8411d --- /dev/null +++ b/frontend/src/components/RelativeTime.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/frontend/src/utils/time.ts b/frontend/src/utils/time.ts new file mode 100644 index 0000000..0acf99c --- /dev/null +++ b/frontend/src/utils/time.ts @@ -0,0 +1,110 @@ +/** + * 相对时间工具函数 + * 将时间字符串转换为相对时间显示文本 + */ + +export interface RelativeTimeResult { + /** 相对时间显示文本 */ + displayText: string + /** 完整时间字符串(hover显示) */ + fullTime: string + /** 是否需要自动刷新(10分钟内) */ + needRefresh: boolean +} + +/** + * 格式化相对时间 + * @param timeStr 时间字符串,支持格式:'yyyy-MM-dd HH:mm:ss' | Date | number(时间戳) + * @returns 相对时间结果 + */ +export function formatRelativeTime(timeStr: string | Date | number | null | undefined): RelativeTimeResult { + if (!timeStr) { + return { displayText: '--', fullTime: '--', needRefresh: false } + } + + let date: Date + if (timeStr instanceof Date) { + date = timeStr + } else if (typeof timeStr === 'number') { + date = new Date(timeStr) + } else { + date = new Date(timeStr.replace(/-/g, '/')) // 兼容iOS Safari的日期解析 + } + + if (isNaN(date.getTime())) { + return { displayText: '--', fullTime: String(timeStr), needRefresh: false } + } + + const now = Date.now() + const diff = now - date.getTime() + const absDiff = Math.abs(diff) + + // 完整时间 + const fullTime = formatFullTime(date) + + // 未来时间直接返回完整时间 + if (diff < -60000) { + return { displayText: fullTime, fullTime, needRefresh: false } + } + + // 10秒内 + if (absDiff < 10 * 1000) { + return { displayText: '刚刚', fullTime, needRefresh: true } + } + + // 1分钟内 + if (absDiff < 60 * 1000) { + const seconds = Math.floor(absDiff / 1000) + return { displayText: `${seconds}秒前`, fullTime, needRefresh: true } + } + + // 1小时内 + if (absDiff < 60 * 60 * 1000) { + const minutes = Math.floor(absDiff / (60 * 1000)) + return { displayText: `${minutes}分钟前`, fullTime, needRefresh: true } + } + + // 24小时内 + if (absDiff < 24 * 60 * 60 * 1000) { + const hours = Math.floor(absDiff / (60 * 60 * 1000)) + const minutes = Math.floor((absDiff % (60 * 60 * 1000)) / (60 * 1000)) + if (minutes > 0) { + return { displayText: `${hours}小时${minutes}分钟前`, fullTime, needRefresh: true } + } + return { displayText: `${hours}小时前`, fullTime, needRefresh: true } + } + + // 2天内(昨天/前天) + if (absDiff < 2 * 24 * 60 * 60 * 1000) { + return { displayText: '1天前', fullTime, needRefresh: false } + } + + // 7天内 + if (absDiff < 7 * 24 * 60 * 60 * 1000) { + const days = Math.floor(absDiff / (24 * 60 * 60 * 1000)) + return { displayText: `${days}天前`, fullTime, needRefresh: false } + } + + // 超过7天:显示 MM-DD HH:mm + return { displayText: formatShortDate(date), fullTime, needRefresh: false } +} + +/** 格式化完整时间 yyyy-MM-dd HH:mm:ss */ +function formatFullTime(date: Date): string { + const y = date.getFullYear() + const m = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + const h = String(date.getHours()).padStart(2, '0') + const min = String(date.getMinutes()).padStart(2, '0') + const s = String(date.getSeconds()).padStart(2, '0') + return `${y}-${m}-${d} ${h}:${min}:${s}` +} + +/** 格式化短日期 MM-DD HH:mm */ +function formatShortDate(date: Date): string { + const m = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + const h = String(date.getHours()).padStart(2, '0') + const min = String(date.getMinutes()).padStart(2, '0') + return `${m}-${d} ${h}:${min}` +}