refactor: show mileage data freshness
This commit is contained in:
@@ -23,6 +23,7 @@ import {
|
|||||||
parseMileageDrillContext,
|
parseMileageDrillContext,
|
||||||
type MileageDrillContext,
|
type MileageDrillContext,
|
||||||
} from './drill-context';
|
} from './drill-context';
|
||||||
|
import { formatMileageSnapshotTime } from './data-freshness';
|
||||||
|
|
||||||
const HIGH_MILEAGE_ALERT_TARGETS = new Set([
|
const HIGH_MILEAGE_ALERT_TARGETS = new Set([
|
||||||
'交投40辆4.5T普货',
|
'交投40辆4.5T普货',
|
||||||
@@ -494,7 +495,7 @@ export default function MonitoringView() {
|
|||||||
const [pageLoading, setPageLoading] = useState(true);
|
const [pageLoading, setPageLoading] = useState(true);
|
||||||
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
const [pageError, setPageError] = useState<{ message: string; scope: 'first' | 'more' } | null>(null);
|
||||||
const [manualRefreshing, setManualRefreshing] = useState(false);
|
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 [showBackToTop, setShowBackToTop] = useState(false);
|
||||||
const [relativeNow, setRelativeNow] = useState(() => Date.now());
|
const [relativeNow, setRelativeNow] = useState(() => Date.now());
|
||||||
const PAGE_SIZE = 50;
|
const PAGE_SIZE = 50;
|
||||||
@@ -599,7 +600,7 @@ export default function MonitoringView() {
|
|||||||
setTotal(d.total);
|
setTotal(d.total);
|
||||||
setPage(1);
|
setPage(1);
|
||||||
setHasMore(d.page < d.totalPages);
|
setHasMore(d.page < d.totalPages);
|
||||||
setLastRefreshedAt(new Date());
|
setDataUpdatedAt(d.updatedAt);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
setPageError({
|
setPageError({
|
||||||
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
message: error instanceof Error ? error.message : '里程数据加载失败,请稍后重试',
|
||||||
@@ -651,6 +652,7 @@ export default function MonitoringView() {
|
|||||||
setVehicles(prev => [...prev, ...d.vehicles]);
|
setVehicles(prev => [...prev, ...d.vehicles]);
|
||||||
setPage(nextPage);
|
setPage(nextPage);
|
||||||
setHasMore(nextPage < d.totalPages);
|
setHasMore(nextPage < d.totalPages);
|
||||||
|
setDataUpdatedAt(d.updatedAt);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
setPageError({
|
setPageError({
|
||||||
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
message: error instanceof Error ? error.message : '更多车辆加载失败,请稍后重试',
|
||||||
@@ -762,6 +764,7 @@ export default function MonitoringView() {
|
|||||||
setFullscreenVehicles(d.vehicles);
|
setFullscreenVehicles(d.vehicles);
|
||||||
setFullscreenStats(d.stats);
|
setFullscreenStats(d.stats);
|
||||||
setFilterOptions(d.filters);
|
setFilterOptions(d.filters);
|
||||||
|
setDataUpdatedAt(d.updatedAt);
|
||||||
}).catch(() => {}).finally(() => setFullscreenLoading(false));
|
}).catch(() => {}).finally(() => setFullscreenLoading(false));
|
||||||
}, [isFullscreen, sortBy, sortOrder, searchTerm, filterDept, filterCustomer, filterRentStatus, filterPlatePrefix, filterTargetNames, filterRegion, filterPlates, rangeStart, rangeEnd, fullscreenRefresh, filterBrands, sourcePriority]);
|
}, [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>
|
||||||
<div className="mt-1 flex items-center gap-1.5">
|
<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="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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-shrink-0 items-center gap-1.5">
|
<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">
|
<span className="hidden text-[9px] font-bold tabular-nums text-slate-400 sm:inline">
|
||||||
{lastRefreshedAt ? lastRefreshedAt.toLocaleTimeString('zh-CN', { hour12: false }) : '--:--:--'}
|
数据截至 {formatMileageSnapshotTime(dataUpdatedAt)}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
type="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