feat(vehicle-hub): 新增车辆运维页(健康监控 / 维护操作 / 批量管理)

- 新增 fleetHealth / vehicleOps API、useVehicleHub 组合式与 VehicleHealthCard 卡片
- 新增 /admin/config/vehicle-hub 与 /monitor/vehicle-hub 双端路由及侧栏菜单
- car 类型补充 rawId/onboardUrl/lstatus 及 FleetHealthRow/VehicleCardModel
- 后端 PageCatalog 注册「车辆运维」页,原「车辆维护」更名为「车辆维护策略」

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-05-31 00:16:16 +08:00
co-authored by Cursor
parent 34635dca05
commit 62943e6c42
12 changed files with 926 additions and 10 deletions
@@ -0,0 +1,37 @@
import http from './http'
import type { FleetHealthRow } from '@/types/car'
import { CARS } from '@/mock/data/cars'
const MOCK = import.meta.env.VITE_USE_MOCK === 'true'
function mockFleetHealth(): FleetHealthRow[] {
return CARS.map((c, i) => {
const rawId = c.rawId ?? (parseInt(c.id.replace(/\D/g, ''), 10) || i + 1)
const ip = c.ip ?? `10.0.1.${10 + i}`
const reachable = c.state !== 'offline'
return {
carId: rawId,
carName: c.name,
ip,
onboardUrl: c.onboardUrl ?? `http://${ip}:8081`,
latencyMs: reachable ? 8 + i * 4 : 2000,
reachable,
probedAt: new Date().toISOString(),
uptimeSecs: 3600 + i * 120,
alarmActiveSecs: c.state === 'fault' ? 120 : i * 5,
faultRatePercent: c.state === 'fault' ? 3.2 : 0.1 * i,
isAlarmActive: c.state === 'fault',
cpuPercent: 20 + i * 8,
memPercent: 40 + i * 5
}
})
}
export async function fetchFleetHealth(): Promise<FleetHealthRow[]> {
if (MOCK) {
await new Promise((r) => setTimeout(r, 120))
return mockFleetHealth()
}
const { data } = await http.get<FleetHealthRow[]>('/sl/projection/fleet/health')
return Array.isArray(data) ? data : []
}
@@ -0,0 +1,30 @@
import { reflectionApi } from './reflection'
export type VehicleMaintenanceMode = 'online' | 'offline' | 'repair' | 'blown'
const METHOD_MAP: Record<VehicleMaintenanceMode, string> = {
online: 'OnlineCar',
offline: 'OfflineCar',
repair: 'Repair',
blown: 'Blown'
}
export async function setVehicleMaintenance(
carId: number,
mode: VehicleMaintenanceMode
): Promise<boolean> {
const method = METHOD_MAP[mode]
if (!method) return false
try {
await reflectionApi.execute('car', carId, method)
return true
} catch {
return false
}
}
export function openOnboardWeb(url?: string | null, ip?: string | null): void {
const target = url ?? (ip ? `http://${ip}:8081` : null)
if (!target) return
window.open(target, '_blank', 'noopener,noreferrer')
}