fix: 里程环比改为真实值(与前一天对比)

- 后端缓存刷新时查询前一天总里程(yesterdayTotal)
- 前端计算真实环比:(今日-昨日)/昨日*100%
- 上涨显示蓝色↑,下跌显示红色↓
- 昨日无数据时不显示环比

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-01 23:33:52 +08:00
parent 66de41d50b
commit cbf0e18634
3 changed files with 19 additions and 6 deletions

View File

@@ -106,7 +106,7 @@ export default function MonitoringView() {
const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' }); const [appliedMileageRange, setAppliedMileageRange] = useState({ min: '', max: '' });
const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]); const [vehicles, setVehicles] = useState<MonitoringVehicle[]>([]);
const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, vehicleCount: 0 }); const [stats, setStats] = useState<MonitoringStats>({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 });
const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [] }); const [filterOptions, setFilterOptions] = useState<MonitoringFilters>({ departments: [], customers: [], plates: [], projects: [], entities: [] });
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
@@ -713,7 +713,11 @@ export default function MonitoringView() {
</div> </div>
<div className="text-2xl font-black tracking-tighter flex items-baseline gap-1"> <div className="text-2xl font-black tracking-tighter flex items-baseline gap-1">
{Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()} {Math.round(sortBy === 'today' ? stats.totalToday : stats.totalAll).toLocaleString()}
{sortBy === 'today' && <span className="text-blue-400 text-[10px] font-bold">{'\u2191'}12%</span>} {sortBy === 'today' && stats.yesterdayTotal > 0 && (() => {
const change = ((stats.totalToday - stats.yesterdayTotal) / stats.yesterdayTotal) * 100;
const isUp = change >= 0;
return <span className={`text-[10px] font-bold ${isUp ? 'text-blue-400' : 'text-rose-400'}`}>{isUp ? '\u2191' : '\u2193'}{Math.abs(change).toFixed(1)}%</span>;
})()}
</div> </div>
<div className="absolute -right-4 -bottom-4 w-12 h-12 bg-blue-500/10 rounded-full blur-xl"></div> <div className="absolute -right-4 -bottom-4 w-12 h-12 bg-blue-500/10 rounded-full blur-xl"></div>
</div> </div>

View File

@@ -18,6 +18,7 @@ export interface MonitoringStats {
totalToday: number; totalToday: number;
totalAll: number; totalAll: number;
vehicleCount: number; vehicleCount: number;
yesterdayTotal: number;
} }
export interface MonitoringFilters { export interface MonitoringFilters {

View File

@@ -43,7 +43,7 @@ interface CachedVehicle {
interface MonitoringCache { interface MonitoringCache {
vehicles: CachedVehicle[]; vehicles: CachedVehicle[];
stats: { totalToday: number; totalAll: number; vehicleCount: number }; stats: { totalToday: number; totalAll: number; vehicleCount: number; yesterdayTotal: 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;
} }
@@ -56,7 +56,7 @@ async function refreshMonitoringCache() {
const start = Date.now(); const start = Date.now();
// 并行查询两个数据库 // 并行查询两个数据库
const [mileageResult, infoRows] = await Promise.all([ const [mileageResult, yesterdayResult, infoRows] = await Promise.all([
(async () => { (async () => {
const [dateRows] = await mileagePool.execute( const [dateRows] = await mileagePool.execute(
'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats' 'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats'
@@ -70,6 +70,13 @@ async function refreshMonitoringCache() {
) as any; ) as any;
return rows; return rows;
})(), })(),
(async () => {
const [rows] = await mileagePool.execute(
`SELECT SUM(daily_km) as total FROM v_vehicle_daily_stats
WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)`
) as any;
return Number(rows[0]?.total) || 0;
})(),
pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]), pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]),
]); ]);
@@ -130,7 +137,7 @@ async function refreshMonitoringCache() {
monitoringCache = { monitoringCache = {
vehicles, vehicles,
stats: { totalToday, totalAll, vehicleCount: vehicles.length }, stats: { totalToday, totalAll, vehicleCount: vehicles.length, yesterdayTotal: yesterdayResult },
filters: { departments, customers, plates, projects, entities }, filters: { departments, customers, plates, projects, entities },
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
}; };
@@ -148,7 +155,7 @@ setInterval(refreshMonitoringCache, 2 * 60 * 1000);
// GET /monitoring — 从缓存取数据,支持筛选/排序/分页 // GET /monitoring — 从缓存取数据,支持筛选/排序/分页
app.get('/monitoring', (c) => { app.get('/monitoring', (c) => {
if (!monitoringCache) { if (!monitoringCache) {
return c.json({ vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() }); return c.json({ vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() });
} }
const sortBy = c.req.query('sortBy') || 'today'; const sortBy = c.req.query('sortBy') || 'today';
@@ -190,6 +197,7 @@ app.get('/monitoring', (c) => {
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: monitoringCache.stats.yesterdayTotal,
}; };
// 排序 // 排序