fix: 里程环比改为真实值(与前一天对比)
- 后端缓存刷新时查询前一天总里程(yesterdayTotal) - 前端计算真实环比:(今日-昨日)/昨日*100% - 上涨显示蓝色↑,下跌显示红色↓ - 昨日无数据时不显示环比 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 排序
|
// 排序
|
||||||
|
|||||||
Reference in New Issue
Block a user