fix:timeformate
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m9s

This commit is contained in:
luo
2026-09-29 17:43:16 +08:00
parent b11a059896
commit b41772455d
6 changed files with 52 additions and 59 deletions

View File

@@ -69,6 +69,7 @@
import { ref, onMounted } from 'vue'
import { Picture, Paperclip } from '@element-plus/icons-vue'
import { mittBus } from '@/utils/sys'
import { formatDateTime } from '@/utils/business/format'
const { width } = useWindowSize()
const isMobile = computed(() => width.value < 500)
@@ -157,7 +158,7 @@
id: messageId.value++,
sender: 'Ricky',
content: text,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
time: formatDateTime(new Date(), 'HH:mm'),
isMe: true
})

View File

@@ -37,6 +37,7 @@
<script setup lang="ts">
import AppConfig from '@/config'
import { ref } from 'vue'
import { formatDateTime } from '@/utils/business/format'
interface Comment {
id: number
@@ -72,10 +73,7 @@
emit('add-reply', props.comment.id, replyAuthor.value, replyContent.value)
}
const formatDate = (timestamp: string) => {
const date = new Date(timestamp)
return date.toLocaleString()
}
const formatDate = (timestamp: string) => formatDateTime(timestamp)
let lastColor: string | null = null

View File

@@ -93,11 +93,9 @@ declare module 'vue' {
ElAlert: typeof import('element-plus/es')['ElAlert']
ElAvatar: typeof import('element-plus/es')['ElAvatar']
ElButton: typeof import('element-plus/es')['ElButton']
ElCalendar: typeof import('element-plus/es')['ElCalendar']
ElCard: typeof import('element-plus/es')['ElCard']
ElCascader: typeof import('element-plus/es')['ElCascader']
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
@@ -115,6 +113,7 @@ declare module 'vue' {
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElImageViewer: typeof import('element-plus/es')['ElImageViewer']
ElInput: typeof import('element-plus/es')['ElInput']
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
ElMenu: typeof import('element-plus/es')['ElMenu']
@@ -124,8 +123,8 @@ declare module 'vue' {
ElPopover: typeof import('element-plus/es')['ElPopover']
ElProgress: typeof import('element-plus/es')['ElProgress']
ElRadio: typeof import('element-plus/es')['ElRadio']
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
ElResult: typeof import('element-plus/es')['ElResult']
ElRow: typeof import('element-plus/es')['ElRow']
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
ElSelect: typeof import('element-plus/es')['ElSelect']

View File

@@ -168,9 +168,9 @@ export function formatDateRange(startDate: string, endDate: string): string {
/**
* 格式化日期时间
* 规则:
* 1. 带明确日期文本的字符串按原始时间文本展示,不跟随客户端本地时区转换
* 2. 纯数字时间戳支持秒/毫秒自动识别
* 3. Date/时间戳这类“只有绝对时刻、没有原始展示时区”的值,按 UTC 稳定输出
* 1. 带 Z 或 UTC 偏移量的字符串按绝对时刻换算为固定东八区(Asia/Shanghai)
* 2. 不带时区的日期时间字符串按东八区时间文本展示
* 3. 纯数字时间戳支持秒/毫秒自动识别,并按固定东八区展示
* @param date 日期字符串或时间戳(支持 Unix 时间戳秒/毫秒,自动检测)
* @param format 格式化模板,默认 'YYYY-MM-DD HH:mm:ss'
* @returns 格式化后的日期字符串
@@ -222,17 +222,37 @@ function normalizeTimestamp(timestamp: number): number {
return timestamp
}
function getUtcDateTimeParts(date: Date): DateTimeParts {
const DISPLAY_TIMEZONE = 'Asia/Shanghai'
const displayDateTimeFormatter = new Intl.DateTimeFormat('en-GB', {
timeZone: DISPLAY_TIMEZONE,
calendar: 'gregory',
numberingSystem: 'latn',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hourCycle: 'h23'
})
function getDisplayDateTimeParts(date: Date): DateTimeParts {
const parts = displayDateTimeFormatter.formatToParts(date)
const getPart = (type: Intl.DateTimeFormatPartTypes) =>
parts.find((part) => part.type === type)?.value ?? '00'
return {
year: String(date.getUTCFullYear()),
month: String(date.getUTCMonth() + 1).padStart(2, '0'),
day: String(date.getUTCDate()).padStart(2, '0'),
hours: String(date.getUTCHours()).padStart(2, '0'),
minutes: String(date.getUTCMinutes()).padStart(2, '0'),
seconds: String(date.getUTCSeconds()).padStart(2, '0')
year: getPart('year'),
month: getPart('month'),
day: getPart('day'),
hours: getPart('hour'),
minutes: getPart('minute'),
seconds: getPart('second')
}
}
const EXPLICIT_TIMEZONE_SUFFIX_REGEX = /(?:Z|[+-]\d{2}:?\d{2})$/i
export function formatDateTime(
date: string | number | Date | undefined | null,
format: string = 'YYYY-MM-DD HH:mm:ss'
@@ -245,6 +265,11 @@ export function formatDateTime(
const parsedText = parseDateTimeText(trimmedDate)
if (parsedText) {
if (EXPLICIT_TIMEZONE_SUFFIX_REGEX.test(trimmedDate)) {
const parsedDate = new Date(trimmedDate)
if (Number.isNaN(parsedDate.getTime())) return '-'
return formatDateTimeParts(getDisplayDateTimeParts(parsedDate), format)
}
return formatDateTimeParts(parsedText, format)
}
@@ -252,14 +277,14 @@ export function formatDateTime(
if (!Number.isNaN(numericTimestamp)) {
const parsedDate = new Date(normalizeTimestamp(numericTimestamp))
if (Number.isNaN(parsedDate.getTime())) return '-'
return formatDateTimeParts(getUtcDateTimeParts(parsedDate), format)
return formatDateTimeParts(getDisplayDateTimeParts(parsedDate), format)
}
}
const parsedDate = new Date(typeof date === 'number' ? normalizeTimestamp(date) : date)
if (Number.isNaN(parsedDate.getTime())) return '-'
return formatDateTimeParts(getUtcDateTimeParts(parsedDate), format)
return formatDateTimeParts(getDisplayDateTimeParts(parsedDate), format)
}
/**

View File

@@ -2,10 +2,11 @@
* 数据格式化相关工具函数
*/
import { formatDateTime } from '@/utils/business/format'
// 时间戳转时间
export function timestampToTime(timestamp: number = Date.now(), isMs: boolean = true): string {
const date = new Date(isMs ? timestamp : timestamp * 1000)
return date.toISOString().replace('T', ' ').slice(0, 19)
return formatDateTime(isMs ? timestamp : timestamp * 1000)
}
// 数字格式化(千位分隔符)

View File

@@ -434,7 +434,9 @@
</div>
<div class="binding-card-field">
<span class="binding-card-field-label">运营商</span>
<span class="binding-card-field-value">{{ getCarrierAccountDisplay(bindingCard) }}</span>
<span class="binding-card-field-value">{{
getCarrierAccountDisplay(bindingCard)
}}</span>
</div>
<div class="binding-card-field">
<span class="binding-card-field-label">卡槽位置</span>
@@ -467,11 +469,7 @@
<div class="binding-card-field">
<span class="binding-card-field-label">实名时间</span>
<span class="binding-card-field-value">
{{
bindingCard.real_name_at
? formatAssetInfoTime(bindingCard.real_name_at)
: '-'
}}
{{ bindingCard.real_name_at ? formatAssetInfoTime(bindingCard.real_name_at) : '-' }}
</span>
</div>
<div class="binding-card-field">
@@ -888,10 +886,7 @@
return !isAgentOrEnterpriseAccount.value
})
const getCarrierAccountDisplay = (card?: {
carrier_type?: string
carrier_name?: string
}) => {
const getCarrierAccountDisplay = (card?: { carrier_type?: string; carrier_name?: string }) => {
if (isAgentOrEnterpriseAccount.value) {
return getCarrierTypeName(card?.carrier_type)
}
@@ -1144,33 +1139,7 @@
return formatDataSize(value)
}
/**
* 接口中的这几项时间以其原始文本为基准直接加 8 小时;时区后缀不参与换算。
* 例如 2026-08-06T11:54:26.693382+08:00 显示为 2026-08-06 19:54:26。
*/
const formatAssetInfoTime = (value?: string | null) => {
if (!value?.trim()) return '-'
const timeText = value.trim()
const match = timeText.match(
/^(\d{4})-(\d{2})-(\d{2})(?:T|\s)(\d{2}):(\d{2})(?::(\d{2})(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?$/i
)
if (!match) return '-'
const [, year, month, day, hours, minutes, seconds = '00'] = match
const date = new Date(
Date.UTC(
Number(year),
Number(month) - 1,
Number(day),
Number(hours) + 8,
Number(minutes),
Number(seconds)
)
)
return formatDateTime(date)
}
const formatAssetInfoTime = (value?: string | null) => formatDateTime(value)
// 处理轮询开关变化
const handlePollingChange = (value: string | number | boolean) => {