fix(dashboard): normalize task modal timestamps to local time (#282)

新增共享 time.ts 时间工具,统一 TaskModal 中调度时间、流转日志、活动时间的本地时区渲染,消除 UTC/本地混显问题
This commit is contained in:
狼哥
2026-04-20 00:17:10 +08:00
committed by GitHub
parent c958d06cb4
commit af234d93d7
2 changed files with 63 additions and 11 deletions

View File

@@ -1,6 +1,7 @@
import { useEffect, useState, useRef, useCallback } from 'react';
import { useStore, getPipeStatus, deptColor, stateLabel, STATE_LABEL } from '../store';
import { api } from '../api';
import { formatDashboardDateTime, formatDashboardTime } from '../time';
import type {
Task,
TaskActivityData,
@@ -43,13 +44,7 @@ function fmtStalled(sec: number): string {
}
function fmtActivityTime(ts: number | string | undefined): string {
if (!ts) return '';
if (typeof ts === 'number') {
const d = new Date(ts);
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`;
}
if (typeof ts === 'string' && ts.length >= 19) return ts.substring(11, 19);
return String(ts).substring(0, 8);
return formatDashboardTime(ts, { showSeconds: true });
}
export default function TaskModal() {
@@ -302,8 +297,8 @@ export default function TaskModal() {
</div>
{sched && (
<div className="sched-line">
{sched.lastProgressAt && <span> {(sched.lastProgressAt || '').replace('T', ' ').substring(0, 19)}</span>}
{sched.lastDispatchAt && <span> {(sched.lastDispatchAt || '').replace('T', ' ').substring(0, 19)}</span>}
{sched.lastProgressAt && <span> {formatDashboardDateTime(sched.lastProgressAt)}</span>}
{sched.lastDispatchAt && <span> {formatDashboardDateTime(sched.lastDispatchAt)}</span>}
<span> {sched.autoRollback === false ? '关闭' : '开启'}</span>
{sched.lastDispatchAgent && <span> {sched.lastDispatchAgent}</span>}
</div>
@@ -365,7 +360,7 @@ export default function TaskModal() {
const col = deptColor(fl.from || '');
return (
<div className="fl-item" key={i}>
<div className="fl-time">{fl.at ? fl.at.substring(11, 16) : ''}</div>
<div className="fl-time">{formatDashboardTime(fl.at, { showSeconds: false })}</div>
<div className="fl-dot" style={{ background: col }} />
<div className="fl-content">
<div className="fl-who">
@@ -458,7 +453,7 @@ function LiveActivitySection({
const agentParts: string[] = [];
if (data.agentLabel) agentParts.push(data.agentLabel);
if (data.relatedAgents && data.relatedAgents.length > 1) agentParts.push(`${data.relatedAgents.length}个 Agent`);
if (data.lastActive) agentParts.push(`最后活跃: ${data.lastActive}`);
if (data.lastActive) agentParts.push(`最后活跃: ${formatDashboardDateTime(data.lastActive)}`);
// Phase durations
const phaseDurations = data.phaseDurations || [];

View File

@@ -0,0 +1,57 @@
function pad2(value: number): string {
return String(value).padStart(2, '0');
}
export function parseDashboardTimestamp(value: number | string | undefined | null): Date | null {
if (value === undefined || value === null || value === '') return null;
if (typeof value === 'number') {
const ms = Math.abs(value) < 1e12 ? value * 1000 : value;
const d = new Date(ms);
return Number.isNaN(d.getTime()) ? null : d;
}
const raw = String(value).trim();
if (!raw) return null;
if (/^\d+(\.\d+)?$/.test(raw)) {
return parseDashboardTimestamp(Number(raw));
}
let normalized = raw;
if (normalized.includes(' ') && !normalized.includes('T')) {
normalized = normalized.replace(' ', 'T');
}
const looksIso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(normalized);
const hasTimezone = /(?:Z|[+\-]\d{2}:\d{2})$/i.test(normalized);
if (looksIso && !hasTimezone) {
normalized += 'Z';
}
const d = new Date(normalized);
return Number.isNaN(d.getTime()) ? null : d;
}
export function formatDashboardTime(
value: number | string | undefined | null,
{ showSeconds = true }: { showSeconds?: boolean } = {}
): string {
const d = parseDashboardTimestamp(value);
if (!d) return '';
const hh = pad2(d.getHours());
const mm = pad2(d.getMinutes());
if (!showSeconds) return `${hh}:${mm}`;
return `${hh}:${mm}:${pad2(d.getSeconds())}`;
}
export function formatDashboardDateTime(
value: number | string | undefined | null,
{ showSeconds = true }: { showSeconds?: boolean } = {}
): string {
const d = parseDashboardTimestamp(value);
if (!d) return '';
const date = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
const time = formatDashboardTime(d.getTime(), { showSeconds });
return `${date} ${time}`;
}