import dayjs from 'dayjs' import 'dayjs/locale/zh-cn' import utc from 'dayjs/plugin/utc' import timezone from 'dayjs/plugin/timezone' import relativeTime from 'dayjs/plugin/relativeTime' dayjs.extend(utc) dayjs.extend(timezone) dayjs.extend(relativeTime) dayjs.locale('zh-cn') const DEFAULT_TZ = 'Asia/Shanghai' dayjs.tz.setDefault(DEFAULT_TZ) const NUMERIC_REGEX = /^-?\d+(?:\.\d+)?$/ const coerceDayjs = (value) => { if (value === null || value === undefined) { return null } if (typeof value === 'number') { return dayjs(value).tz(DEFAULT_TZ) } const stringValue = String(value).trim() if (!stringValue) { return null } if (NUMERIC_REGEX.test(stringValue)) { const numeric = Number(stringValue) if (Number.isNaN(numeric)) { return null } // 值小于 10^12 时视为秒级时间戳,否则视为毫秒 if (Math.abs(numeric) < 1e12) { return dayjs.unix(numeric).tz(DEFAULT_TZ) } return dayjs(numeric).tz(DEFAULT_TZ) } // 解析 ISO 字符串(dayjs 会自动识别时区信息,如 Z 后缀表示 UTC) // 需要先转换为 UTC 再设置时区,否则 .tz() 只会改变显示而不会正确转换 const parsed = dayjs(stringValue) if (!parsed.isValid()) { return null } // 先转换为 UTC(保留原始时间值),再转换到上海时区 return parsed.utc().tz(DEFAULT_TZ) } export const parseToShanghai = (value) => coerceDayjs(value) export const formatDateTime = (value, format = 'YYYY-MM-DD HH:mm') => { const parsed = coerceDayjs(value) if (!parsed) return '-' return parsed.format(format) } export const formatFullDateTime = (value) => formatDateTime(value, 'YYYY-MM-DD HH:mm:ss') export const formatRelative = (value) => { const parsed = coerceDayjs(value) if (!parsed) return '-' return parsed.fromNow() } export const sortByDatetimeDesc = (items, accessor) => { const copy = [...items] copy.sort((a, b) => { const first = coerceDayjs(accessor(a)) const second = coerceDayjs(accessor(b)) if (!first && !second) return 0 if (!first) return 1 if (!second) return -1 return second.valueOf() - first.valueOf() }) return copy } export default dayjs