unify Shanghai time across Semi UI workspaces
This commit is contained in:
@@ -7,8 +7,10 @@ import { api } from '../../api/client';
|
||||
import type { HistoryDataResponse, HistoryDataRow, HistoryExportRequest, HistoryMetricDefinition, HistorySeriesResponse } from '../../api/types';
|
||||
import { buildHistorySeriesPanels, formatExportFileSize, formatHistoryValue, formatSeriesGrain, historyExportPollInterval, parseHistoryKeywords } from '../domain/history';
|
||||
import { downloadBlob } from '../domain/download';
|
||||
import { formatShanghaiDateTime } from '../domain/formatters';
|
||||
import { InlineError, PanelEmpty, PanelLoading } from '../shared/AsyncState';
|
||||
import { MonitorReturnBar } from '../shared/MonitorReturnBar';
|
||||
import { PlatformTime } from '../shared/PlatformTime';
|
||||
import { TablePagination } from '../shared/TablePagination';
|
||||
import { WorkspaceCommandBar } from '../shared/WorkspaceCommandBar';
|
||||
import { WorkspaceFilterPanel } from '../shared/WorkspaceFilterPanel';
|
||||
@@ -25,16 +27,6 @@ import { useSideSheetA11y } from '../hooks/useSideSheetA11y';
|
||||
const DESKTOP_HISTORY_PAGE_SIZE = 50;
|
||||
const MOBILE_HISTORY_PAGE_SIZE = 20;
|
||||
const historyAxisTimeFormatter = new Intl.DateTimeFormat('zh-CN', { timeZone: 'Asia/Shanghai', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false });
|
||||
const historyDateTimeFormatter = new Intl.DateTimeFormat('sv-SE', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
|
||||
function historyQualityLabel(quality: string) {
|
||||
if (quality === 'normal' || quality === 'good') return '正常';
|
||||
@@ -99,11 +91,7 @@ export function formatAxisTime(value: string) {
|
||||
}
|
||||
|
||||
export function formatHistoryDateTime(value: string) {
|
||||
const fallback = value.replace('T', ' ').replace(/\.\d{1,9}/, '').replace(/Z$/, '').slice(0, 19);
|
||||
if (!/(?:Z|[+-]\d{2}:?\d{2})$/i.test(value)) return fallback;
|
||||
const parsed = new Date(value);
|
||||
if (!Number.isFinite(parsed.getTime())) return fallback;
|
||||
return historyDateTimeFormatter.format(parsed).replace('T', ' ');
|
||||
return formatShanghaiDateTime(value);
|
||||
}
|
||||
|
||||
function currentHistoryWindow() {
|
||||
@@ -152,8 +140,8 @@ function EvidencePanel({ row, metrics, onClose, showHeader = true }: { row?: His
|
||||
{row ? <><Descriptions className="v2-history-evidence-descriptions" align="left" size="small" data={[
|
||||
{ key: '车牌', value: row.plate || '—' },
|
||||
{ key: 'VIN', value: row.vin },
|
||||
{ key: '设备时间', value: <time className="v2-history-time" dateTime={row.deviceTime} title={row.deviceTime}>{formatHistoryDateTime(row.deviceTime)}</time> },
|
||||
{ key: '服务时间', value: <time className="v2-history-time" dateTime={row.serverTime} title={row.serverTime}>{formatHistoryDateTime(row.serverTime)}</time> },
|
||||
{ key: '设备时间', value: <PlatformTime className="v2-history-time" value={row.deviceTime} /> },
|
||||
{ key: '服务时间', value: <PlatformTime className="v2-history-time" value={row.serverTime} /> },
|
||||
{ key: '数据来源', value: <Tag color="blue" type="light" size="small">{row.protocol}</Tag> },
|
||||
{ key: '数据质量', value: <HistoryQualityTag quality={row.quality} /> },
|
||||
{ key: '质量原因', value: row.qualityReason || '平台未返回质量说明' }
|
||||
@@ -203,8 +191,8 @@ function HistoryDataTable({ rows, metrics, selectedRowID, onSelect }: { rows: Hi
|
||||
title: '', dataIndex: 'selection', width: 42,
|
||||
render: (_: unknown, row: HistoryDataRow) => <Checkbox checked={selectedRowID === row.id} onChange={() => onSelect(row)} aria-label={`选择 ${row.plate || row.vin} ${row.deviceTime}`} />
|
||||
},
|
||||
{ title: '设备时间', dataIndex: 'deviceTime', width: 150, render: (value: string) => <time className="v2-history-time" dateTime={value} title={value}>{formatHistoryDateTime(value)}</time> },
|
||||
{ title: '服务时间', dataIndex: 'serverTime', width: 150, render: (value: string) => <time className="v2-history-time" dateTime={value} title={value}>{formatHistoryDateTime(value)}</time> },
|
||||
{ title: '设备时间', dataIndex: 'deviceTime', width: 150, render: (value: string) => <PlatformTime className="v2-history-time" value={value} /> },
|
||||
{ title: '服务时间', dataIndex: 'serverTime', width: 150, render: (value: string) => <PlatformTime className="v2-history-time" value={value} /> },
|
||||
{ title: '车牌', dataIndex: 'plate', width: 110, render: (value: string) => value || '—' },
|
||||
{ title: 'VIN', dataIndex: 'vin', width: 160, render: (value: string) => <span className="v2-history-vin" title={value}>{value}</span> },
|
||||
{ title: '协议', dataIndex: 'protocol', width: 100 },
|
||||
@@ -440,7 +428,7 @@ export default function HistoryPage() {
|
||||
aria-label={mobileLayout ? '历史明细列表,可上下滚动' : undefined}
|
||||
>
|
||||
{mobileLayout
|
||||
? <div className="v2-history-mobile-list">{resultRows.map((row) => <Card key={row.id} className={`v2-history-mobile-card${selectedRow?.id === row.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-history-mobile-action" aria-pressed={selectedRow?.id === row.id} aria-expanded={selectedRow?.id === row.id} aria-label={`查看 ${row.plate || row.vin} ${row.deviceTime} 数据详情`} onClick={() => selectRow(row)}><span className="v2-history-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><time className="v2-history-time" dateTime={row.deviceTime} title={row.deviceTime}>{formatHistoryDateTime(row.deviceTime)}</time><b>{row.protocol}</b></p><small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small><dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><footer>查看完整详情<IconChevronRight /></footer></span></Button></Card>)}</div>
|
||||
? <div className="v2-history-mobile-list">{resultRows.map((row) => <Card key={row.id} className={`v2-history-mobile-card${selectedRow?.id === row.id ? ' is-selected' : ''}`} bodyStyle={{ padding: 0 }}><Button theme="borderless" type="tertiary" className="v2-history-mobile-action" aria-pressed={selectedRow?.id === row.id} aria-expanded={selectedRow?.id === row.id} aria-label={`查看 ${row.plate || row.vin} ${row.deviceTime} 数据详情`} onClick={() => selectRow(row)}><span className="v2-history-mobile-card-content"><header><span><strong>{row.plate || '未绑定车牌'}</strong><small>{row.vin}</small></span><span className={`v2-quality is-${row.quality}`}><i />{historyQualityLabel(row.quality)}</span></header><p><PlatformTime className="v2-history-time" value={row.deviceTime} /><b>{row.protocol}</b></p><small className="v2-history-mobile-quality">{row.qualityReason || '平台未返回质量说明'}</small><dl>{visibleMetrics.slice(0, 4).map((metric) => <div key={metric.key}><dt>{metric.label}</dt><dd>{formatHistoryValue(row.values[metric.key], metric)}</dd></div>)}</dl><footer>查看完整详情<IconChevronRight /></footer></span></Button></Card>)}</div>
|
||||
: <HistoryDataTable rows={resultRows} metrics={visibleMetrics} selectedRowID={selectedRow?.id} onSelect={selectRow} />}
|
||||
{dataQuery.isPending && keywords.length
|
||||
? <PanelLoading className="v2-history-loading" title="正在加载当前筛选范围的历史数据" description="按车辆、时间和协议整理可追溯证据。" />
|
||||
|
||||
Reference in New Issue
Block a user