fix(platform): harden queries and batch vehicle search
This commit is contained in:
@@ -6,6 +6,7 @@ import { api } from '../../api/client';
|
||||
import type { DailyMileageRow, MileageStatistics, VehicleRow } from '../../api/types';
|
||||
import { downloadMileageWorkbook } from '../domain/mileageExport';
|
||||
import { InlineError } from '../shared/AsyncState';
|
||||
import { QUERY_MEMORY } from '../queryPolicy';
|
||||
|
||||
const DAY = 86_400_000;
|
||||
const DETAIL_LIMIT = 10_000;
|
||||
@@ -134,9 +135,10 @@ function VehicleMultiSelect({ value, onChange }: { value: VehicleOption[]; onCha
|
||||
}, [debounced]);
|
||||
const candidates = useQuery({
|
||||
queryKey: ['mileage-vehicle-options', candidateParams.toString()],
|
||||
queryFn: () => api.vehicles(candidateParams),
|
||||
queryFn: ({ signal }) => api.vehicles(candidateParams, signal),
|
||||
enabled: open,
|
||||
staleTime: 60_000
|
||||
staleTime: 30_000,
|
||||
gcTime: QUERY_MEMORY.optionGcTime
|
||||
});
|
||||
const selected = useMemo(() => new Set(value.map((vehicle) => vehicle.vin)), [value]);
|
||||
const options = (candidates.data?.items ?? []).filter((vehicle, index, rows) => rows.findIndex((item) => item.vin === vehicle.vin) === index);
|
||||
@@ -174,7 +176,7 @@ function SummaryRail({ data, criteria, fleetTotal }: { data?: MileageStatistics;
|
||||
const days = inclusiveDays(criteria.dateFrom, criteria.dateTo);
|
||||
const vehicleCount = criteria.vehicles.length ? data?.vehicleCount ?? 0 : fleetTotal ?? 0;
|
||||
const items = [
|
||||
['查询车辆', `${vehicleCount} 辆`, criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `全部车辆 · ${data?.vehicleCount ?? 0} 辆有里程`],
|
||||
['已绑定主车辆', `${vehicleCount} 辆`, criteria.vehicles.length ? `已选择 ${criteria.vehicles.length} 辆` : `档案口径 · ${data?.vehicleCount ?? 0} 辆有里程`],
|
||||
['统计天数', `${days} 天`, `${criteria.dateFrom} 至 ${criteria.dateTo}`],
|
||||
['区间总里程', `${formatKm(data?.periodMileageKm)} km`, `${data?.recordCount ?? 0} 条车辆日记录`],
|
||||
['日均里程', `${formatKm(data?.averageDailyMileageKm)} km`, '按有效车辆日平均']
|
||||
@@ -225,9 +227,10 @@ export default function StatisticsPage() {
|
||||
const fleetParams = useMemo(() => new URLSearchParams({ limit: String(PAGE_SIZE), offset: String((page - 1) * PAGE_SIZE), bindingStatus: 'bound' }), [page]);
|
||||
const fleetVehicles = useQuery({
|
||||
queryKey: ['mileage-fleet-page', fleetParams.toString()],
|
||||
queryFn: () => api.vehicleCoverage(fleetParams),
|
||||
queryFn: ({ signal }) => api.vehicleCoverage(fleetParams, signal),
|
||||
enabled: !hasVehicles,
|
||||
staleTime: 60_000
|
||||
staleTime: 60_000,
|
||||
gcTime: QUERY_MEMORY.summaryGcTime
|
||||
});
|
||||
const displayVehicles = useMemo<VehicleOption[]>(() => hasVehicles
|
||||
? criteria.vehicles
|
||||
@@ -235,8 +238,8 @@ export default function StatisticsPage() {
|
||||
const statisticsParams = useMemo(() => mileageParams(criteria, -1), [criteria]);
|
||||
const rowsCriteria = useMemo(() => ({ ...criteria, vehicles: displayVehicles }), [criteria, displayVehicles]);
|
||||
const rowsParams = useMemo(() => mileageParams(rowsCriteria, 0), [rowsCriteria]);
|
||||
const statistics = useQuery({ queryKey: ['mileage-statistics', statisticsParams.toString()], queryFn: () => api.mileageStatistics(statisticsParams), staleTime: 60_000, placeholderData: (previous) => previous });
|
||||
const mileage = useQuery({ queryKey: ['daily-mileage-query', rowsParams.toString()], queryFn: () => api.dailyMileage(rowsParams), enabled: displayVehicles.length > 0, staleTime: 60_000, placeholderData: (previous) => previous });
|
||||
const statistics = useQuery({ queryKey: ['mileage-statistics', statisticsParams.toString()], queryFn: ({ signal }) => api.mileageStatistics(statisticsParams, signal), staleTime: 60_000, gcTime: QUERY_MEMORY.summaryGcTime, placeholderData: (previous) => previous });
|
||||
const mileage = useQuery({ queryKey: ['daily-mileage-query', rowsParams.toString()], queryFn: ({ signal }) => api.dailyMileage(rowsParams, signal), enabled: displayVehicles.length > 0, staleTime: 60_000, gcTime: QUERY_MEMORY.highVolumeGcTime, placeholderData: (previous) => previous });
|
||||
const totals = useMemo(() => new Map((statistics.data?.ranking ?? []).map((row) => [row.vin, row.mileageKm])), [statistics.data?.ranking]);
|
||||
const dates = useMemo(() => rangeDates(criteria.dateFrom, criteria.dateTo), [criteria.dateFrom, criteria.dateTo]);
|
||||
const matrixRows = useMemo(() => displayVehicles.map((vehicle) => {
|
||||
@@ -304,7 +307,7 @@ export default function StatisticsPage() {
|
||||
};
|
||||
|
||||
return <div className="v2-mileage-page">
|
||||
<header className="v2-mileage-heading"><div><h2>里程查询</h2><p>未选择车牌时分页展示全部车辆;选择车牌后查询指定车辆。</p></div><button type="button" onClick={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} disabled={refreshing}><IconRefresh />{refreshing ? '更新中' : '刷新数据'}</button></header>
|
||||
<header className="v2-mileage-heading"><div><h2>里程查询</h2><p>未选择车牌时分页展示全部已绑定主车辆;选择车牌后查询指定车辆。</p></div><button type="button" onClick={() => { statistics.refetch(); mileage.refetch(); if (!hasVehicles) fleetVehicles.refetch(); }} disabled={refreshing}><IconRefresh />{refreshing ? '更新中' : '刷新数据'}</button></header>
|
||||
<section className="v2-mileage-query-panel">
|
||||
<form className="v2-mileage-filter" onSubmit={submit}>
|
||||
<VehicleMultiSelect value={draft.vehicles} onChange={(vehicles) => setDraft((current) => ({ ...current, vehicles }))} />
|
||||
@@ -323,6 +326,6 @@ export default function StatisticsPage() {
|
||||
{!fleetVehicles.isLoading && !displayVehicles.length ? <div className="v2-mileage-empty">当前没有可展示的车辆</div> : null}
|
||||
<footer><span>{hasVehicles ? `已选择 ${totalVehicles} 辆车辆` : `第 ${page} / ${totalPages} 页 · 共 ${totalVehicles} 辆 · 每页 ${PAGE_SIZE} 辆`}{exportFeedback ? ` · ${exportFeedback}` : ''}</span>{!hasVehicles && totalVehicles ? <div><button type="button" disabled={page <= 1 || fleetVehicles.isFetching} onClick={() => setPage((current) => Math.max(1, current - 1))}>上一页</button><button type="button" disabled={page >= totalPages || fleetVehicles.isFetching} onClick={() => setPage((current) => Math.min(totalPages, current + 1))}>下一页</button></div> : null}</footer>
|
||||
</section>
|
||||
<footer className="v2-mileage-evidence"><span>数据更新时间:{statistics.data?.asOf || '—'}</span><span>来源优先级:{criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(' > ')}</span><span>查询结果缓存 1 分钟</span></footer>
|
||||
<footer className="v2-mileage-evidence"><span>数据更新时间:{statistics.data?.asOf || '—'}</span><span>来源优先级:{criteria.sources.filter((source) => source.enabled).map((source) => source.protocol).join(' > ')}</span><span>当前筛选复用 1 分钟 · 离开后释放明细</span></footer>
|
||||
</div>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user