feat: 多项优化 - 全屏加载全部数据、无值筛选、刷新按钮、加载动画、负值显示为0
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

- 全屏监控一次加载全部车辆数据,支持完整滚动和筛选
- 客户/部门筛选增加"无值"选项筛选空数据
- 全屏刷新按钮实际触发数据重新加载,带旋转动画
- 全屏筛选时显示加载遮罩
- 负值里程前端显示为0
- 未对接车机显示"未对接"替代"-"
- 删除"未同步"标签
- 统计报表配色统一为白色主题、KPI联动选中项目
- 统计报表全屏表格列合并优化
- 车辆明细面板增加日期选择、租赁状态/部门/客户信息、里程合计
- 每分钟自动刷新数据
- 清除按钮修复租赁状态重置

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
kkfluous
2026-04-02 10:52:45 +08:00
parent 06a2edc470
commit adc9c3a9db
5 changed files with 338 additions and 224 deletions

View File

@@ -159,7 +159,7 @@ async function refreshMonitoringCache() {
// 启动时立即刷新之后每2分钟刷新
refreshMonitoringCache();
setInterval(refreshMonitoringCache, 2 * 60 * 1000);
setInterval(refreshMonitoringCache, 60 * 1000);
// 查询指定日期的里程数据(非缓存)
async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[] }> {
@@ -258,8 +258,8 @@ app.get('/monitoring', async (c) => {
(v.project || '').toLowerCase().includes(q)
);
}
if (dept) vehicles = vehicles.filter(v => v.department === dept);
if (customer) vehicles = vehicles.filter(v => v.customer === customer);
if (dept) vehicles = vehicles.filter(v => dept === '__EMPTY__' ? !v.department : v.department === dept);
if (customer) vehicles = vehicles.filter(v => customer === '__EMPTY__' ? !v.customer : v.customer === customer);
if (project) vehicles = vehicles.filter(v => v.project === project);
if (entity) vehicles = vehicles.filter(v => v.entity === entity);
if (rentStatus) vehicles = vehicles.filter(v => v.rentStatus === rentStatus);
@@ -398,10 +398,12 @@ app.get('/targets', async (c) => {
}
});
// GET /target/:id/vehicles — 某项目的车辆明细
// GET /target/:id/vehicles — 某项目的车辆明细(支持 ?date= 查询指定日期里程)
app.get('/target/:id/vehicles', async (c) => {
const targetId = c.req.param('id');
const date = c.req.query('date') || '';
try {
// 获取考核车辆基本信息
const [rows] = await pool.execute(
`SELECT plate_number, today_mileage, vehicle_total_mileage,
completion_rate, is_qualified, current_year_is_qualified,
@@ -412,15 +414,58 @@ app.get('/target/:id/vehicles', async (c) => {
[targetId]
) as any;
const result = rows.map((r: any) => ({
plateNumber: r.plate_number,
todayMileage: Number(r.today_mileage) || 0,
totalMileage: Number(r.vehicle_total_mileage) || 0,
completionRate: Number(r.completion_rate) || 0,
isQualified: r.is_qualified === 1,
currentYearIsQualified: r.current_year_is_qualified === 1,
dailyRequiredMileage: Number(r.daily_required_mileage) || 0,
}));
// 获取车辆关联信息(租赁状态、部门、客户)
const plates = rows.map((r: any) => r.plate_number);
const infoMap = new Map<string, any>();
if (plates.length > 0) {
const [infoRows] = await pool.execute(
`${VEHICLE_INFO_SQL} AND truck.plate_number IN (${plates.map(() => '?').join(',')})`,
plates
) as any;
for (const row of infoRows) {
infoMap.set(row.plate, row);
}
}
// 如果指定了日期,从里程数据库查询该日期的里程
const dateMileageMap = new Map<string, { dailyKm: number; totalKm: number | null; isOnline: boolean }>();
if (date && plates.length > 0) {
const [mileageRows] = await mileagePool.execute(
`SELECT plate, daily_km, total_km, source FROM v_vehicle_daily_stats
WHERE stat_date = ? AND plate IN (${plates.map(() => '?').join(',')})`,
[date, ...plates]
) as any;
for (const m of mileageRows) {
const existing = dateMileageMap.get(m.plate);
const dailyKm = Number(m.daily_km) || 0;
if (!existing || dailyKm > existing.dailyKm) {
const source = m.source || 'NONE';
dateMileageMap.set(m.plate, {
dailyKm,
totalKm: m.total_km !== null ? Number(m.total_km) : null,
isOnline: source !== 'NONE' && dailyKm > 0,
});
}
}
}
const result = rows.map((r: any) => {
const info = infoMap.get(r.plate_number);
const dateMileage = date ? dateMileageMap.get(r.plate_number) : null;
return {
plateNumber: r.plate_number,
todayMileage: dateMileage ? dateMileage.dailyKm : (Number(r.today_mileage) || 0),
totalMileage: dateMileage?.totalKm ?? (Number(r.vehicle_total_mileage) || 0),
completionRate: Number(r.completion_rate) || 0,
isQualified: r.is_qualified === 1,
currentYearIsQualified: r.current_year_is_qualified === 1,
dailyRequiredMileage: Number(r.daily_required_mileage) || 0,
rentStatus: info?.rent_status || null,
department: info?.department || null,
customer: info?.customer || null,
isOnline: dateMileage ? dateMileage.isOnline : true,
};
});
return c.json(result);
} catch (e) {