feat(platform): refine realtime vehicle monitoring workflow
This commit is contained in:
@@ -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/h;SOC:${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>
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user