refactor: show mileage data freshness
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
||||
parseMileageDrillContext,
|
||||
type MileageDrillContext,
|
||||
} from './drill-context';
|
||||
import { formatMileageSnapshotTime } from './data-freshness';
|
||||
|
||||
const HIGH_MILEAGE_ALERT_TARGETS = new Set([
|
||||
'交投40辆4.5T普货',
|
||||
@@ -494,7 +495,7 @@ export default function MonitoringView() {
|
||||
const [pageLoading, setPageLoading] = useState(true);
|
||||
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
||||
const [manualRefreshing, setManualRefreshing] = useState(false);
|
||||
const [lastRefreshedAt, setLastRefreshedAt] = useState<Date | null>(null);
|
||||
const [dataUpdatedAt, setDataUpdatedAt] = useState<string | null>(null);
|
||||
const [showBackToTop, setShowBackToTop] = useState(false);
|
||||
const [relativeNow, setRelativeNow] = useState(() => Date.now());
|
||||
const PAGE_SIZE = 50;
|
||||
@@ -599,7 +600,7 @@ export default function MonitoringView() {
|
||||
setTotal(d.total);
|
||||
setPage(1);
|
||||
setHasMore(d.page < d.totalPages);
|
||||
setLastRefreshedAt(new Date());
|
||||
setDataUpdatedAt(d.updatedAt);
|
||||
}).catch(error => {
|
||||
setPageError({
|
||||
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
||||
@@ -651,6 +652,7 @@ export default function MonitoringView() {
|
||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||
setPage(nextPage);
|
||||
setHasMore(nextPage < d.totalPages);
|
||||
setDataUpdatedAt(d.updatedAt);
|
||||
}).catch(error => {
|
||||
setPageError({
|
||||
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
||||
@@ -762,6 +764,7 @@ export default function MonitoringView() {
|
||||
setFullscreenVehicles(d.vehicles);
|
||||
setFullscreenStats(d.stats);
|
||||
setFilterOptions(d.filters);
|
||||
setDataUpdatedAt(d.updatedAt);
|
||||
}).catch(() => {}).finally(() => setFullscreenLoading(false));
|
||||
}, [isFullscreen, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, rangeStart, rangeEnd, fullscreenRefresh, filterBrands, sourcePriority]);
|
||||
|
||||
@@ -1078,14 +1081,19 @@ export default function MonitoringView() {
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-1.5">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-blue-500" />
|
||||
<span className="text-[9px] font-bold uppercase tracking-tight text-slate-400">数据监控 • OneOS 实时查询</span>
|
||||
<span
|
||||
className="truncate text-[9px] font-bold tracking-tight text-slate-400"
|
||||
title={`OneOS 分钟级快照 · 数据截至 ${formatMileageSnapshotTime(dataUpdatedAt)}`}
|
||||
>
|
||||
OneOS 分钟级快照 · 截至 {formatMileageSnapshotTime(dataUpdatedAt)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-shrink-0 items-center gap-1.5">
|
||||
<span className="hidden text-[9px] font-bold tabular-nums text-slate-400 sm:inline">
|
||||
{lastRefreshedAt ? lastRefreshedAt.toLocaleTimeString('zh-CN', { hour12: false }) : '--:--:--'}
|
||||
数据截至 {formatMileageSnapshotTime(dataUpdatedAt)}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { formatMileageSnapshotTime } from './data-freshness.js';
|
||||
|
||||
test('formats snapshot instants in Asia/Shanghai', () => {
|
||||
assert.equal(formatMileageSnapshotTime('2026-08-07T07:45:06.000Z'), '15:45:06');
|
||||
});
|
||||
|
||||
test('keeps historical date snapshots explicit', () => {
|
||||
assert.equal(formatMileageSnapshotTime('2026-08-06'), '2026-08-06');
|
||||
assert.equal(formatMileageSnapshotTime(null), '--:--:--');
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
export function formatMileageSnapshotTime(value: string | null): string {
|
||||
if (!value) return '--:--:--';
|
||||
if (/^\d{4}-\d{2}-\d{2}$/.test(value)) return value;
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) return value;
|
||||
return new Intl.DateTimeFormat('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
hourCycle: 'h23',
|
||||
}).format(date);
|
||||
}
|
||||
Reference in New Issue
Block a user