diff --git a/docs/oneos-mileage-api-requirements.md b/docs/oneos-mileage-api-requirements.md index f3909b9..6fcbcfc 100644 --- a/docs/oneos-mileage-api-requirements.md +++ b/docs/oneos-mileage-api-requirements.md @@ -22,6 +22,7 @@ POST /api/v1/vehicles/mileage/range/query "startDate": "2026-07-01", "endDate": "2026-07-23", "plateNumbers": ["沪A00001", "沪A00002"], + "protocolPriority": ["GB32960", "MQTT", "JT808"], "cursor": null, "pageSize": 5000 } @@ -31,6 +32,14 @@ POST /api/v1/vehicles/mileage/range/query - `startDate`、`endDate` 必填,最长支持 366 天; - `plateNumbers` 可省略,省略时返回授权范围内全部车辆; +- `protocolPriority` 可省略;提供时只允许 `GB32960`、`MQTT`、`JT808`, + 数组顺序表示逐车逐日的数据源优先级,未列出的协议视为禁用; +- BI 只会使用以下四种组合: + - 仪表数据优先:`["GB32960", "MQTT", "JT808"]`; + - GPS 数据优先:`["JT808", "GB32960", "MQTT"]`; + - 仅仪表数据:`["GB32960", "MQTT"]`; + - 仅 GPS 数据:`["JT808"]`; +- 仪表数据内部优先级固定为 `GB32960 > MQTT`,不提供反向切换; - 数据量过大时使用 `cursor` 分页,同一次查询使用相同 `snapshotId`; - 每辆车每天返回一条记录; - 无数据返回 `status: "NO_DATA"` 且 `dailyMileageKm: null`,真实零里程返回 `status: "NORMAL"` 且 `dailyMileageKm: 0`。 @@ -47,6 +56,7 @@ POST /api/v1/vehicles/mileage/range/query "plateNumber": "沪A00001", "date": "2026-07-01", "dailyMileageKm": 182.437, + "sourceProtocol": "GB32960", "dataTime": "2026-07-01T23:58:45+08:00", "updatedAt": "2026-07-02T05:10:00+08:00", "status": "NORMAL" @@ -71,13 +81,15 @@ POST /api/v1/vehicles/mileage/query ```json { "date": "2026-07-23", - "plateNumbers": ["沪A00001", "沪A00002"] + "plateNumbers": ["沪A00001", "沪A00002"], + "protocolPriority": ["GB32960", "MQTT", "JT808"] } ``` 规则: - `date` 和 `plateNumbers` 沿用现有接口规则; +- `protocolPriority` 的枚举、顺序和禁用规则与区间接口完全一致; - `plateNumbers` 省略时返回授权范围内全部车辆; - 接口默认在现有车辆结果中返回 `totalMileageKm`、`dataTime` 和 `updatedAt`; - 只增加响应字段,不删除或修改任何现有字段及其语义; @@ -100,6 +112,7 @@ POST /api/v1/vehicles/mileage/query "date": "2026-07-23", "dailyMileageKm": 182.437, "totalMileageKm": 12345.678, + "sourceProtocol": "GB32960", "dataTime": "2026-07-23T10:35:42+08:00", "updatedAt": "2026-07-23T10:35:46+08:00", "status": "NORMAL" @@ -114,6 +127,11 @@ POST /api/v1/vehicles/mileage/query ## 通用要求 - `dataTime`、`updatedAt` 使用带 `+08:00` 的 ISO 8601 格式; +- `sourceProtocol` 必须返回本条结果实际采用的协议: + `GB32960`、`MQTT` 或 `JT808`;无数据时返回 `null`; +- OneOS 必须按 `protocolPriority` 选择第一份有效数据,不得返回已禁用协议的数据; +- `sourceProtocol=GB32960/MQTT` 在 BI 显示为“仪表数据”, + `sourceProtocol=JT808` 显示为“GPS数据”; - 每个响应返回 `traceId`; - 接口返回值保留原始精度; - 负里程不得作为正常数据返回; @@ -127,3 +145,6 @@ POST /api/v1/vehicles/mileage/query 3. 现有接口不传车牌时,默认返回全部授权车辆的日里程、累计里程和数据时间。 4. 正确区分无数据与真实零里程。 5. `dataTime` 能反映每辆车实际数据的新鲜度。 +6. 四种 `protocolPriority` 组合均能按顺序选源,且响应中的 + `sourceProtocol` 与实际采用协议一致。 +7. 禁用某类协议后,响应不得回退到该协议。 diff --git a/src/modules/mileage/MonitoringView.tsx b/src/modules/mileage/MonitoringView.tsx index af4402c..b034ec2 100644 --- a/src/modules/mileage/MonitoringView.tsx +++ b/src/modules/mileage/MonitoringView.tsx @@ -4,10 +4,15 @@ import { Truck, Filter, ChevronDown, Maximize2, Minimize2, RotateCcw, ArrowUp, ArrowDown, ChevronsUp, Download, Check, CalendarDays, - RefreshCw, + RefreshCw, ArrowLeftRight, Power, } from 'lucide-react'; import { BarChart, Bar, ResponsiveContainer, Tooltip, ReferenceLine, XAxis } from 'recharts'; -import type { MonitoringVehicle, MonitoringStats, MonitoringFilters } from './types'; +import type { + MileageSourceGroup, + MonitoringVehicle, + MonitoringStats, + MonitoringFilters, +} from './types'; import { fetchMonitoring } from './api'; import Blur from '../../components/Blur'; import PlateMultiSelect from './PlateMultiSelect'; @@ -19,6 +24,116 @@ const HIGH_MILEAGE_ALERT_TARGETS = new Set([ '交投190辆4.5T冷链车', ]); const HIGH_MILEAGE_ALERT_KM = 800; +const ALL_MILEAGE_SOURCE_GROUPS: MileageSourceGroup[] = ['instrument', 'gps']; + +const MILEAGE_SOURCE_META = { + instrument: { label: '仪表数据', protocol: '32960 > MQTT' }, + gps: { label: 'GPS数据', protocol: 'JT808' }, +} as const; + +function vehicleSourceDisplay(vehicle: MonitoringVehicle): { + label: string; + title: string; + className: string; +} { + if (vehicle.sourceCategory === 'INSTRUMENT') { + return { + label: '仪表数据', + title: vehicle.sourceProtocol || 'GB32960 / MQTT', + className: 'bg-violet-50 text-violet-600', + }; + } + if (vehicle.sourceCategory === 'GPS') { + return { + label: 'GPS数据', + title: vehicle.sourceProtocol || 'JT808', + className: 'bg-emerald-50 text-emerald-600', + }; + } + if (vehicle.sourceCategory === 'MIXED') { + return { + label: '仪表+GPS', + title: '所选区间内包含仪表数据和 GPS 数据', + className: 'bg-blue-50 text-blue-600', + }; + } + return { + label: vehicle.isDataSynced ? '来源待接口' : '无数据', + title: vehicle.isDataSynced + ? 'OneOS 当前响应未返回 sourceProtocol' + : '所选来源没有有效数据', + className: vehicle.isDataSynced + ? 'bg-amber-50 text-amber-600' + : 'bg-slate-100 text-slate-400', + }; +} + +function SourcePriorityControl({ + priority, + onChange, +}: { + priority: MileageSourceGroup[]; + onChange: (next: MileageSourceGroup[]) => void; +}) { + const ordered = [ + ...priority, + ...ALL_MILEAGE_SOURCE_GROUPS.filter(group => !priority.includes(group)), + ]; + const toggle = (group: MileageSourceGroup) => { + if (priority.includes(group)) { + if (priority.length === 1) return; + onChange(priority.filter(item => item !== group)); + return; + } + onChange([...priority, group]); + }; + const swap = () => { + if (priority.length === 2) onChange([priority[1], priority[0]]); + }; + + return ( +