feat(platform): customerize realtime monitoring view

This commit is contained in:
lingniu
2026-07-05 00:58:16 +08:00
parent 245af2c293
commit b824289cf6
2 changed files with 44 additions and 44 deletions

View File

@@ -34,7 +34,7 @@ function vehicleServiceStatus(row: VehicleRealtimeRow) {
} }
function sourceEvidenceText(row: VehicleRealtimeRow) { function sourceEvidenceText(row: VehicleRealtimeRow) {
return `${row.onlineSourceCount}/${row.sourceCount} 来源在线`; return `${row.onlineSourceCount}/${row.sourceCount} 通道在线`;
} }
function formatPercent(value: number) { function formatPercent(value: number) {
@@ -154,11 +154,11 @@ const realtimeExportColumns: CsvColumn<VehicleRealtimeRow>[] = [
{ title: '车牌', value: (row) => row.plate }, { title: '车牌', value: (row) => row.plate },
{ title: '手机号', value: (row) => row.phone }, { title: '手机号', value: (row) => row.phone },
{ title: 'OEM', value: (row) => row.oem }, { title: 'OEM', value: (row) => row.oem },
{ title: '主来源', value: (row) => row.primaryProtocol }, { title: '主通道', value: (row) => row.primaryProtocol },
{ title: '来源列表', value: (row) => row.protocols?.join('|') }, { title: '数据通道', value: (row) => row.protocols?.join('|') },
{ title: '在线', value: (row) => row.online ? '在线' : '离线' }, { title: '在线', value: (row) => row.online ? '在线' : '离线' },
{ title: '车辆服务状态', value: (row) => vehicleServiceStatus(row).label }, { title: '车辆服务状态', value: (row) => vehicleServiceStatus(row).label },
{ title: '来源在线', value: (row) => sourceEvidenceText(row) }, { title: '通道在线', value: (row) => sourceEvidenceText(row) },
{ title: '经度', value: (row) => row.longitude }, { title: '经度', value: (row) => row.longitude },
{ title: '纬度', value: (row) => row.latitude }, { title: '纬度', value: (row) => row.latitude },
{ title: '速度km/h', value: (row) => row.speedKmh }, { title: '速度km/h', value: (row) => row.speedKmh },
@@ -187,7 +187,7 @@ function realtimeExportFileName(filters: Record<string, string>) {
function realtimeFilterSummary(filters: Record<string, string>) { function realtimeFilterSummary(filters: Record<string, string>) {
return [ return [
filters.keyword ? `关键词:${filters.keyword}` : '', filters.keyword ? `关键词:${filters.keyword}` : '',
filters.protocol ? `数据来源${filters.protocol}` : '', filters.protocol ? `数据通道${filters.protocol}` : '',
filters.online ? `在线:${onlineLabel[filters.online] ?? filters.online}` : '', filters.online ? `在线:${onlineLabel[filters.online] ?? filters.online}` : '',
filters.serviceStatus ? `服务状态:${serviceStatusLabel[filters.serviceStatus] ?? filters.serviceStatus}` : '' filters.serviceStatus ? `服务状态:${serviceStatusLabel[filters.serviceStatus] ?? filters.serviceStatus}` : ''
].filter(Boolean); ].filter(Boolean);
@@ -265,13 +265,13 @@ function realtimeIssueLabels(row: VehicleRealtimeRow) {
function realtimeIssueAction(row: VehicleRealtimeRow) { function realtimeIssueAction(row: VehicleRealtimeRow) {
const issues = realtimeIssueLabels(row).join(''); const issues = realtimeIssueLabels(row).join('');
if (!isValidCoordinate(row)) { if (!isValidCoordinate(row)) {
return `核对${row.primaryProtocol || '实时来源'}定位字段解析和平台转发`; return `核对${row.primaryProtocol || '实时数据'}定位字段解析和平台转发`;
} }
if (dataFreshness(row).stale) { if (dataFreshness(row).stale) {
return `确认${row.primaryProtocol || '实时来源'}是否持续上报,必要时检查接入链路`; return `确认${row.primaryProtocol || '实时数据'}是否持续上报,必要时查看告警事件`;
} }
if (hasSourceIssue(row)) { if (hasSourceIssue(row)) {
return '补齐离线来源,确认多来源实时状态一致'; return '处理离线数据通道,确认车辆实时状态一致';
} }
return issues === '暂无异常' ? '持续观察' : '结合车辆服务详情继续排查'; return issues === '暂无异常' ? '持续观察' : '结合车辆服务详情继续排查';
} }
@@ -385,12 +385,12 @@ function realtimeDutyHandoffText({
lines.push( lines.push(
`${index + 1}. ${row.plate || '-'} / ${row.vin} / ${protocol || '-'} / ${status.label}`, `${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 ?? '-'}%`, ` 最后上报:${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: 'detail', keyword: row.vin, protocol }))}`,
` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol }))}`, ` 实时监控:${appURL(buildAppHash({ page: 'realtime', keyword: row.vin, protocol }))}`,
` 轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol }))}`, ` 轨迹回放:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol }))}`,
` 里程统计:${appURL(buildAppHash({ page: 'mileage', keyword: row.vin, protocol }))}`, ` 里程统计:${appURL(buildAppHash({ page: 'mileage', keyword: row.vin, protocol }))}`,
` 历史RAW${protocol ? rawFrameAPIURL(row, protocol) : '-'}` ` 原始记录${protocol ? rawFrameAPIURL(row, protocol) : '-'}`
); );
}); });
return lines.join('\n'); return lines.join('\n');
@@ -431,7 +431,7 @@ function realtimeImpactReportText({
`定位影响:${locatedCount.toLocaleString()} 辆有有效坐标`, `定位影响:${locatedCount.toLocaleString()} 辆有有效坐标`,
`服务影响:${degradedCount.toLocaleString()} 辆降级 / ${staleCount.toLocaleString()} 辆超时`, `服务影响:${degradedCount.toLocaleString()} 辆降级 / ${staleCount.toLocaleString()} 辆超时`,
`地图能力:${amapConfigured ? '高德地图可用' : '高德地图未配置,使用坐标预览'}`, `地图能力:${amapConfigured ? '高德地图可用' : '高德地图未配置,使用坐标预览'}`,
`建议动作:${impactLevel === '实时稳定' ? '保持监控并关注告警队列' : '优先处理离线、超时和来源不完整车辆,并回到轨迹/RAW证据复核'}`, `建议动作:${impactLevel === '实时稳定' ? '保持监控并关注告警队列' : '优先处理离线、超时和来源不完整车辆,并回到轨迹和原始记录复核'}`,
`实时监控:${appURL(buildAppHash({ page: 'realtime', protocol: filters.protocol, filters }))}` `实时监控:${appURL(buildAppHash({ page: 'realtime', protocol: filters.protocol, filters }))}`
].join('\n'); ].join('\n');
} }
@@ -728,7 +728,7 @@ export function Realtime({
applyFilters(nextFilters); applyFilters(nextFilters);
}} style={{ marginBottom: 12 }}> }} style={{ marginBottom: 12 }}>
<Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号 / OEM" style={{ width: 260 }} /> <Form.Input field="keyword" label="车辆关键词" placeholder="VIN / 车牌 / 手机号 / OEM" style={{ width: 260 }} />
<Form.Select field="protocol" label="数据来源" placeholder="全部来源" style={{ width: 180 }}> <Form.Select field="protocol" label="数据通道" placeholder="全部数据通道" style={{ width: 180 }}>
<Select.Option value="GB32960">GB32960</Select.Option> <Select.Option value="GB32960">GB32960</Select.Option>
<Select.Option value="JT808">JT808</Select.Option> <Select.Option value="JT808">JT808</Select.Option>
<Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option> <Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option>
@@ -815,7 +815,7 @@ export function Realtime({
{staleCount > 0 ? <Tag color="red"> {staleCount.toLocaleString()}</Tag> : <Tag color="green"></Tag>} {staleCount > 0 ? <Tag color="red"> {staleCount.toLocaleString()}</Tag> : <Tag color="green"></Tag>}
{sourceIssueRows.length > 0 && onOpenQuality ? ( {sourceIssueRows.length > 0 && onOpenQuality ? (
<Button size="small" theme="light" type="warning" onClick={() => onOpenQuality({ serviceStatus: 'degraded' })}> <Button size="small" theme="light" type="warning" onClick={() => onOpenQuality({ serviceStatus: 'degraded' })}>
{sourceIssueRows.length.toLocaleString()} {sourceIssueRows.length.toLocaleString()}
</Button> </Button>
) : null} ) : null}
</div> </div>
@@ -825,7 +825,7 @@ export function Realtime({
<div className="vp-current-service-title"></div> <div className="vp-current-service-title"></div>
<div className="vp-source-coverage-grid"> <div className="vp-source-coverage-grid">
{sourceCoverageRows.length === 0 ? ( {sourceCoverageRows.length === 0 ? (
<Typography.Text type="tertiary"></Typography.Text> <Typography.Text type="tertiary"></Typography.Text>
) : sourceCoverageRows.map((item) => ( ) : sourceCoverageRows.map((item) => (
<button <button
key={item.protocol} key={item.protocol}
@@ -894,7 +894,7 @@ export function Realtime({
<Tag color={vehicleServiceStatus(selectedMapRow).color}>{vehicleServiceStatus(selectedMapRow).label}</Tag> <Tag color={vehicleServiceStatus(selectedMapRow).color}>{vehicleServiceStatus(selectedMapRow).label}</Tag>
<Tag color={dataFreshness(selectedMapRow).color}>{dataFreshness(selectedMapRow).label}</Tag> <Tag color={dataFreshness(selectedMapRow).color}>{dataFreshness(selectedMapRow).label}</Tag>
<Tag color={selectedMapRow.online ? 'green' : 'orange'}>{selectedMapRow.online ? '在线' : '离线'}</Tag> <Tag color={selectedMapRow.online ? 'green' : 'orange'}>{selectedMapRow.online ? '在线' : '离线'}</Tag>
<Tag color="blue">{selectedMapRow.primaryProtocol || '未知来源'}</Tag> <Tag color="blue">{selectedMapRow.primaryProtocol || '未知通道'}</Tag>
</Space> </Space>
<Typography.Title heading={6} style={{ margin: '8px 0 0' }}> <Typography.Title heading={6} style={{ margin: '8px 0 0' }}>
{selectedMapRow.plate || selectedMapRow.vin} {selectedMapRow.plate || selectedMapRow.vin}
@@ -915,7 +915,7 @@ export function Realtime({
</div> </div>
) : null} ) : null}
<div className="vp-map-integration-panel"> <div className="vp-map-integration-panel">
<div className="vp-map-service-queue-title"></div> <div className="vp-map-service-queue-title"></div>
{mapIntegrationRows.map((item) => ( {mapIntegrationRows.map((item) => (
<div key={item.label} className="vp-map-integration-row"> <div key={item.label} className="vp-map-integration-row">
<div> <div>
@@ -927,9 +927,9 @@ export function Realtime({
))} ))}
</div> </div>
<div className="vp-source-consistency-panel"> <div className="vp-source-consistency-panel">
<div className="vp-map-service-queue-title"></div> <div className="vp-map-service-queue-title"></div>
{sourceIssueRows.length === 0 ? ( {sourceIssueRows.length === 0 ? (
<Typography.Text type="tertiary"></Typography.Text> <Typography.Text type="tertiary"></Typography.Text>
) : ( ) : (
sourceIssueRows.map((row) => { sourceIssueRows.map((row) => {
const status = vehicleServiceStatus(row); const status = vehicleServiceStatus(row);
@@ -946,16 +946,16 @@ export function Realtime({
<Tag color="blue">{sourceEvidenceText(row)}</Tag> <Tag color="blue">{sourceEvidenceText(row)}</Tag>
<Tag color={dataFreshness(row).color}>{dataFreshness(row).label}</Tag> <Tag color={dataFreshness(row).color}>{dataFreshness(row).label}</Tag>
{sourceIssueTags(row).map((item) => ( {sourceIssueTags(row).map((item) => (
<Tag key={item} color="orange">{item}</Tag> <Tag key={item} color="orange">{item}</Tag>
))} ))}
</Space> </Space>
<Typography.Text type="secondary" size="small"> <Typography.Text type="secondary" size="small">
{row.serviceStatus?.detail || sourceEvidenceText(row)} {row.serviceStatus?.detail || sourceEvidenceText(row)}
</Typography.Text> </Typography.Text>
<div className="vp-map-service-item-footer"> <div className="vp-map-service-item-footer">
<Typography.Text type="tertiary" size="small">{row.lastSeen || '-'}</Typography.Text> <Typography.Text type="tertiary" size="small">{row.lastSeen || '-'}</Typography.Text>
<Space spacing={4} wrap> <Space spacing={4} wrap>
<Button size="small" disabled={!onOpenQuality} onClick={() => openQualityEvidence(row)}></Button> <Button size="small" disabled={!onOpenQuality} onClick={() => openQualityEvidence(row)}></Button>
<Button size="small" onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button> <Button size="small" onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button>
</Space> </Space>
</div> </div>
@@ -965,7 +965,7 @@ export function Realtime({
)} )}
</div> </div>
<div className="vp-map-service-queue"> <div className="vp-map-service-queue">
<div className="vp-map-service-queue-title"></div> <div className="vp-map-service-queue-title"></div>
{mapServiceRows.length === 0 ? ( {mapServiceRows.length === 0 ? (
<Typography.Text type="tertiary"></Typography.Text> <Typography.Text type="tertiary"></Typography.Text>
) : ( ) : (
@@ -1006,8 +1006,8 @@ export function Realtime({
<Space spacing={4} wrap> <Space spacing={4} wrap>
<Button size="small" onClick={() => window.open(amapMarkerURL(row), '_blank', 'noopener,noreferrer')}></Button> <Button size="small" onClick={() => window.open(amapMarkerURL(row), '_blank', 'noopener,noreferrer')}></Button>
<Button size="small" onClick={() => onOpenHistory?.(row.vin, filters.protocol || row.primaryProtocol)}></Button> <Button size="small" onClick={() => onOpenHistory?.(row.vin, filters.protocol || row.primaryProtocol)}></Button>
<Button size="small" disabled={!onOpenQuality} onClick={() => openQualityEvidence(row)}></Button> <Button size="small" disabled={!onOpenQuality} onClick={() => openQualityEvidence(row)}></Button>
<Button size="small" onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button> <Button size="small" onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button>
</Space> </Space>
</div> </div>
</div> </div>
@@ -1117,13 +1117,13 @@ export function Realtime({
) )
}, },
{ {
title: '来源证据', title: '实时通道',
width: 260, width: 260,
render: (_: unknown, row: VehicleRealtimeRow) => ( render: (_: unknown, row: VehicleRealtimeRow) => (
<SourceStatusTags sourceStatus={row.sourceStatus} protocols={row.protocols} lastSeen={row.lastSeen} /> <SourceStatusTags sourceStatus={row.sourceStatus} protocols={row.protocols} lastSeen={row.lastSeen} />
) )
}, },
{ title: '据覆盖', width: 120, render: (_: unknown, row: VehicleRealtimeRow) => sourceEvidenceText(row) }, { title: '据覆盖', width: 120, render: (_: unknown, row: VehicleRealtimeRow) => sourceEvidenceText(row) },
{ title: '在线', width: 90, render: (_: unknown, row: VehicleRealtimeRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> }, { title: '在线', width: 90, render: (_: unknown, row: VehicleRealtimeRow) => <StatusTag status={row.online ? 'ok' : 'offline'} /> },
{ title: '最后时间', dataIndex: 'lastSeen', width: 170 }, { title: '最后时间', dataIndex: 'lastSeen', width: 170 },
{ {
@@ -1133,7 +1133,7 @@ export function Realtime({
<Space spacing={4} wrap> <Space spacing={4} wrap>
<Button disabled={!isValidCoordinate(row)} onClick={() => selectRealtimeRow(row)}></Button> <Button disabled={!isValidCoordinate(row)} onClick={() => selectRealtimeRow(row)}></Button>
<Button disabled={!canOpenVehicle(row.vin)} onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button> <Button disabled={!canOpenVehicle(row.vin)} onClick={() => onOpenVehicle(row.vin, filters.protocol || row.primaryProtocol)}></Button>
<Button disabled={!canOpenVehicle(row.vin) || !onOpenQuality} onClick={() => openQualityEvidence(row)}></Button> <Button disabled={!canOpenVehicle(row.vin) || !onOpenQuality} onClick={() => openQualityEvidence(row)}></Button>
</Space> </Space>
) )
} }

View File

@@ -192,7 +192,7 @@ test('exposes AMap operations shortcuts when map key is configured', async () =>
fireEvent.click(screen.getByRole('button', { name: '顶部地图态势' })); fireEvent.click(screen.getByRole('button', { name: '顶部地图态势' }));
expect(window.location.hash).toBe('#/map'); expect(window.location.hash).toBe('#/map');
expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument(); expect(await screen.findByRole('heading', { name: '实时地图' })).toBeInTheDocument();
expect(screen.getByText('地图车辆服务队列')).toBeInTheDocument(); expect(screen.getByText('地图车辆队列')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '顶部实时监控' })); fireEvent.click(screen.getByRole('button', { name: '顶部实时监控' }));
expect(window.location.hash).toBe('#/realtime'); expect(window.location.hash).toBe('#/realtime');
fireEvent.click(screen.getByRole('button', { name: '顶部轨迹回放' })); fireEvent.click(screen.getByRole('button', { name: '顶部轨迹回放' }));
@@ -8791,11 +8791,11 @@ test('frames realtime page as one vehicle service with source evidence', async (
expect(screen.getByText('高德地图待配置')).toBeInTheDocument(); expect(screen.getByText('高德地图待配置')).toBeInTheDocument();
expect(screen.getAllByText('定位有效').length).toBeGreaterThan(0); expect(screen.getAllByText('定位有效').length).toBeGreaterThan(0);
expect(screen.getByText('车辆核心数据')).toBeInTheDocument(); expect(screen.getByText('车辆核心数据')).toBeInTheDocument();
expect(screen.getByText('来源证据')).toBeInTheDocument(); expect(screen.getByText('实时通道')).toBeInTheDocument();
expect(screen.getByText('GB32960 在线')).toBeInTheDocument(); expect(screen.getByText('GB32960 在线')).toBeInTheDocument();
expect(screen.getByText('JT808 在线')).toBeInTheDocument(); expect(screen.getByText('JT808 在线')).toBeInTheDocument();
expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument(); expect(screen.getByText('YUTONG_MQTT 未接入')).toBeInTheDocument();
expect(screen.getAllByText('2/2 来源在线').length).toBeGreaterThan(0); expect(screen.getAllByText('2/2 通道在线').length).toBeGreaterThan(0);
expect(screen.getByText('实时运营影响')).toBeInTheDocument(); expect(screen.getByText('实时运营影响')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /复制影响报告/ })).toBeInTheDocument(); expect(screen.getByRole('button', { name: /复制影响报告/ })).toBeInTheDocument();
expect(screen.getByText('需要处置')).toBeInTheDocument(); expect(screen.getByText('需要处置')).toBeInTheDocument();
@@ -9177,7 +9177,7 @@ test('copies realtime operations summary from realtime page', async () => {
fireEvent.click(screen.getByRole('button', { name: '复制实时摘要' })); fireEvent.click(screen.getByRole('button', { name: '复制实时摘要' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时监控摘要】')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时监控摘要】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前筛选:数据来源JT808在线在线')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前筛选:数据通道JT808在线在线'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆总数3当前页3')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆总数3当前页3'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线车辆2定位有效2')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线车辆2定位有效2'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('需要关注2数据通道3')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('需要关注2数据通道3'));
@@ -9187,7 +9187,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/#/realtime?protocol=JT808&online=online'));
fireEvent.click(screen.getByRole('button', { name: '复制实时异常处置清单' })); fireEvent.click(screen.getByRole('button', { name: '复制实时异常处置清单' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时异常处置清单】')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【实时异常处置清单】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前筛选:数据来源JT808在线在线')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前筛选:数据通道JT808在线在线'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('异常车辆3 / 当前页 3 / 总计 3')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('异常车辆3 / 当前页 3 / 总计 3'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('粤A摘要3 / VIN-RT-SUMMARY-003 / JT808')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('粤A摘要3 / VIN-RT-SUMMARY-003 / JT808'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('状态:车辆离线;在线:离线;问题:')); expect(writeText).toHaveBeenCalledWith(expect.stringContaining('状态:车辆离线;在线:离线;问题:'));
@@ -9205,7 +9205,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/#/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/#/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/#/mileage?keyword=VIN-RT-SUMMARY-003&protocol=JT808'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('历史RAWhttp://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' })); fireEvent.click(screen.getByRole('button', { name: '实时筛选 需要关注 2' }));
expect(window.location.hash).toBe('#/realtime?protocol=JT808&serviceStatus=degraded&online=online'); expect(window.location.hash).toBe('#/realtime?protocol=JT808&serviceStatus=degraded&online=online');
fireEvent.click(screen.getByRole('button', { name: '筛选数据通道 GB32960' })); fireEvent.click(screen.getByRole('button', { name: '筛选数据通道 GB32960' }));
@@ -9313,7 +9313,7 @@ test('shows production AMap integration status on realtime page', async () => {
render(<App />); render(<App />);
expect(await screen.findByText('地图能力状态')).toBeInTheDocument(); expect(await screen.findByText('地图展示状态')).toBeInTheDocument();
expect(screen.getByText('Web JS Key')).toBeInTheDocument(); expect(screen.getByText('Web JS Key')).toBeInTheDocument();
expect(screen.getByText('服务端 API Key')).toBeInTheDocument(); expect(screen.getByText('服务端 API Key')).toBeInTheDocument();
expect(screen.getAllByText('已配置').length).toBeGreaterThan(0); expect(screen.getAllByText('已配置').length).toBeGreaterThan(0);
@@ -9508,10 +9508,10 @@ test('opens vehicle service from realtime map service queue', async () => {
render(<App />); render(<App />);
expect(await screen.findByText('地图车辆服务队列')).toBeInTheDocument(); expect(await screen.findByText('地图车辆队列')).toBeInTheDocument();
expect(screen.getAllByText('粤AMAP01').length).toBeGreaterThan(0); expect(screen.getAllByText('粤AMAP01').length).toBeGreaterThan(0);
expect(screen.getByText('JT808 离线GB32960 仍可支撑车辆服务')).toBeInTheDocument(); expect(screen.getByText('JT808 离线GB32960 仍可支撑车辆服务')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '地图车辆服务' })); fireEvent.click(screen.getByRole('button', { name: '车辆详情' }));
await waitFor(() => { await waitFor(() => {
expect(window.location.hash).toBe('#/detail?keyword=VIN-MAP-001&protocol=GB32960'); expect(window.location.hash).toBe('#/detail?keyword=VIN-MAP-001&protocol=GB32960');
@@ -9579,7 +9579,7 @@ test('opens amap coordinate and trajectory playback from realtime map service qu
render(<App />); render(<App />);
expect(await screen.findByText('地图车辆服务队列')).toBeInTheDocument(); expect(await screen.findByText('地图车辆队列')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '高德坐标' })); fireEvent.click(screen.getByRole('button', { name: '高德坐标' }));
expect(openSpy).toHaveBeenCalledWith( expect(openSpy).toHaveBeenCalledWith(
expect.stringContaining('https://uri.amap.com/marker?position=113.24567,23.12345'), expect.stringContaining('https://uri.amap.com/marker?position=113.24567,23.12345'),
@@ -9682,11 +9682,11 @@ test('surfaces multi-source realtime consistency issues with quality drilldown',
render(<App />); render(<App />);
expect(await screen.findByText('车辆数据一致性检查')).toBeInTheDocument(); expect(await screen.findByText('需关注车辆')).toBeInTheDocument();
expect(screen.getAllByText('粤A一致监控').length).toBeGreaterThan(0); expect(screen.getAllByText('粤A一致监控').length).toBeGreaterThan(0);
expect(screen.getByText('一致性诊断YUTONG_MQTT 离线GB32960/JT808 仍可支撑车辆服务')).toBeInTheDocument(); expect(screen.getByText('处置说明YUTONG_MQTT 离线GB32960/JT808 仍可支撑车辆服务')).toBeInTheDocument();
expect(screen.getByText('一致性YUTONG_MQTT 离线')).toBeInTheDocument(); expect(screen.getByText('问题YUTONG_MQTT 离线')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '检查质量' })); fireEvent.click(screen.getByRole('button', { name: '查看告警' }));
await waitFor(() => { await waitFor(() => {
expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-CONSISTENCY&protocol=GB32960'); expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-CONSISTENCY&protocol=GB32960');
@@ -9816,7 +9816,7 @@ test('opens quality issues from realtime vehicle row with source evidence', asyn
render(<App />); render(<App />);
expect((await screen.findAllByText('VIN-RT-QUALITY')).length).toBeGreaterThan(0); expect((await screen.findAllByText('VIN-RT-QUALITY')).length).toBeGreaterThan(0);
fireEvent.click(screen.getAllByRole('button', { name: '质量问题' })[0]); fireEvent.click(screen.getAllByRole('button', { name: '告警事件' })[0]);
await waitFor(() => { await waitFor(() => {
expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-QUALITY&protocol=JT808'); expect(window.location.hash).toBe('#/alert-events?keyword=VIN-RT-QUALITY&protocol=JT808');
@@ -9991,7 +9991,7 @@ test('shows and clears current realtime service filters', async () => {
expect(await screen.findByText('当前实时筛选')).toBeInTheDocument(); expect(await screen.findByText('当前实时筛选')).toBeInTheDocument();
expect(screen.getByText('关键词粤ART002')).toBeInTheDocument(); expect(screen.getByText('关键词粤ART002')).toBeInTheDocument();
expect(screen.getByText('数据来源JT808')).toBeInTheDocument(); expect(screen.getByText('数据通道JT808')).toBeInTheDocument();
expect(screen.getByText('在线:在线')).toBeInTheDocument(); expect(screen.getByText('在线:在线')).toBeInTheDocument();
expect(screen.getByText('服务状态:来源不完整')).toBeInTheDocument(); expect(screen.getByText('服务状态:来源不完整')).toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: '清空筛选' })); fireEvent.click(screen.getByRole('button', { name: '清空筛选' }));
@@ -10030,7 +10030,7 @@ test('updates realtime hash when source filters are submitted', async () => {
await screen.findByRole('heading', { name: '实时监控' }); await screen.findByRole('heading', { name: '实时监控' });
fireEvent.change(screen.getByPlaceholderText('VIN / 车牌 / 手机号 / OEM'), { target: { value: '粤ART002' } }); fireEvent.change(screen.getByPlaceholderText('VIN / 车牌 / 手机号 / OEM'), { target: { value: '粤ART002' } });
fireEvent.click(screen.getByText('全部来源')); fireEvent.click(screen.getByText('全部数据通道'));
fireEvent.click(await screen.findByText('JT808')); fireEvent.click(await screen.findByText('JT808'));
fireEvent.click(screen.getByRole('button', { name: '查询' })); fireEvent.click(screen.getByRole('button', { name: '查询' }));