From 5bb3ceb47ad487d4aab48debec2a9901b3d957c7 Mon Sep 17 00:00:00 2001 From: kkfluous Date: Wed, 3 Jun 2026 15:10:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(mileage):=20=E4=BA=A4=E6=8A=95=E9=AB=98?= =?UTF-8?q?=E9=87=8C=E7=A8=8B=E8=BD=A6=E8=BE=86=E6=A0=87=E7=BA=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/mileage/MonitoringView.tsx | 34 +++++++++---- src/modules/mileage/types.ts | 1 + src/server/routes/mileage/cache.ts | 66 ++++++++++++++++++++------ src/server/routes/mileage/types.ts | 1 + 4 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/modules/mileage/MonitoringView.tsx b/src/modules/mileage/MonitoringView.tsx index 78694ab..723f505 100644 --- a/src/modules/mileage/MonitoringView.tsx +++ b/src/modules/mileage/MonitoringView.tsx @@ -12,6 +12,12 @@ import PlateMultiSelect from './PlateMultiSelect'; import { exportMileageXlsx } from './xlsx-export'; import VehicleDetailModal from './VehicleDetailModal'; +const HIGH_MILEAGE_ALERT_TARGETS = new Set([ + '交投40辆4.5T普货', + '交投190辆4.5T冷链车', +]); +const HIGH_MILEAGE_ALERT_KM = 800; + const SearchableSelect = ({ options, value, @@ -137,6 +143,12 @@ export default function MonitoringView() { const departments = filterOptions.departments; const plateNumbers = filterOptions.plates; + const isHighMileageAlert = useCallback((v: MonitoringVehicle) => { + const inAlertTarget = v.targetNames?.some(name => HIGH_MILEAGE_ALERT_TARGETS.has(name)) + || HIGH_MILEAGE_ALERT_TARGETS.has(filterTargetName); + return inAlertTarget && Math.max(0, v.dailyKm || 0) >= HIGH_MILEAGE_ALERT_KM; + }, [filterTargetName]); + // 加载首页数据 const loadFirstPage = useCallback(() => { setPageLoading(true); @@ -525,7 +537,9 @@ export default function MonitoringView() { - {fullscreenVehicles.map((v) => ( + {fullscreenVehicles.map((v) => { + const highMileageAlert = isHighMileageAlert(v); + return (
@@ -535,8 +549,8 @@ export default function MonitoringView() { {v.rentStatus || '-'} {v.department || '-'} - - {(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接} + + {(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接} @@ -545,7 +559,8 @@ export default function MonitoringView() { - ))} + ); + })} @@ -896,7 +911,9 @@ export default function MonitoringView() { )}
- {filteredVehicles.map((v) => ( + {filteredVehicles.map((v) => { + const highMileageAlert = isHighMileageAlert(v); + return (
)} -
- {(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接} +
+ {(v.isDataSynced || v.totalKm != null) ? <>{Math.max(0, v.dailyKm || 0).toLocaleString()} km : 未对接}
@@ -942,7 +959,8 @@ export default function MonitoringView() {
- ))} + ); + })} {filteredVehicles.length === 0 && !loadingMore && ( diff --git a/src/modules/mileage/types.ts b/src/modules/mileage/types.ts index e7371ca..4207619 100644 --- a/src/modules/mileage/types.ts +++ b/src/modules/mileage/types.ts @@ -13,6 +13,7 @@ export interface MonitoringVehicle { entity: string | null; project: string | null; region: string | null; + targetNames: string[]; } export interface MonitoringStats { diff --git a/src/server/routes/mileage/cache.ts b/src/server/routes/mileage/cache.ts index 10c7e5d..5bce187 100644 --- a/src/server/routes/mileage/cache.ts +++ b/src/server/routes/mileage/cache.ts @@ -65,6 +65,41 @@ interface MileageRow { source: string; } +interface TargetRow { + id: number; + target_name: string; + plate_number: string; +} + +async function fetchTargetRows(): Promise { + return pool.execute( + `SELECT t.id, t.target_name, v.plate_number + FROM tab_mileage_assessment_target t + JOIN tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0 + WHERE t.is_deleted = 0` + ).then(([rows]) => rows as TargetRow[]); +} + +function buildTargetPlatesMap(targetRows: TargetRow[]): Map> { + const targetPlatesMap = new Map>(); + for (const r of targetRows) { + const set = targetPlatesMap.get(r.target_name) || new Set(); + set.add(r.plate_number); + targetPlatesMap.set(r.target_name, set); + } + return targetPlatesMap; +} + +function buildPlateTargetNamesMap(targetRows: TargetRow[]): Map { + const map = new Map(); + for (const r of targetRows) { + const list = map.get(r.plate_number) || []; + list.push(r.target_name); + map.set(r.plate_number, list); + } + return map; +} + async function fetchBizTotalMileageMap(): Promise> { // v_vehicle_daily_stats.total_km 对 G7S 数据源常为 NULL(G7 只回传日增量), // 业务库 tab_mileage_assessment_vehicle.vehicle_total_mileage 是累加后的权威累计值, @@ -115,6 +150,7 @@ function mergeVehicles( yesterdayMap: Map, bizTotalMap: Map, latestPgTotalMap: Map, + targetNamesByPlate: Map, ): CachedVehicle[] { const mileageMap = new Map(); for (const row of mileageRows) { @@ -147,6 +183,7 @@ function mergeVehicles( entity: info?.entity || null, project: info?.project || null, region: regionMap[m.plate] || null, + targetNames: targetNamesByPlate.get(m.plate) || [], yesterdayKm: yesterdayMap.get(m.plate) || 0, }; }); @@ -184,25 +221,16 @@ export async function refreshMonitoringCache(): Promise { return map; })(), fetchVehicleInfoMap(), - pool.execute( - `SELECT t.id, t.target_name, v.plate_number - FROM tab_mileage_assessment_target t - JOIN tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0 - WHERE t.is_deleted = 0` - ).then(([rows]) => rows as { id: number; target_name: string; plate_number: string }[]), + fetchTargetRows(), fetchBizTotalMileageMap(), fetchLatestPgTotalMileageMap(), ]); - const targetPlatesMap = new Map>(); - for (const r of targetRows) { - const set = targetPlatesMap.get(r.target_name) || new Set(); - set.add(r.plate_number); - targetPlatesMap.set(r.target_name, set); - } + const targetPlatesMap = buildTargetPlatesMap(targetRows); + const targetNamesByPlate = buildPlateTargetNamesMap(targetRows); const targetNames = Array.from(targetPlatesMap.keys()); - const vehicles = mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap, latestPgTotalMap); + const vehicles = mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap, latestPgTotalMap, targetNamesByPlate); const totalToday = vehicles.reduce((sum, v) => sum + v.dailyKm, 0); const totalAll = vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0); @@ -221,7 +249,7 @@ export async function refreshMonitoringCache(): Promise { } export async function queryDateMileage(dateStr: string): Promise { - const [mileageRows, yesterdayRows, infoMap, bizTotalMap, latestPgTotalMap] = await Promise.all([ + const [mileageRows, yesterdayRows, infoMap, targetRows, bizTotalMap, latestPgTotalMap] = await Promise.all([ mileagePool.execute( 'SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?', [dateStr] @@ -231,6 +259,7 @@ export async function queryDateMileage(dateStr: string): Promise r as { plate: string; daily_km: string }[]), fetchVehicleInfoMap(), + fetchTargetRows(), fetchBizTotalMileageMap(), fetchLatestPgTotalMileageMap(dateStr), ]); @@ -242,7 +271,14 @@ export async function queryDateMileage(dateStr: string): Promise existing) yesterdayMap.set(r.plate, km); } - return mergeVehicles(mileageRows, infoMap, yesterdayMap, bizTotalMap, latestPgTotalMap); + return mergeVehicles( + mileageRows, + infoMap, + yesterdayMap, + bizTotalMap, + latestPgTotalMap, + buildPlateTargetNamesMap(targetRows), + ); } export function buildDateFilters(vehicles: CachedVehicle[]): MonitoringFilters { diff --git a/src/server/routes/mileage/types.ts b/src/server/routes/mileage/types.ts index a997fea..0c953dd 100644 --- a/src/server/routes/mileage/types.ts +++ b/src/server/routes/mileage/types.ts @@ -15,6 +15,7 @@ export interface CachedVehicle { entity: string | null; project: string | null; region: string | null; + targetNames: string[]; yesterdayKm: number; }