import { Hono } from 'hono'; import { getCache, queryDateMileage, buildDateFilters } from './cache.js'; import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js'; import type { AuthUser } from '../../auth/types.js'; import type { CachedVehicle, MonitoringFilters, MonitoringResponse } from './types.js'; const app = new Hono(); const EMPTY_RESPONSE: MonitoringResponse = { vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [], platePrefixes: [], targetNames: [], regions: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString(), }; function applyFilters(vehicles: CachedVehicle[], params: { search: string; dept: string; customer: string; project: string; entity: string; rentStatus: string; plate: string; platePrefix: string; targetName: string; region: string; mileageMin: string; mileageMax: string; }): CachedVehicle[] { let result = vehicles; if (params.search) { const q = params.search.toLowerCase(); result = result.filter(v => v.plate.toLowerCase().includes(q) || (v.customer || '').toLowerCase().includes(q) || (v.project || '').toLowerCase().includes(q) ); } if (params.dept) result = result.filter(v => params.dept === '__EMPTY__' ? !v.department : v.department === params.dept); if (params.customer) result = result.filter(v => params.customer === '__EMPTY__' ? !v.customer : v.customer === params.customer); if (params.project) result = result.filter(v => v.project === params.project); if (params.entity) result = result.filter(v => v.entity === params.entity); if (params.rentStatus) result = result.filter(v => v.rentStatus === params.rentStatus); if (params.plate) { const wanted = new Set(params.plate.split(',').map(s => s.trim()).filter(Boolean)); if (wanted.size > 0) result = result.filter(v => wanted.has(v.plate)); } if (params.platePrefix) result = result.filter(v => v.plate.startsWith(params.platePrefix)); if (params.region) result = result.filter(v => v.region === params.region); if (params.targetName) { const cache = getCache(); const tPlates = cache?.targetPlatesMap.get(params.targetName); result = tPlates ? result.filter(v => tPlates.has(v.plate)) : []; } if (params.mileageMin) result = result.filter(v => v.dailyKm >= Number(params.mileageMin)); if (params.mileageMax) result = result.filter(v => v.dailyKm <= Number(params.mileageMax)); return result; } app.get('/', async (c) => { const sortBy = c.req.query('sortBy') || 'today'; const sortOrder = c.req.query('sortOrder') || 'desc'; const limit = Number(c.req.query('limit')) || 50; const page = Number(c.req.query('page')) || 1; const date = c.req.query('date') || ''; const filterParams = { search: c.req.query('search') || '', dept: c.req.query('dept') || '', customer: c.req.query('customer') || '', project: c.req.query('project') || '', entity: c.req.query('entity') || '', rentStatus: c.req.query('rentStatus') || '', plate: c.req.query('plate') || '', platePrefix: c.req.query('platePrefix') || '', targetName: c.req.query('targetName') || '', region: c.req.query('region') || '', mileageMin: c.req.query('mileageMin') || '', mileageMax: c.req.query('mileageMax') || '', }; let allVehicles: CachedVehicle[]; let filters: MonitoringFilters; if (date) { try { allVehicles = await queryDateMileage(date); filters = buildDateFilters(allVehicles); } catch (e: unknown) { console.error('monitoring date query error:', e); return c.json(EMPTY_RESPONSE, 500); } } else { const cache = getCache(); if (!cache) return c.json(EMPTY_RESPONSE); allVehicles = cache.vehicles; filters = cache.filters; } // 权限过滤 const user = (c as any).get('user') as AuthUser | undefined; if (user) { allVehicles = filterByPermission(allVehicles, user); filters = buildDateFilters(allVehicles); // 重算筛选选项以匹配权限范围 } // 区域级联:选中运营区域时,下游筛选选项(车牌等)只展示该区域车辆 if (filterParams.region) { const regionScope = allVehicles.filter(v => v.region === filterParams.region); filters = buildDateFilters(regionScope); } const filtered = applyFilters(allVehicles, filterParams); const stats = { totalToday: filtered.reduce((sum, v) => sum + v.dailyKm, 0), totalAll: filtered.reduce((sum, v) => sum + (v.totalKm || 0), 0), vehicleCount: filtered.length, yesterdayTotal: filtered.reduce((sum, v) => sum + v.yesterdayKm, 0), }; const sorted = [...filtered].sort((a, b) => { const valA = sortBy === 'today' ? a.dailyKm : (a.totalKm || 0); const valB = sortBy === 'today' ? b.dailyKm : (b.totalKm || 0); return sortOrder === 'desc' ? valB - valA : valA - valB; }); const offset = (page - 1) * limit; const paged = sorted.slice(offset, offset + limit); const total = filtered.length; return c.json({ vehicles: maskCustomerNames(paged), stats, filters, total, page, totalPages: Math.ceil(total / limit), updatedAt: date || getCache()?.updatedAt || new Date().toISOString(), }); }); export default app;