feat(platform): refine realtime vehicle monitoring workflow

This commit is contained in:
lingniu
2026-07-05 12:37:26 +08:00
parent 0b7fc31d52
commit 9c6973b4ee
2 changed files with 23 additions and 20 deletions

View File

@@ -19,8 +19,11 @@ function canOpenVehicle(vin?: string) {
function vehicleServiceStatus(row: VehicleRealtimeRow) {
if (row.serviceStatus) {
const title = row.serviceStatus.status === 'degraded' || row.serviceStatus.title === '来源不完整'
? '数据通道不完整'
: row.serviceStatus.title;
return {
label: row.serviceStatus.title,
label: title,
color: row.serviceStatus.severity === 'ok' ? 'green' as const : row.serviceStatus.severity === 'error' ? 'red' as const : 'orange' as const
};
}
@@ -28,7 +31,7 @@ function vehicleServiceStatus(row: VehicleRealtimeRow) {
return { label: '车辆离线', color: 'red' as const };
}
if (row.onlineSourceCount < row.sourceCount) {
return { label: '来源不完整', color: 'orange' as const };
return { label: '数据通道不完整', color: 'orange' as const };
}
return { label: '服务正常', color: 'green' as const };
}
@@ -55,7 +58,7 @@ function sourceIssueTags(row: VehicleRealtimeRow) {
.filter((source) => !source.online || !source.hasRealtime)
.map((source) => `${source.protocol} ${source.hasRealtime ? '离线' : '未接入'}`) ?? [];
if (tags.length > 0) return tags;
if (row.onlineSourceCount < row.sourceCount) return ['来源缺失'];
if (row.onlineSourceCount < row.sourceCount) return ['数据通道缺失'];
return [];
}
@@ -144,7 +147,7 @@ const onlineLabel: Record<string, string> = {
const serviceStatusLabel: Record<string, string> = {
healthy: '服务正常',
degraded: '来源不完整',
degraded: '数据通道不完整',
offline: '车辆离线',
identity_required: '身份未绑定'
};
@@ -369,7 +372,7 @@ function realtimeDutyHandoffText({
`页面车辆:${rows.length.toLocaleString()} / 总计 ${total.toLocaleString()};版本:${runtimeRelease || '未标记'}`,
`在线:${onlineCount.toLocaleString()};离线:${(rows.length - onlineCount).toLocaleString()};定位有效:${locatedCount.toLocaleString()};需要关注:${degradedCount.toLocaleString()};超时:${staleCount.toLocaleString()};数据通道:${sourceTypeCount.toLocaleString()}`,
`地图:${amapConfigured ? '高德已配置' : '高德未配置'}`,
`车辆覆盖:${sourceLines.length > 0 ? sourceLines.join('') : '当前页暂无来源明细'}`,
`车辆覆盖:${sourceLines.length > 0 ? sourceLines.join('') : '当前页暂无数据通道明细'}`,
`实时入口:${appURL(buildAppHash({ page: 'realtime', protocol: filters.protocol, filters }))}`,
''
];
@@ -385,12 +388,12 @@ function realtimeDutyHandoffText({
lines.push(
`${index + 1}. ${row.plate || '-'} / ${row.vin} / ${protocol || '-'} / ${status.label}`,
` 最后上报:${row.lastSeen || '-'};新鲜度:${freshness.label};位置:${isValidCoordinate(row) ? `${row.longitude},${row.latitude}` : '无有效坐标'};速度:${row.speedKmh ?? '-'} km/hSOC${row.socPercent ?? '-'}%`,
` 数据覆盖${sourceEvidenceText(row)};问题:${realtimeIssueLabels(row).join('')}`,
` 数据通道${sourceEvidenceText(row)};问题:${realtimeIssueLabels(row).join('')}`,
` 车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol }))}`,
` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol }))}`,
` 轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol }))}`,
` 里程统计:${appURL(buildAppHash({ page: 'mileage', keyword: row.vin, protocol }))}`,
` 原始记录${protocol ? rawFrameAPIURL(row, protocol) : '-'}`
` 历史查询${protocol ? rawFrameAPIURL(row, protocol) : '-'}`
);
});
return lines.join('\n');
@@ -431,7 +434,7 @@ function realtimeImpactReportText({
`定位影响:${locatedCount.toLocaleString()} 辆有有效坐标`,
`服务影响:${degradedCount.toLocaleString()} 辆降级 / ${staleCount.toLocaleString()} 辆超时`,
`地图能力:${amapConfigured ? '高德地图可用' : '高德地图未配置,使用坐标预览'}`,
`建议动作:${impactLevel === '实时稳定' ? '保持监控并关注告警队列' : '优先处理离线、超时和来源不完整车辆,并回到轨迹和原始记录复核'}`,
`建议动作:${impactLevel === '实时稳定' ? '保持监控并关注告警队列' : '优先处理离线、超时和数据通道不完整车辆,并回到轨迹和历史查询复核'}`,
`实时监控:${appURL(buildAppHash({ page: 'realtime', protocol: filters.protocol, filters }))}`
].join('\n');
}
@@ -517,7 +520,7 @@ function mapCustomerDecisionText({
`在线车辆:${appURL(buildAppHash({ page: 'map', protocol: filters.protocol, filters: { ...filters, online: 'online' } }))}`,
selectedVIN ? `车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: selectedVIN, protocol: selectedProtocol }))}` : '',
selectedVIN ? `轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: selectedVIN, protocol: selectedProtocol }))}` : '',
selectedVIN ? `数据导出:${appURL(buildAppHash({ page: 'history-query', keyword: selectedVIN, protocol: selectedProtocol, filters: { tab: 'raw', includeFields: 'true' } }))}` : ''
selectedVIN ? `历史查询导出:${appURL(buildAppHash({ page: 'history-query', keyword: selectedVIN, protocol: selectedProtocol, filters: { tab: 'raw', includeFields: 'true' } }))}` : ''
].filter(Boolean).join('\n');
}
@@ -558,7 +561,7 @@ async function copyText(value: string, label: string) {
export function Realtime({
mode = 'realtime',
title = '实时监控',
description = '以车辆为主对象查看最新位置、在线来源、核心实时数据和地图作业状态',
description = '以车辆为主对象查看最新位置、在线状态、核心实时数据和地图作业状态',
onOpenVehicle,
onOpenHistory,
onOpenQuality,
@@ -834,7 +837,7 @@ export function Realtime({
{
title: '车辆服务',
value: selectedVehicleLabel,
detail: selectedMapRow ? '查看车辆画像、来源状态和最新实时字段。' : '先从地图或车辆列表选择一辆车。',
detail: selectedMapRow ? '查看车辆画像、数据通道状态和最新实时字段。' : '先从地图或车辆列表选择一辆车。',
action: '车辆档案',
color: selectedMapRow ? vehicleServiceStatus(selectedMapRow).color : 'grey' as const,
disabled: !selectedMapRow || !canOpenVehicle(selectedMapRow.vin),
@@ -918,10 +921,10 @@ export function Realtime({
{
title: '查询导出',
value: `${rows.length.toLocaleString()} 辆当前页`,
detail: '导出当前车辆清单,或进入选中车辆的历史记录。',
detail: '导出当前车辆清单,或进入选中车辆的历史查询。',
color: 'blue' as const,
primaryAction: '导出当前页',
secondaryAction: '历史记录',
secondaryAction: '历史查询',
onPrimary: exportRealtime,
onSecondary: openSelectedVehicleRaw,
disabled: false,
@@ -1235,7 +1238,7 @@ export function Realtime({
<>
<Tag color={vehicleServiceStatus(selectedMapRow).color}>{vehicleServiceStatus(selectedMapRow).label}</Tag>
<Tag color={dataFreshness(selectedMapRow).color}>{dataFreshness(selectedMapRow).detail}</Tag>
<Tag color="blue">{selectedVehicleProtocol || '未知来源'}</Tag>
<Tag color="blue">{selectedVehicleProtocol || '未知通道'}</Tag>
</>
) : (
<Tag color="grey"></Tag>
@@ -1293,7 +1296,7 @@ export function Realtime({
<Space wrap>
<Tag color={vehicleServiceStatus(selectedMapRow).color}>{vehicleServiceStatus(selectedMapRow).label}</Tag>
<Tag color={dataFreshness(selectedMapRow).color}>{dataFreshness(selectedMapRow).detail}</Tag>
<Tag color="blue">{selectedMapRow.primaryProtocol || '未知来源'}</Tag>
<Tag color="blue">{selectedMapRow.primaryProtocol || '未知通道'}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: '8px 0 0' }}>
{selectedMapRow.plate || selectedMapRow.vin}
@@ -1411,7 +1414,7 @@ export function Realtime({
</Form.Select>
<Form.Select field="serviceStatus" label="服务状态" placeholder="全部" style={{ width: 150 }}>
<Select.Option value="healthy"></Select.Option>
<Select.Option value="degraded"></Select.Option>
<Select.Option value="degraded"></Select.Option>
<Select.Option value="offline">线</Select.Option>
<Select.Option value="identity_required"></Select.Option>
</Form.Select>
@@ -1724,7 +1727,7 @@ export function Realtime({
{ label: '在线率', value: formatPercent(onlineRate), color: onlineRate >= 80 ? 'green' as const : 'orange' as const, detail: `${onlineCount.toLocaleString()} / ${rows.length.toLocaleString()} 辆在线。` },
{ label: '定位有效率', value: formatPercent(locatedRate), color: locatedRate >= 80 ? 'green' as const : 'orange' as const, detail: `${locatedCount.toLocaleString()} / ${rows.length.toLocaleString()} 辆坐标有效。` },
{ label: '新鲜数据', value: `${(rows.length - staleCount).toLocaleString()}`, color: staleCount > 0 ? 'orange' as const : 'green' as const, detail: `${staleCount.toLocaleString()} 辆超过 5 分钟未更新。` },
{ label: '降级率', value: formatPercent(degradedRate), color: degradedCount > 0 ? 'orange' as const : 'green' as const, detail: `${degradedCount.toLocaleString()} 辆存在来源不完整或离线。` },
{ label: '降级率', value: formatPercent(degradedRate), color: degradedCount > 0 ? 'orange' as const : 'green' as const, detail: `${degradedCount.toLocaleString()} 辆存在数据通道不完整或离线。` },
{ label: '分页覆盖率', value: formatPercent(pageCoverageRate), color: pageCoverageRate >= 100 ? 'green' as const : 'grey' as const, detail: '当前页车辆数 / 当前筛选总车辆数。' }
].map((item) => (
<Card key={item.label} bordered>

View File

@@ -8992,7 +8992,7 @@ test('shows canonical service status in realtime vehicles', async () => {
render(<App />);
expect((await screen.findAllByText('VIN-RT-DEGRADED')).length).toBeGreaterThan(0);
expect(screen.getAllByText('来源不完整').length).toBeGreaterThan(0);
expect(screen.getAllByText('数据通道不完整').length).toBeGreaterThan(0);
});
test('frames realtime page as one vehicle service with source evidence', async () => {
@@ -9535,7 +9535,7 @@ test('copies realtime operations summary from realtime page', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('实时入口http://localhost:3000/#/realtime?protocol=JT808&online=online'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务http://localhost:3000/#/detail?keyword=VIN-RT-SUMMARY-003&protocol=JT808'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('里程统计http://localhost:3000/#/mileage?keyword=VIN-RT-SUMMARY-003&protocol=JT808'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('原始记录http://localhost:3000/api/history/raw-frames?protocol=JT808&vin=VIN-RT-SUMMARY-003&limit=20&includeFields=true'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('历史查询http://localhost:3000/api/history/raw-frames?protocol=JT808&vin=VIN-RT-SUMMARY-003&limit=20&includeFields=true'));
fireEvent.click(screen.getByRole('button', { name: '实时筛选 需要关注 2' }));
expect(window.location.hash).toBe('#/realtime?protocol=JT808&serviceStatus=degraded&online=online');
fireEvent.click(screen.getByRole('button', { name: '筛选数据通道 GB32960' }));
@@ -10323,7 +10323,7 @@ test('shows and clears current realtime service filters', async () => {
expect(screen.getByText('关键词粤ART002')).toBeInTheDocument();
expect(screen.getAllByText('数据通道JT808').length).toBeGreaterThan(0);
expect(screen.getByText('在线:在线')).toBeInTheDocument();
expect(screen.getByText('服务状态:来源不完整')).toBeInTheDocument();
expect(screen.getByText('服务状态:数据通道不完整')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '清空筛选' }));
await waitFor(() => {