fix: 环比统计跟随筛选条件正确计算
每辆车缓存其昨日里程(yesterdayKm),筛选后的环比基于 相同筛选条件下的车辆计算,而非全局对比。 例如筛选"业务一部"后,今日和昨日都只统计一部的车辆。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,11 +39,12 @@ interface CachedVehicle {
|
|||||||
rentStatus: string | null;
|
rentStatus: string | null;
|
||||||
entity: string | null;
|
entity: string | null;
|
||||||
project: string | null;
|
project: string | null;
|
||||||
|
yesterdayKm: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MonitoringCache {
|
interface MonitoringCache {
|
||||||
vehicles: CachedVehicle[];
|
vehicles: CachedVehicle[];
|
||||||
stats: { totalToday: number; totalAll: number; vehicleCount: number; yesterdayTotal: number };
|
stats: { totalToday: number; totalAll: number; vehicleCount: number };
|
||||||
filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] };
|
filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[] };
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
@@ -72,10 +73,16 @@ async function refreshMonitoringCache() {
|
|||||||
})(),
|
})(),
|
||||||
(async () => {
|
(async () => {
|
||||||
const [rows] = await mileagePool.execute(
|
const [rows] = await mileagePool.execute(
|
||||||
`SELECT SUM(daily_km) as total FROM v_vehicle_daily_stats
|
`SELECT plate, daily_km FROM v_vehicle_daily_stats
|
||||||
WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)`
|
WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)`
|
||||||
) as any;
|
) as any;
|
||||||
return Number(rows[0]?.total) || 0;
|
const map = new Map<string, number>();
|
||||||
|
for (const r of rows) {
|
||||||
|
const existing = map.get(r.plate) || 0;
|
||||||
|
const km = Number(r.daily_km) || 0;
|
||||||
|
if (km > existing) map.set(r.plate, km);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
})(),
|
})(),
|
||||||
pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]),
|
pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]),
|
||||||
]);
|
]);
|
||||||
@@ -114,6 +121,7 @@ async function refreshMonitoringCache() {
|
|||||||
rentStatus: info?.rent_status || null,
|
rentStatus: info?.rent_status || null,
|
||||||
entity: info?.entity || null,
|
entity: info?.entity || null,
|
||||||
project: info?.project || null,
|
project: info?.project || null,
|
||||||
|
yesterdayKm: yesterdayResult.get(m.plate) || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -137,7 +145,7 @@ async function refreshMonitoringCache() {
|
|||||||
|
|
||||||
monitoringCache = {
|
monitoringCache = {
|
||||||
vehicles,
|
vehicles,
|
||||||
stats: { totalToday, totalAll, vehicleCount: vehicles.length, yesterdayTotal: yesterdayResult },
|
stats: { totalToday, totalAll, vehicleCount: vehicles.length },
|
||||||
filters: { departments, customers, plates, projects, entities },
|
filters: { departments, customers, plates, projects, entities },
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
@@ -153,18 +161,20 @@ refreshMonitoringCache();
|
|||||||
setInterval(refreshMonitoringCache, 2 * 60 * 1000);
|
setInterval(refreshMonitoringCache, 2 * 60 * 1000);
|
||||||
|
|
||||||
// 查询指定日期的里程数据(非缓存)
|
// 查询指定日期的里程数据(非缓存)
|
||||||
async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[]; yesterdayTotal: number }> {
|
async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[] }> {
|
||||||
const [mileageRows] = await mileagePool.execute(
|
const [mileageRows, yesterdayRows, infoRows] = await Promise.all([
|
||||||
`SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?`,
|
mileagePool.execute(`SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?`, [dateStr]).then(([r]) => r as any[]),
|
||||||
[dateStr]
|
mileagePool.execute(`SELECT plate, daily_km FROM v_vehicle_daily_stats WHERE stat_date = DATE_SUB(?, INTERVAL 1 DAY)`, [dateStr]).then(([r]) => r as any[]),
|
||||||
) as any;
|
pool.execute(VEHICLE_INFO_SQL).then(([r]) => r as any[]),
|
||||||
const [yesterdayRows] = await mileagePool.execute(
|
]);
|
||||||
`SELECT SUM(daily_km) as total FROM v_vehicle_daily_stats WHERE stat_date = DATE_SUB(?, INTERVAL 1 DAY)`,
|
|
||||||
[dateStr]
|
|
||||||
) as any;
|
|
||||||
const [infoRows] = await pool.execute(VEHICLE_INFO_SQL) as any;
|
|
||||||
const infoMap = new Map<string, any>();
|
const infoMap = new Map<string, any>();
|
||||||
for (const row of infoRows) infoMap.set(row.plate, row);
|
for (const row of infoRows) infoMap.set(row.plate, row);
|
||||||
|
const yesterdayMap = new Map<string, number>();
|
||||||
|
for (const r of yesterdayRows) {
|
||||||
|
const km = Number(r.daily_km) || 0;
|
||||||
|
const existing = yesterdayMap.get(r.plate) || 0;
|
||||||
|
if (km > existing) yesterdayMap.set(r.plate, km);
|
||||||
|
}
|
||||||
const mileageMap = new Map<string, any>();
|
const mileageMap = new Map<string, any>();
|
||||||
for (const row of mileageRows) {
|
for (const row of mileageRows) {
|
||||||
const existing = mileageMap.get(row.plate);
|
const existing = mileageMap.get(row.plate);
|
||||||
@@ -181,9 +191,10 @@ async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehi
|
|||||||
customer: info?.customer || null, department: info?.department || null,
|
customer: info?.customer || null, department: info?.department || null,
|
||||||
manager: info?.manager || null, rentStatus: info?.rent_status || null,
|
manager: info?.manager || null, rentStatus: info?.rent_status || null,
|
||||||
entity: info?.entity || null, project: info?.project || null,
|
entity: info?.entity || null, project: info?.project || null,
|
||||||
|
yesterdayKm: yesterdayMap.get(m.plate) || 0,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return { vehicles, yesterdayTotal: Number(yesterdayRows[0]?.total) || 0 };
|
return { vehicles };
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页
|
// GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页
|
||||||
@@ -205,7 +216,6 @@ app.get('/monitoring', async (c) => {
|
|||||||
const date = c.req.query('date') || '';
|
const date = c.req.query('date') || '';
|
||||||
|
|
||||||
let allVehicles: CachedVehicle[];
|
let allVehicles: CachedVehicle[];
|
||||||
let yesterdayTotal: number;
|
|
||||||
let filters: MonitoringCache['filters'];
|
let filters: MonitoringCache['filters'];
|
||||||
|
|
||||||
if (date) {
|
if (date) {
|
||||||
@@ -213,8 +223,6 @@ app.get('/monitoring', async (c) => {
|
|||||||
try {
|
try {
|
||||||
const result = await queryDateMileage(date);
|
const result = await queryDateMileage(date);
|
||||||
allVehicles = result.vehicles;
|
allVehicles = result.vehicles;
|
||||||
yesterdayTotal = result.yesterdayTotal;
|
|
||||||
// 从查询结果提取筛选选项
|
|
||||||
const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
|
const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
|
||||||
filters = {
|
filters = {
|
||||||
departments: (Array.from(new Set(allVehicles.map(v => v.department).filter(Boolean))) as string[]).sort((a, b) => {
|
departments: (Array.from(new Set(allVehicles.map(v => v.department).filter(Boolean))) as string[]).sort((a, b) => {
|
||||||
@@ -231,10 +239,8 @@ app.get('/monitoring', async (c) => {
|
|||||||
return c.json(emptyResponse, 500);
|
return c.json(emptyResponse, 500);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 默认:从缓存取
|
|
||||||
if (!monitoringCache) return c.json(emptyResponse);
|
if (!monitoringCache) return c.json(emptyResponse);
|
||||||
allVehicles = monitoringCache.vehicles;
|
allVehicles = monitoringCache.vehicles;
|
||||||
yesterdayTotal = monitoringCache.stats.yesterdayTotal;
|
|
||||||
filters = monitoringCache.filters;
|
filters = monitoringCache.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,12 +265,12 @@ app.get('/monitoring', async (c) => {
|
|||||||
|
|
||||||
const total = vehicles.length;
|
const total = vehicles.length;
|
||||||
|
|
||||||
// 基于筛选后的数据计算统计
|
// 基于筛选后的数据计算统计(yesterdayTotal 也基于筛选后的车辆)
|
||||||
const filteredStats = {
|
const filteredStats = {
|
||||||
totalToday: vehicles.reduce((sum, v) => sum + v.dailyKm, 0),
|
totalToday: vehicles.reduce((sum, v) => sum + v.dailyKm, 0),
|
||||||
totalAll: vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0),
|
totalAll: vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0),
|
||||||
vehicleCount: vehicles.length,
|
vehicleCount: vehicles.length,
|
||||||
yesterdayTotal,
|
yesterdayTotal: vehicles.reduce((sum, v) => sum + v.yesterdayKm, 0),
|
||||||
};
|
};
|
||||||
|
|
||||||
// 排序
|
// 排序
|
||||||
|
|||||||
Reference in New Issue
Block a user