fix(platform): harden queries and batch vehicle search

This commit is contained in:
lingniu
2026-07-16 00:14:16 +08:00
parent c29ccdf2da
commit 53e1b57e86
19 changed files with 193 additions and 53 deletions

View File

@@ -12,6 +12,7 @@ import { MAX_MONITOR_SEARCH_TERMS, MONITOR_REFRESH, monitorQueryParams, parseMon
const protocols = ['', 'GB32960', 'JT808', 'YUTONG_MQTT'];
const statuses = ['', 'online', 'offline', 'driving', 'idle'];
const EMPTY_VEHICLES: VehicleRealtimeRow[] = [];
type AddressCoordinate = { longitude: number; latitude: number; key: string };
@@ -194,6 +195,30 @@ export default function MonitorPage() {
if (status === 'driving' || status === 'idle') return data.filter((vehicle) => vehicleStatus(vehicle) === status);
return data;
}, [status, vehicles.data?.items]);
const listRows = realtimeListQuery.data?.items ?? EMPTY_VEHICLES;
const activeBatchRows = mode === 'map' ? rows : listRows;
const activeBatchQuery = mode === 'map' ? vehicles : realtimeListQuery;
const batchSearchPending = searchTerms.length > 1 && (
keyword !== deferredKeyword || activeBatchQuery.isLoading || activeBatchQuery.isPlaceholderData
);
const batchMatch = useMemo(() => {
const identities = new Set<string>();
for (const vehicle of activeBatchRows) {
identities.add(vehicle.vin.toLocaleUpperCase());
if (vehicle.plate) identities.add(vehicle.plate.toLocaleUpperCase());
}
const missing = searchTerms.filter((term) => !identities.has(term));
return { matched: searchTerms.length - missing.length, missing };
}, [activeBatchRows, searchTerms]);
const visibleRows = batchSearchPending ? [] : rows;
const batchStatus = batchSearchPending
? `正在查找 ${searchTerms.length}`
: batchMatch.missing.length
? `已找到 ${batchMatch.matched}/${searchTerms.length} · 未找到 ${batchMatch.missing.length}`
: `已找到 ${batchMatch.matched}/${searchTerms.length}`;
const batchStatusTitle = batchMatch.missing.length
? `未找到:${batchMatch.missing.join('、')}`
: searchTerms.join('、');
const selected = selectedVin
? rows.find((vehicle) => vehicle.vin === selectedVin) ?? selectedVehicle.data?.items[0]
: undefined;
@@ -232,7 +257,7 @@ export default function MonitorPage() {
}}
placeholder="车牌 / VIN可批量粘贴车牌"
/>
{searchTerms.length > 1 ? <span className="v2-search-batch-count" aria-live="polite" title={searchTerms.length === MAX_MONITOR_SEARCH_TERMS ? `最多支持 ${MAX_MONITOR_SEARCH_TERMS} 条;${searchTerms.join('、')}` : searchTerms.join('、')}> {searchTerms.length} </span> : null}
{searchTerms.length > 1 ? <span className={`v2-search-batch-count${batchMatch.missing.length && !batchSearchPending ? ' has-missing' : ''}`} aria-live="polite" title={searchTerms.length === MAX_MONITOR_SEARCH_TERMS ? `最多支持 ${MAX_MONITOR_SEARCH_TERMS} 条;${batchStatusTitle}` : batchStatusTitle}>{batchSearchPending ? `已识别 ${searchTerms.length}` : `已找到 ${batchMatch.matched}/${searchTerms.length}`}</span> : null}
</label>
<select value={protocol} onChange={(event) => { setProtocol(event.target.value); setListOffset(0); }} aria-label="协议">
{protocols.map((item) => <option key={item} value={item}>{item || '全部协议'}</option>)}
@@ -248,7 +273,7 @@ export default function MonitorPage() {
<section className="v2-kpis" aria-label="车辆整体统计">
{[
['接入车辆', formatNumber(summary.data?.totalVehicles ?? vehicles.data?.total ?? rows.length), 'fleet'],
['有实时位置', formatNumber(summary.data?.totalVehicles ?? vehicles.data?.total ?? rows.length), 'fleet'],
['当前在线', formatNumber(summary.data?.onlineVehicles ?? rows.length - offline), 'online'],
['当前离线', formatNumber(summary.data?.offlineVehicles ?? offline), 'offline'],
['行驶车辆', formatNumber(summary.data?.drivingVehicles ?? driving), 'driving'],
@@ -262,18 +287,18 @@ export default function MonitorPage() {
{mode === 'list' && realtimeListQuery.isError ? <InlineError message={realtimeListQuery.error instanceof Error ? realtimeListQuery.error.message : '车辆列表加载失败'} onRetry={() => realtimeListQuery.refetch()} /> : null}
{mode === 'map' ? <section className={`v2-monitor-workspace${selected && detailOpen ? ' is-detail-open' : ''}${selected && !detailOpen ? ' is-detail-collapsed' : ''}`}>
<div className="v2-vehicle-rail">
<header><strong></strong><span>{formatNumber(vehicles.data?.total ?? rows.length)} </span></header>
<div className="v2-rail-search"><IconSearch /><span>{searchTerms.length > 1 ? `正在批量筛选 ${searchTerms.length}` : deferredKeyword ? `正在筛选“${deferredKeyword}` : '按最新上报排序'}</span></div>
<header><strong></strong><span>{batchSearchPending ? '查询中' : `${formatNumber(vehicles.data?.total ?? visibleRows.length)}`}</span></header>
<div className={`v2-rail-search${batchMatch.missing.length && !batchSearchPending ? ' has-missing' : ''}`} title={searchTerms.length > 1 ? batchStatusTitle : undefined}><IconSearch /><span>{searchTerms.length > 1 ? batchStatus : deferredKeyword ? `正在筛选“${deferredKeyword}` : '按最新上报排序'}</span></div>
<div className="v2-vehicle-scroll">
{vehicles.isLoading ? <div className="v2-list-loading"><span className="v2-spinner" /></div> : null}
{!vehicles.isLoading && rows.length === 0 ? <EmptyState /> : null}
{rows.map((vehicle) => <VehicleRow key={vehicle.vin} vehicle={vehicle} selected={vehicle.vin === selectedVin} onSelect={selectVehicle} />)}
{vehicles.isLoading || batchSearchPending ? <div className="v2-list-loading"><span className="v2-spinner" />{batchSearchPending ? `正在查找 ${searchTerms.length} 辆车` : '加载车辆'}</div> : null}
{!vehicles.isLoading && !batchSearchPending && visibleRows.length === 0 ? <EmptyState /> : null}
{visibleRows.map((vehicle) => <VehicleRow key={vehicle.vin} vehicle={vehicle} selected={vehicle.vin === selectedVin} onSelect={selectVehicle} />)}
</div>
<footer> {rows.length} / {vehicles.data?.total ?? rows.length} </footer>
<footer>{searchTerms.length > 1 && !batchSearchPending ? batchStatus : `当前载入 ${visibleRows.length} / ${vehicles.data?.total ?? visibleRows.length}`}</footer>
</div>
<MemoFleetMap
vehicles={rows}
monitorMap={map.data}
vehicles={visibleRows}
monitorMap={searchTerms.length > 1 && (batchSearchPending || map.isPlaceholderData) ? undefined : map.data}
selectedVin={selectedVin || undefined}
onSelect={selectMapVehicle}
onSelectVin={selectVehicle}
@@ -303,7 +328,7 @@ export default function MonitorPage() {
<section className="v2-event-strip">
<strong></strong>
<span><i className="is-online" />{vehicles.isFetching ? '正在刷新' : '实时车辆已同步'}</span>
<span>{mode === 'map' ? `列表 ${rows.length} 条 · 地图 ${map.data?.clusters.length ? `${map.data.clusters.length} 个聚合 + ${map.data.points.length} 个车辆点` : `${map.data?.points.length ?? 0} 个点`}` : `实时列表 ${realtimeListQuery.data?.items.length ?? 0} / ${realtimeListQuery.data?.total ?? 0}`}</span>
<span>{mode === 'map' ? `列表 ${visibleRows.length} 条 · 地图 ${map.data?.clusters.length ? `${map.data.clusters.length} 个聚合 + ${map.data.points.length} 个车辆点` : `${map.data?.points.length ?? 0} 个点`}` : `实时列表 ${realtimeListQuery.data?.items.length ?? 0} / ${realtimeListQuery.data?.total ?? 0}`}</span>
<span className="v2-refresh-cadence"><b></b> {MONITOR_REFRESH.selected / 1000} · {MONITOR_REFRESH.fleet / 1000} · {MONITOR_REFRESH.summary / 1000} </span>
<time>{new Date().toLocaleString('zh-CN', { hour12: false })}</time>
</section>