fix: decouple mileage KPIs from sorting

This commit is contained in:
kkfluous
2026-08-07 18:44:49 +08:00
parent c0d504cb7b
commit 559094cdc5
7 changed files with 123 additions and 18 deletions
+22 -14
View File
@@ -29,6 +29,7 @@ import {
parseMileageMonitoringContext,
type MileageMonitoringContext,
} from './monitoring-context';
import { buildMonitoringKpis } from './monitoring-kpis';
const HIGH_MILEAGE_ALERT_TARGETS = new Set([
'交投40辆4.5T普货',
@@ -473,6 +474,7 @@ export default function MonitoringView() {
const [fullscreenError, setFullscreenError] = useState<string | null>(null);
const [fullscreenSnapshotKey, setFullscreenSnapshotKey] = useState<string | null>(null);
const [fullscreenUpdatedAt, setFullscreenUpdatedAt] = useState<string | null>(null);
const [fullscreenDateRange, setFullscreenDateRange] = useState<{ start: string; end: string } | undefined>();
// New filters from image
const [filterPlates, setFilterPlates] = useState<string[]>(initialMonitoringContext.plates);
@@ -704,6 +706,11 @@ export default function MonitoringView() {
const averageDailyKm = pageHasSnapshot && rangeDailyTotals.length > 0
? rangeDailyTotals.reduce((sum, item) => sum + item.totalKm, 0) / rangeDailyTotals.length
: 0;
const pageKpis = buildMonitoringKpis(stats, displayedRange);
const fullscreenKpis = buildMonitoringKpis(
fullscreenStats,
fullscreenHasSnapshot ? fullscreenDateRange : { start: rangeStart, end: rangeEnd },
);
// 加载首页数据
const loadFirstPage = useCallback((showPageLoading = true, force = false) => {
@@ -871,6 +878,7 @@ export default function MonitoringView() {
setFullscreenStats(d.stats);
setFilterOptions(d.filters);
setFullscreenUpdatedAt(d.updatedAt);
setFullscreenDateRange(d.dateRange || { start: rangeStart, end: rangeEnd });
setFullscreenSnapshotKey(monitoringRequestKey);
}).catch(error => {
if (cancelled) return;
@@ -879,7 +887,7 @@ export default function MonitoringView() {
if (!cancelled) setFullscreenLoading(false);
});
return () => { cancelled = true; };
}, [fullscreenRefresh, isFullscreen, monitoringFilters, monitoringRequestKey, sortBy, sortOrder]);
}, [fullscreenRefresh, isFullscreen, monitoringFilters, monitoringRequestKey, rangeEnd, rangeStart, sortBy, sortOrder]);
// 全屏时禁止背景滚动
useEffect(() => {
@@ -943,13 +951,13 @@ export default function MonitoringView() {
<h2 className="text-white font-bold text-xs"></h2>
</div>
<div className="flex items-center gap-3 text-[10px]">
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenStats.totalToday).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-500">{fullscreenKpis.distanceShortLabel} <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenKpis.distanceKm).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-700">|</span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenStats.totalAll).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? Math.round(fullscreenKpis.cumulativeOdometerKm).toLocaleString() : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-700">|</span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? fullscreenStats.vehicleCount : '—'}</span> </span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? fullscreenKpis.vehicleCount : '—'}</span> </span>
<span className="text-slate-700">|</span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? (fullscreenStats.vehicleCount > 0 ? (sortBy === 'total' ? fullscreenStats.totalAll : fullscreenStats.totalToday) / fullscreenStats.vehicleCount : 0).toFixed(0) : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-500"> <span className="text-white font-black">{fullscreenHasSnapshot ? fullscreenKpis.averagePerVehicleKm.toFixed(0) : '—'}</span> <span className="text-blue-400">km</span></span>
<span className="text-slate-700">|</span>
<span className="text-slate-500">
<span className="text-white font-black">
@@ -1275,9 +1283,9 @@ export default function MonitoringView() {
<span>{manualRefreshing ? '刷新中' : '刷新数据'}</span>
</button>
<div className="flex items-center gap-1 rounded-lg bg-slate-100 p-0.5">
<button onClick={() => setSortBy('today')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'today' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button onClick={() => setSortBy('total')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'total' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button onClick={() => setSortBy('statisticTime')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'statisticTime' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button aria-pressed={sortBy === 'today'} title="按所选日期区间里程排序" onClick={() => setSortBy('today')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'today' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button aria-pressed={sortBy === 'total'} title="按最新累计仪表里程排序" onClick={() => setSortBy('total')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'total' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button aria-pressed={sortBy === 'statisticTime'} title="按最后有效数据时间排序" onClick={() => setSortBy('statisticTime')} className={`rounded-md px-2 py-1 text-[9px] font-bold transition-all ${sortBy === 'statisticTime' ? 'bg-white text-blue-600 shadow-sm' : 'text-slate-400'}`}></button>
<button
onClick={() => setSortOrder(sortOrder === 'desc' ? 'asc' : 'desc')}
className="rounded-md p-1 text-blue-600 transition-all hover:bg-white"
@@ -1650,11 +1658,11 @@ export default function MonitoringView() {
<div className="sticky top-[44px] z-20 bg-[var(--app-bg)] pt-1 pb-1 space-y-2">
<div className={`grid grid-cols-4 gap-2 transition-opacity ${pageLoading && pageHasSnapshot ? 'opacity-60' : ''}`}>
<div className="relative col-span-2 flex min-h-[68px] flex-col justify-center overflow-hidden rounded-xl bg-slate-900 p-2.5 text-white">
<div className="text-[7px] font-bold text-slate-500 uppercase tracking-wider">{sortBy === 'total' ? '累计' : (isRangeMode ? '区间' : '当日')}</div>
<div className="text-[7px] font-bold text-slate-500 uppercase tracking-wider">{pageKpis.distanceLabel}</div>
<div className="text-lg font-black tracking-tighter leading-tight flex items-baseline gap-1">
{pageLoading && !pageHasSnapshot
? <div className="h-5 w-20 bg-slate-700 rounded animate-pulse"></div>
: <>{pageHasSnapshot ? Math.round(sortBy === 'total' ? stats.totalAll : stats.totalToday).toLocaleString() : '—'} <span className="text-[8px] text-slate-400">km</span></>}
: <>{pageHasSnapshot ? Math.round(pageKpis.distanceKm).toLocaleString() : '—'} <span className="text-[8px] text-slate-400">km</span></>}
</div>
<div className="mt-0.5 truncate text-[8px] font-bold text-slate-500">{rangeLabel}</div>
</div>
@@ -1663,7 +1671,7 @@ export default function MonitoringView() {
<div className="text-sm font-black text-slate-800 leading-tight">
{pageLoading && !pageHasSnapshot
? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div>
: pageHasSnapshot ? (stats.vehicleCount > 0 ? (sortBy === 'total' ? stats.totalAll : stats.totalToday) / stats.vehicleCount : 0).toFixed(0) : '—'}
: pageHasSnapshot ? pageKpis.averagePerVehicleKm.toFixed(0) : '—'}
</div>
<div className="text-[7px] text-slate-400">km/</div>
</div>
@@ -1672,7 +1680,7 @@ export default function MonitoringView() {
<div className="text-sm font-black text-slate-800 leading-tight">
{pageLoading && !pageHasSnapshot
? <div className="h-4 w-8 bg-slate-100 rounded animate-pulse"></div>
: pageHasSnapshot ? stats.vehicleCount : '—'}
: pageHasSnapshot ? pageKpis.vehicleCount : '—'}
</div>
<div className="text-[7px] text-slate-400"></div>
</div>
@@ -1729,11 +1737,11 @@ export default function MonitoringView() {
</div>
<div className="flex flex-col justify-center rounded-lg bg-blue-50 px-2 py-1.5 text-right">
<div className="text-[9px] font-black text-blue-400"></div>
<div className="mt-1 text-xs font-black tabular-nums text-blue-700">{Math.round(stats.totalToday).toLocaleString()}</div>
<div className="mt-1 text-xs font-black tabular-nums text-blue-700">{Math.round(pageKpis.distanceKm).toLocaleString()}</div>
</div>
<div className="flex flex-col justify-center rounded-lg bg-slate-50 px-2 py-1.5 text-right">
<div className="text-[9px] font-black text-slate-400"></div>
<div className="mt-1 text-xs font-black tabular-nums text-slate-800">{stats.vehicleCount > 0 ? Math.round(stats.totalToday / stats.vehicleCount).toLocaleString() : 0}</div>
<div className="mt-1 text-xs font-black tabular-nums text-slate-800">{Math.round(pageKpis.averagePerVehicleKm).toLocaleString()}</div>
</div>
</div>
)}
@@ -0,0 +1,47 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { buildMonitoringKpis } from './monitoring-kpis.js';
test('builds daily monitoring KPIs from the selected-day flow metric', () => {
const model = buildMonitoringKpis(
{ totalToday: 120, totalAll: 3_000, vehicleCount: 3, yesterdayTotal: 100 },
{ start: '2026-08-07', end: '2026-08-07' },
);
assert.deepEqual(model, {
distanceMetricId: 'mileage.daily_total',
distanceLabel: '当日总里程',
distanceShortLabel: '当日',
distanceKm: 120,
averagePerVehicleKm: 40,
vehicleCount: 3,
cumulativeOdometerKm: 3_000,
});
});
test('builds range KPIs without substituting cumulative odometer values', () => {
const model = buildMonitoringKpis(
{ totalToday: 900, totalAll: 30_000, vehicleCount: 6, yesterdayTotal: 0 },
{ start: '2026-08-01', end: '2026-08-07' },
);
assert.equal(model.distanceMetricId, 'mileage.period_total');
assert.equal(model.distanceLabel, '区间总里程');
assert.equal(model.distanceKm, 900);
assert.equal(model.averagePerVehicleKm, 150);
assert.equal(model.cumulativeOdometerKm, 30_000);
});
test('normalizes invalid monitoring KPI inputs', () => {
const model = buildMonitoringKpis({
totalToday: Number.NaN,
totalAll: -10,
vehicleCount: Number.POSITIVE_INFINITY,
yesterdayTotal: 0,
});
assert.equal(model.distanceKm, 0);
assert.equal(model.averagePerVehicleKm, 0);
assert.equal(model.vehicleCount, 0);
assert.equal(model.cumulativeOdometerKm, 0);
});
+31
View File
@@ -0,0 +1,31 @@
import type { MonitoringStats } from './types';
export interface MonitoringKpiModel {
distanceMetricId: 'mileage.daily_total' | 'mileage.period_total';
distanceLabel: '当日总里程' | '区间总里程';
distanceShortLabel: '当日' | '区间';
distanceKm: number;
averagePerVehicleKm: number;
vehicleCount: number;
cumulativeOdometerKm: number;
}
export function buildMonitoringKpis(
stats: MonitoringStats,
dateRange?: { start: string; end: string },
): MonitoringKpiModel {
const isRange = !!dateRange?.start && !!dateRange?.end && dateRange.start !== dateRange.end;
const vehicleCount = Number.isFinite(stats.vehicleCount) ? Math.max(0, stats.vehicleCount) : 0;
const distanceKm = Number.isFinite(stats.totalToday) ? Math.max(0, stats.totalToday) : 0;
const cumulativeOdometerKm = Number.isFinite(stats.totalAll) ? Math.max(0, stats.totalAll) : 0;
return {
distanceMetricId: isRange ? 'mileage.period_total' : 'mileage.daily_total',
distanceLabel: isRange ? '区间总里程' : '当日总里程',
distanceShortLabel: isRange ? '区间' : '当日',
distanceKm,
averagePerVehicleKm: vehicleCount > 0 ? distanceKm / vehicleCount : 0,
vehicleCount,
cumulativeOdometerKm,
};
}