fix:timeformate
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m9s
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 8m9s
This commit is contained in:
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
5
src/types/components.d.ts
vendored
5
src/types/components.d.ts
vendored
@@ -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']
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
// 数字格式化(千位分隔符)
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user