feat(platform): refine vehicle detail operations view
This commit is contained in:
@@ -21,7 +21,7 @@ type VehicleQuery = {
|
|||||||
const sourceCapabilities: Array<{ key: keyof Pick<VehicleSourceStatus, 'hasRealtime' | 'hasHistory' | 'hasRaw' | 'hasMileage'>; label: string }> = [
|
const sourceCapabilities: Array<{ key: keyof Pick<VehicleSourceStatus, 'hasRealtime' | 'hasHistory' | 'hasRaw' | 'hasMileage'>; label: string }> = [
|
||||||
{ key: 'hasRealtime', label: '实时' },
|
{ key: 'hasRealtime', label: '实时' },
|
||||||
{ key: 'hasHistory', label: '历史' },
|
{ key: 'hasHistory', label: '历史' },
|
||||||
{ key: 'hasRaw', label: 'RAW' },
|
{ key: 'hasRaw', label: '原始记录' },
|
||||||
{ key: 'hasMileage', label: '里程' }
|
{ key: 'hasMileage', label: '里程' }
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ function sourceFusionRow(source: VehicleSourceStatus, realtime: RealtimeLocation
|
|||||||
source.hasRealtime || realtime ? '实时字段' : '',
|
source.hasRealtime || realtime ? '实时字段' : '',
|
||||||
hasLocation ? '定位证据' : '',
|
hasLocation ? '定位证据' : '',
|
||||||
source.hasHistory ? '轨迹回放' : '',
|
source.hasHistory ? '轨迹回放' : '',
|
||||||
source.hasRaw ? 'RAW证据' : '',
|
source.hasRaw ? '原始记录' : '',
|
||||||
hasMileage ? '统计口径' : ''
|
hasMileage ? '统计口径' : ''
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
return {
|
return {
|
||||||
@@ -85,7 +85,7 @@ function sourceFusionRow(source: VehicleSourceStatus, realtime: RealtimeLocation
|
|||||||
freshness: source.online ? `在线 / ${source.lastSeen || realtime?.lastSeen || '-'}` : source.lastSeen ? `离线 / ${source.lastSeen}` : '未接入',
|
freshness: source.online ? `在线 / ${source.lastSeen || realtime?.lastSeen || '-'}` : source.lastSeen ? `离线 / ${source.lastSeen}` : '未接入',
|
||||||
realtimeText: source.hasRealtime || realtime ? `速度 ${formatCompactNumber(realtime?.speedKmh, ' km/h')} / SOC ${formatCompactNumber(realtime?.socPercent, '%')}` : '无实时字段',
|
realtimeText: source.hasRealtime || realtime ? `速度 ${formatCompactNumber(realtime?.speedKmh, ' km/h')} / SOC ${formatCompactNumber(realtime?.socPercent, '%')}` : '无实时字段',
|
||||||
locationText: hasLocation ? `${realtime?.longitude}, ${realtime?.latitude}` : '无有效坐标',
|
locationText: hasLocation ? `${realtime?.longitude}, ${realtime?.latitude}` : '无有效坐标',
|
||||||
rawText: source.hasRaw ? '可查 RAW' : '无 RAW 证据',
|
rawText: source.hasRaw ? '可查原始记录' : '无原始记录',
|
||||||
mileageText: hasMileage ? formatCompactNumber(realtime?.totalMileageKm, ' km') : '无里程证据',
|
mileageText: hasMileage ? formatCompactNumber(realtime?.totalMileageKm, ' km') : '无里程证据',
|
||||||
contribution: contribution.length > 0 ? contribution : ['待补来源']
|
contribution: contribution.length > 0 ? contribution : ['待补来源']
|
||||||
};
|
};
|
||||||
@@ -414,13 +414,13 @@ export function VehicleDetail({
|
|||||||
`定位证据:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源有位置` : '暂无位置'}`,
|
`定位证据:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源有位置` : '暂无位置'}`,
|
||||||
`里程差异:${formatCompactNumber(evidenceMileageDelta, ' km')}`,
|
`里程差异:${formatCompactNumber(evidenceMileageDelta, ' km')}`,
|
||||||
`时间差异:${formatSeconds(evidenceTimeDeltaSeconds)}`,
|
`时间差异:${formatSeconds(evidenceTimeDeltaSeconds)}`,
|
||||||
`数据规模:实时 ${overview?.realtimeCount ?? detail.realtime.length} / 历史 ${overview?.historyCount ?? detail.history.total ?? 0} / RAW ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0} / 质量 ${overview?.qualityIssueCount ?? qualityCount}`,
|
`服务数据:实时 ${overview?.realtimeCount ?? detail.realtime.length} / 轨迹 ${overview?.historyCount ?? detail.history.total ?? 0} / 原始记录 ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0} / 告警 ${overview?.qualityIssueCount ?? qualityCount}`,
|
||||||
`一致性:${consistencyTitle}`,
|
`一致性:${consistencyTitle}`,
|
||||||
`一致性说明:${consistencyDetail}`,
|
`一致性说明:${consistencyDetail}`,
|
||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
||||||
`实时:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`RAW:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`里程:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
`里程:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '诊断摘要');
|
copyText(lines.join('\n'), '诊断摘要');
|
||||||
@@ -445,14 +445,14 @@ export function VehicleDetail({
|
|||||||
` 贡献:${row.contribution.join('、')}`,
|
` 贡献:${row.contribution.join('、')}`,
|
||||||
` 实时:${row.realtimeText}`,
|
` 实时:${row.realtimeText}`,
|
||||||
` 定位:${row.locationText}`,
|
` 定位:${row.locationText}`,
|
||||||
` RAW:${row.rawText}`,
|
` 原始记录:${row.rawText}`,
|
||||||
` 里程:${row.mileageText}`
|
` 里程:${row.mileageText}`
|
||||||
].join('\n')),
|
].join('\n')),
|
||||||
'',
|
'',
|
||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
||||||
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`RAW证据:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '多源归并矩阵');
|
copyText(lines.join('\n'), '多源归并矩阵');
|
||||||
@@ -536,7 +536,7 @@ export function VehicleDetail({
|
|||||||
color: (overview?.historyCount ?? detail?.history?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const
|
color: (overview?.historyCount ?? detail?.history?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'RAW',
|
label: '原始记录',
|
||||||
value: formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0),
|
value: formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0),
|
||||||
detail: latestRaw?.frameType || '解析字段',
|
detail: latestRaw?.frameType || '解析字段',
|
||||||
color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const
|
color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const
|
||||||
@@ -596,13 +596,13 @@ export function VehicleDetail({
|
|||||||
`实时主来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`,
|
`实时主来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`,
|
||||||
`来源在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`,
|
`来源在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`,
|
||||||
`业务可用性:${online ? '在线可用' : hasResolvedVIN ? '离线待查' : '待绑定'}`,
|
`业务可用性:${online ? '在线可用' : hasResolvedVIN ? '离线待查' : '待绑定'}`,
|
||||||
`数据资产:实时 ${overview?.realtimeCount ?? detail.realtime.length} / 轨迹 ${overview?.historyCount ?? detail.history.total ?? 0} / RAW ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0} / 告警 ${overview?.qualityIssueCount ?? qualityCount}`,
|
`服务数据:实时 ${overview?.realtimeCount ?? detail.realtime.length} / 轨迹 ${overview?.historyCount ?? detail.history.total ?? 0} / 原始记录 ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0} / 告警 ${overview?.qualityIssueCount ?? qualityCount}`,
|
||||||
`下一步:${serviceActions[0]?.label ?? serviceConclusion.nextStep}`,
|
`下一步:${serviceActions[0]?.label ?? serviceConclusion.nextStep}`,
|
||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
||||||
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`历史RAW:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`数据导出:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`统计查询:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '统一车辆服务摘要');
|
copyText(lines.join('\n'), '统一车辆服务摘要');
|
||||||
};
|
};
|
||||||
@@ -628,7 +628,7 @@ export function VehicleDetail({
|
|||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
||||||
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`RAW证据:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '车辆服务档案');
|
copyText(lines.join('\n'), '车辆服务档案');
|
||||||
@@ -650,13 +650,13 @@ export function VehicleDetail({
|
|||||||
`在线来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`,
|
`在线来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`,
|
||||||
`定位来源:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount}` : '0'}`,
|
`定位来源:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount}` : '0'}`,
|
||||||
`质量问题:${qualityCount.toLocaleString()} 项`,
|
`质量问题:${qualityCount.toLocaleString()} 项`,
|
||||||
`数据规模:历史 ${overview?.historyCount ?? detail.history.total ?? 0} / RAW ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0}`,
|
`服务数据:轨迹 ${overview?.historyCount ?? detail.history.total ?? 0} / 原始记录 ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0}`,
|
||||||
`下一步清单:${actionLines.length > 0 ? '' : '暂无明确处置项,持续观察实时状态。'}`,
|
`下一步清单:${actionLines.length > 0 ? '' : '暂无明确处置项,持续观察实时状态。'}`,
|
||||||
...actionLines,
|
...actionLines,
|
||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`,
|
||||||
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`RAW证据:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '运营摘要');
|
copyText(lines.join('\n'), '运营摘要');
|
||||||
@@ -685,14 +685,14 @@ export function VehicleDetail({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'RAW 证据',
|
title: '原始记录',
|
||||||
value: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 帧`,
|
value: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 帧`,
|
||||||
meta: latestRaw?.frameType || '解析字段',
|
meta: latestRaw?.frameType || '字段明细',
|
||||||
color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
|
color: (overview?.rawCount ?? detail?.raw?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const,
|
||||||
detail: '查看原始报文和解析字段,定位协议解析、来源转发和字段缺失问题。',
|
detail: '查看原始记录和字段明细,用于复核定位、里程和告警依据。',
|
||||||
disabled: !hasResolvedVIN,
|
disabled: !hasResolvedVIN,
|
||||||
actions: [
|
actions: [
|
||||||
{ label: '查看 RAW', onClick: () => onOpenRaw(resolvedVIN, activeProtocol) }
|
{ label: '查看原始记录', onClick: () => onOpenRaw(resolvedVIN, activeProtocol) }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -736,10 +736,10 @@ export function VehicleDetail({
|
|||||||
onClick: () => onOpenHistory(resolvedVIN, activeProtocol)
|
onClick: () => onOpenHistory(resolvedVIN, activeProtocol)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '核对 RAW 字段',
|
title: '核对原始记录',
|
||||||
evidence: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 帧,最新类型 ${latestRaw?.frameType || '-'}`,
|
evidence: `${formatCompactNumber(overview?.rawCount ?? detail?.raw?.total ?? 0)} 帧,最新类型 ${latestRaw?.frameType || '-'}`,
|
||||||
acceptance: '关键字段来自解析字段而不是页面二次推断。',
|
acceptance: '关键字段来自解析字段而不是页面二次推断。',
|
||||||
action: 'RAW 证据',
|
action: '原始记录',
|
||||||
disabled: !hasResolvedVIN,
|
disabled: !hasResolvedVIN,
|
||||||
onClick: () => onOpenRaw(resolvedVIN, activeProtocol)
|
onClick: () => onOpenRaw(resolvedVIN, activeProtocol)
|
||||||
},
|
},
|
||||||
@@ -762,12 +762,12 @@ export function VehicleDetail({
|
|||||||
];
|
];
|
||||||
const copyVehicleRunbook = () => {
|
const copyVehicleRunbook = () => {
|
||||||
if (!detail) {
|
if (!detail) {
|
||||||
Toast.warning('当前没有可复制的单车Runbook');
|
Toast.warning('当前没有可复制的单车处理清单');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const vehicleName = [archivePlate, displayVIN].filter((item) => item && item !== '-').join(' / ') || displayLookupKey;
|
const vehicleName = [archivePlate, displayVIN].filter((item) => item && item !== '-').join(' / ') || displayLookupKey;
|
||||||
const lines = [
|
const lines = [
|
||||||
'【单车服务闭环Runbook】',
|
'【单车服务处理清单】',
|
||||||
`车辆:${vehicleName}`,
|
`车辆:${vehicleName}`,
|
||||||
`当前范围:${scopeText}`,
|
`当前范围:${scopeText}`,
|
||||||
`服务状态:${serviceStatus?.title ?? serviceConclusion.status}`,
|
`服务状态:${serviceStatus?.title ?? serviceConclusion.status}`,
|
||||||
@@ -779,12 +779,12 @@ export function VehicleDetail({
|
|||||||
].join('\n')),
|
].join('\n')),
|
||||||
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
`实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`,
|
||||||
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
`轨迹回放:${vehicleServiceURL(vehicleServiceHash('history'))}`,
|
||||||
`RAW证据:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
`原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`,
|
||||||
`告警事件:${vehicleServiceURL(buildAppHash({ page: 'alert-events', keyword: resolvedVIN, protocol: activeProtocol }))}`,
|
`告警事件:${vehicleServiceURL(buildAppHash({ page: 'alert-events', keyword: resolvedVIN, protocol: activeProtocol }))}`,
|
||||||
`统计查询:${vehicleServiceURL(vehicleServiceHash('mileage'))}`,
|
`里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}`,
|
||||||
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`
|
`车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`
|
||||||
];
|
];
|
||||||
copyText(lines.join('\n'), '单车Runbook');
|
copyText(lines.join('\n'), '单车处理清单');
|
||||||
};
|
};
|
||||||
const qualityTable = (
|
const qualityTable = (
|
||||||
<Table
|
<Table
|
||||||
@@ -824,7 +824,7 @@ export function VehicleDetail({
|
|||||||
{ section: '证据链', item: '一致性结论', value: consistencyTitle },
|
{ section: '证据链', item: '一致性结论', value: consistencyTitle },
|
||||||
{ section: '证据链', item: '一致性说明', value: consistencyDetail },
|
{ section: '证据链', item: '一致性说明', value: consistencyDetail },
|
||||||
{ section: '数据规模', item: '历史记录', value: String(overview?.historyCount ?? detail.history.total ?? 0) },
|
{ section: '数据规模', item: '历史记录', value: String(overview?.historyCount ?? detail.history.total ?? 0) },
|
||||||
{ section: '数据规模', item: 'RAW记录', value: String(overview?.rawCount ?? detail.raw.total ?? 0) },
|
{ section: '数据规模', item: '原始记录', value: String(overview?.rawCount ?? detail.raw.total ?? 0) },
|
||||||
{ section: '数据规模', item: '里程记录', value: String(overview?.mileageCount ?? detail.mileage.total ?? 0) },
|
{ section: '数据规模', item: '里程记录', value: String(overview?.mileageCount ?? detail.mileage.total ?? 0) },
|
||||||
{ section: '数据规模', item: '质量问题', value: String(qualityCount) }
|
{ section: '数据规模', item: '质量问题', value: String(qualityCount) }
|
||||||
];
|
];
|
||||||
@@ -841,7 +841,7 @@ export function VehicleDetail({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
<PageHeader title="车辆服务" description="以 VIN 为主对象聚合身份、实时、历史、RAW 和里程,协议仅作为数据来源" />
|
<PageHeader title="车辆服务" description="以车辆为主对象查看实时位置、轨迹回放、里程统计、告警和可导出的原始记录" />
|
||||||
<Card bordered>
|
<Card bordered>
|
||||||
<Form key={formKey} initValues={query} layout="horizontal" onSubmit={(values) => {
|
<Form key={formKey} initValues={query} layout="horizontal" onSubmit={(values) => {
|
||||||
const nextQuery = { keyword: String(values.keyword ?? ''), protocol: String(values.protocol ?? '') };
|
const nextQuery = { keyword: String(values.keyword ?? ''), protocol: String(values.protocol ?? '') };
|
||||||
@@ -863,9 +863,9 @@ export function VehicleDetail({
|
|||||||
<Button disabled={!detail} icon={<IconCopy />} onClick={copyVehicleOperationsSummary}>复制运营摘要</Button>
|
<Button disabled={!detail} icon={<IconCopy />} onClick={copyVehicleOperationsSummary}>复制运营摘要</Button>
|
||||||
<Button disabled={!detail} icon={<IconDownload />} onClick={exportVehicleReport}>导出档案 CSV</Button>
|
<Button disabled={!detail} icon={<IconDownload />} onClick={exportVehicleReport}>导出档案 CSV</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRealtime(resolvedVIN, activeProtocol)}>查看实时</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRealtime(resolvedVIN, activeProtocol)}>查看实时</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>查看历史</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>轨迹回放</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>查看 RAW</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>数据导出</Button>
|
||||||
<Button disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>查看里程</Button>
|
<Button disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>里程统计</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -874,7 +874,7 @@ export function VehicleDetail({
|
|||||||
<span className="vp-scope-label">当前查看范围</span>
|
<span className="vp-scope-label">当前查看范围</span>
|
||||||
<Tag color={activeProtocol ? 'blue' : 'green'}>{scopeText}</Tag>
|
<Tag color={activeProtocol ? 'blue' : 'green'}>{scopeText}</Tag>
|
||||||
<Typography.Text type="tertiary">
|
<Typography.Text type="tertiary">
|
||||||
{activeProtocol ? '下方实时、历史、RAW 和里程仅展示该来源返回的数据。' : '下方数据按车辆聚合展示全部可用来源。'}
|
{activeProtocol ? '下方实时、轨迹、原始记录和里程仅展示该数据通道返回的数据。' : '下方数据按车辆聚合展示全部可用数据通道。'}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -890,7 +890,7 @@ export function VehicleDetail({
|
|||||||
|
|
||||||
<Card
|
<Card
|
||||||
bordered
|
bordered
|
||||||
title={<Space><span>统一车辆服务视图</span><Button size="small" disabled={!detail} icon={<IconCopy />} onClick={copyUnifiedVehicleService}>复制归一摘要</Button></Space>}
|
title={<Space><span>车辆运营总览</span><Button size="small" disabled={!detail} icon={<IconCopy />} onClick={copyUnifiedVehicleService}>复制归一摘要</Button></Space>}
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
>
|
>
|
||||||
<div className="vp-unified-service-board">
|
<div className="vp-unified-service-board">
|
||||||
@@ -908,8 +908,8 @@ export function VehicleDetail({
|
|||||||
<div className="vp-unified-service-actions">
|
<div className="vp-unified-service-actions">
|
||||||
<Button size="small" theme="solid" type="primary" disabled={!hasResolvedVIN} onClick={() => onOpenRealtime(resolvedVIN, activeProtocol)}>实时监控</Button>
|
<Button size="small" theme="solid" type="primary" disabled={!hasResolvedVIN} onClick={() => onOpenRealtime(resolvedVIN, activeProtocol)}>实时监控</Button>
|
||||||
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>轨迹回放</Button>
|
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenHistory(resolvedVIN, activeProtocol)}>轨迹回放</Button>
|
||||||
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>历史 RAW</Button>
|
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenRaw(resolvedVIN, activeProtocol)}>数据导出</Button>
|
||||||
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>统计查询</Button>
|
<Button size="small" theme="light" disabled={!hasResolvedVIN} onClick={() => onOpenMileage(resolvedVIN, activeProtocol)}>里程统计</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="vp-unified-service-grid">
|
<div className="vp-unified-service-grid">
|
||||||
@@ -984,7 +984,7 @@ export function VehicleDetail({
|
|||||||
{ key: '覆盖状态', value: <Tag color={overview.coverageStatus === 'online' ? 'green' : overview.coverageStatus === 'partial' ? 'orange' : 'grey'}>{coverageStatusText[overview.coverageStatus] ?? overview.coverageStatus}</Tag> },
|
{ key: '覆盖状态', value: <Tag color={overview.coverageStatus === 'online' ? 'green' : overview.coverageStatus === 'partial' ? 'orange' : 'grey'}>{coverageStatusText[overview.coverageStatus] ?? overview.coverageStatus}</Tag> },
|
||||||
{ key: '主来源', value: overview.primaryProtocol || '-' },
|
{ key: '主来源', value: overview.primaryProtocol || '-' },
|
||||||
{ key: '最后上报', value: overview.lastSeen || '-' },
|
{ key: '最后上报', value: overview.lastSeen || '-' },
|
||||||
{ key: '数据规模', value: `历史 ${overview.historyCount} / RAW ${overview.rawCount} / 里程 ${overview.mileageCount}` },
|
{ key: '服务数据', value: `轨迹 ${overview.historyCount} / 原始记录 ${overview.rawCount} / 里程 ${overview.mileageCount}` },
|
||||||
{ key: '实时来源', value: overview.realtimeCount },
|
{ key: '实时来源', value: overview.realtimeCount },
|
||||||
{ key: '质量问题', value: qualityIssueAction(overview.qualityIssueCount) }
|
{ key: '质量问题', value: qualityIssueAction(overview.qualityIssueCount) }
|
||||||
]}
|
]}
|
||||||
@@ -1007,7 +1007,7 @@ export function VehicleDetail({
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card bordered title="单车服务作业台" style={{ marginTop: 16 }}>
|
<Card bordered title="单车运营工作台" style={{ marginTop: 16 }}>
|
||||||
<div className="vp-workbench-grid">
|
<div className="vp-workbench-grid">
|
||||||
{detailWorkbench.map((item) => (
|
{detailWorkbench.map((item) => (
|
||||||
<div key={item.title} className="vp-workbench-item">
|
<div key={item.title} className="vp-workbench-item">
|
||||||
@@ -1025,7 +1025,7 @@ export function VehicleDetail({
|
|||||||
theme="light"
|
theme="light"
|
||||||
type="primary"
|
type="primary"
|
||||||
disabled={item.disabled || (item.title === '告警处置' && !onOpenQuality)}
|
disabled={item.disabled || (item.title === '告警处置' && !onOpenQuality)}
|
||||||
aria-label={`单车作业台 ${item.title} ${action.label}`}
|
aria-label={`单车运营工作台 ${item.title} ${action.label}`}
|
||||||
onClick={action.onClick}
|
onClick={action.onClick}
|
||||||
>
|
>
|
||||||
{action.label}
|
{action.label}
|
||||||
@@ -1039,7 +1039,7 @@ export function VehicleDetail({
|
|||||||
|
|
||||||
<Card
|
<Card
|
||||||
bordered
|
bordered
|
||||||
title={<Space><span>单车服务闭环Runbook</span><Button size="small" onClick={copyVehicleRunbook}>复制Runbook</Button></Space>}
|
title={<Space><span>单车服务处理清单</span><Button size="small" onClick={copyVehicleRunbook}>复制处理清单</Button></Space>}
|
||||||
style={{ marginTop: 16 }}
|
style={{ marginTop: 16 }}
|
||||||
>
|
>
|
||||||
<div className="vp-runbook-grid">
|
<div className="vp-runbook-grid">
|
||||||
@@ -1055,7 +1055,7 @@ export function VehicleDetail({
|
|||||||
theme="light"
|
theme="light"
|
||||||
type="primary"
|
type="primary"
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
aria-label={`单车Runbook ${item.title} ${item.action}`}
|
aria-label={`单车处理清单 ${item.title} ${item.action}`}
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
>
|
>
|
||||||
{item.action}
|
{item.action}
|
||||||
@@ -1112,7 +1112,7 @@ export function VehicleDetail({
|
|||||||
{[
|
{[
|
||||||
{ label: '实时', value: row.realtimeText },
|
{ label: '实时', value: row.realtimeText },
|
||||||
{ label: '定位', value: row.locationText },
|
{ label: '定位', value: row.locationText },
|
||||||
{ label: 'RAW', value: row.rawText },
|
{ label: '原始记录', value: row.rawText },
|
||||||
{ label: '里程', value: row.mileageText }
|
{ label: '里程', value: row.mileageText }
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<div key={item.label} className="vp-source-fusion-fact">
|
<div key={item.label} className="vp-source-fusion-fact">
|
||||||
@@ -1134,7 +1134,7 @@ export function VehicleDetail({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button size="small" theme="light" disabled={!row.source.hasRealtime} onClick={() => onOpenRealtime(resolvedVIN, row.source.protocol)}>实时</Button>
|
<Button size="small" theme="light" disabled={!row.source.hasRealtime} onClick={() => onOpenRealtime(resolvedVIN, row.source.protocol)}>实时</Button>
|
||||||
<Button size="small" theme="light" disabled={!row.source.hasHistory} onClick={() => onOpenHistory(resolvedVIN, row.source.protocol)}>轨迹</Button>
|
<Button size="small" theme="light" disabled={!row.source.hasHistory} onClick={() => onOpenHistory(resolvedVIN, row.source.protocol)}>轨迹</Button>
|
||||||
<Button size="small" theme="light" disabled={!row.source.hasRaw} onClick={() => onOpenRaw(resolvedVIN, row.source.protocol)}>RAW</Button>
|
<Button size="small" theme="light" disabled={!row.source.hasRaw} onClick={() => onOpenRaw(resolvedVIN, row.source.protocol)}>原始记录</Button>
|
||||||
<Button size="small" theme="light" disabled={!row.source.hasMileage} onClick={() => onOpenMileage(resolvedVIN, row.source.protocol)}>里程</Button>
|
<Button size="small" theme="light" disabled={!row.source.hasMileage} onClick={() => onOpenMileage(resolvedVIN, row.source.protocol)}>里程</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@@ -1209,7 +1209,7 @@ export function VehicleDetail({
|
|||||||
{qualityIssueVehicleLabel(priorityQualityIssue, resolvedVIN)}
|
{qualityIssueVehicleLabel(priorityQualityIssue, resolvedVIN)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Typography.Text type="secondary" style={{ display: 'block', marginTop: 6 }}>
|
<Typography.Text type="secondary" style={{ display: 'block', marginTop: 6 }}>
|
||||||
{priorityQualityIssue.detail || '该车存在质量问题,需要结合轨迹和 RAW 证据确认影响范围。'}
|
{priorityQualityIssue.detail || '该车存在告警事件,需要结合轨迹和原始记录确认影响范围。'}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
@@ -1230,13 +1230,13 @@ export function VehicleDetail({
|
|||||||
轨迹证据
|
轨迹证据
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
aria-label="单车告警RAW证据"
|
aria-label="单车告警原始记录"
|
||||||
size="small"
|
size="small"
|
||||||
theme="light"
|
theme="light"
|
||||||
type="tertiary"
|
type="tertiary"
|
||||||
onClick={() => openQualityIssueRawEvidence(priorityQualityIssue)}
|
onClick={() => openQualityIssueRawEvidence(priorityQualityIssue)}
|
||||||
>
|
>
|
||||||
RAW 证据
|
原始记录
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
@@ -1276,7 +1276,7 @@ export function VehicleDetail({
|
|||||||
},
|
},
|
||||||
{ key: '在线来源', value: summary ? `${summary.onlineSourceCount}/${summary.sourceCount}` : '-' },
|
{ key: '在线来源', value: summary ? `${summary.onlineSourceCount}/${summary.sourceCount}` : '-' },
|
||||||
{ key: '最后位置时间', value: lastSeen },
|
{ key: '最后位置时间', value: lastSeen },
|
||||||
{ key: '最新 RAW 时间', value: latestRaw?.serverTime ?? '-' },
|
{ key: '最新原始记录时间', value: latestRaw?.serverTime ?? '-' },
|
||||||
{ key: '质量问题', value: <Tag color={qualityCount > 0 ? 'orange' : 'green'}>{qualityCount > 0 ? `${qualityCount} 项` : '无'}</Tag> }
|
{ key: '质量问题', value: <Tag color={qualityCount > 0 ? 'orange' : 'green'}>{qualityCount > 0 ? `${qualityCount} 项` : '无'}</Tag> }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -1374,7 +1374,7 @@ export function VehicleDetail({
|
|||||||
disabled={!row.hasHistory}
|
disabled={!row.hasHistory}
|
||||||
onClick={() => onOpenHistory(resolvedVIN, row.protocol)}
|
onClick={() => onOpenHistory(resolvedVIN, row.protocol)}
|
||||||
>
|
>
|
||||||
查看 {row.protocol} 历史
|
查看 {row.protocol} 轨迹
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1383,7 +1383,7 @@ export function VehicleDetail({
|
|||||||
disabled={!row.hasRaw}
|
disabled={!row.hasRaw}
|
||||||
onClick={() => onOpenRaw(resolvedVIN, row.protocol)}
|
onClick={() => onOpenRaw(resolvedVIN, row.protocol)}
|
||||||
>
|
>
|
||||||
查看 {row.protocol} RAW
|
查看 {row.protocol} 原始记录
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
@@ -1484,7 +1484,7 @@ export function VehicleDetail({
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="RAW 字段" itemKey="raw">
|
<Tabs.TabPane tab="原始字段" itemKey="raw">
|
||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
pagination={false}
|
pagination={false}
|
||||||
@@ -1498,7 +1498,7 @@ export function VehicleDetail({
|
|||||||
{ title: '入库时间', dataIndex: 'serverTime', width: 190 }
|
{ title: '入库时间', dataIndex: 'serverTime', width: 190 }
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Typography.Text type="tertiary">最新 RAW 解析字段</Typography.Text>
|
<Typography.Text type="tertiary">最新原始记录字段明细</Typography.Text>
|
||||||
<pre className="vp-json">{JSON.stringify(latestRaw?.parsedFields ?? {}, null, 2)}</pre>
|
<pre className="vp-json">{JSON.stringify(latestRaw?.parsedFields ?? {}, null, 2)}</pre>
|
||||||
</Tabs.TabPane>
|
</Tabs.TabPane>
|
||||||
<Tabs.TabPane tab="里程" itemKey="mileage">
|
<Tabs.TabPane tab="里程" itemKey="mileage">
|
||||||
|
|||||||
@@ -9587,7 +9587,7 @@ test('opens amap coordinate and trajectory playback from realtime map service qu
|
|||||||
'noopener,noreferrer'
|
'noopener,noreferrer'
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: '轨迹回放' }));
|
fireEvent.click(screen.getAllByRole('button', { name: '轨迹回放' })[0]);
|
||||||
expect(window.location.hash).toBe('#/history?keyword=VIN-MAP-LINK&protocol=GB32960');
|
expect(window.location.hash).toBe('#/history?keyword=VIN-MAP-LINK&protocol=GB32960');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -10738,11 +10738,11 @@ test('switches vehicle detail to a source from the source matrix', async () => {
|
|||||||
expect(screen.getAllByText('定位证据').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('定位证据').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('统计口径').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('统计口径').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('无实时字段')).toBeInTheDocument();
|
expect(screen.getByText('无实时字段')).toBeInTheDocument();
|
||||||
expect(screen.getByText('无 RAW 证据')).toBeInTheDocument();
|
expect(screen.getByText('无原始记录')).toBeInTheDocument();
|
||||||
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('1. GB32960 / 主来源'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. GB32960 / 主来源'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('贡献:主实时来源、在线心跳、实时字段、定位证据、轨迹回放、RAW证据、统计口径'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('贡献:主实时来源、在线心跳、实时字段、定位证据、轨迹回放、原始记录、统计口径'));
|
||||||
expect(screen.getByText('车辆服务角色')).toBeInTheDocument();
|
expect(screen.getByText('车辆服务角色')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('主来源').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('主来源').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('在线证据').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('在线证据').length).toBeGreaterThanOrEqual(1);
|
||||||
@@ -10828,22 +10828,22 @@ test('shows unified service overview on vehicle detail', async () => {
|
|||||||
expect(await screen.findByText('车辆服务概览')).toBeInTheDocument();
|
expect(await screen.findByText('车辆服务概览')).toBeInTheDocument();
|
||||||
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();
|
||||||
expect(screen.getByText('单车服务闭环Runbook')).toBeInTheDocument();
|
expect(screen.getByText('单车服务处理清单')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('档案完整度').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('档案完整度').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('实时')).toBeInTheDocument();
|
expect(screen.getByText('实时')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('轨迹').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('轨迹').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('RAW').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('原始记录').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('里程').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('里程').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('告警').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('告警').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('实时定位')).toBeInTheDocument();
|
expect(screen.getByText('实时定位')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('轨迹回放').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('轨迹回放').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('RAW 证据').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('确认实时状态')).toBeInTheDocument();
|
expect(screen.getByText('确认实时状态')).toBeInTheDocument();
|
||||||
expect(screen.getByText('回放轨迹断点')).toBeInTheDocument();
|
expect(screen.getByText('回放轨迹断点')).toBeInTheDocument();
|
||||||
expect(screen.getByText('核对 RAW 字段')).toBeInTheDocument();
|
expect(screen.getByText('核对原始记录')).toBeInTheDocument();
|
||||||
expect(screen.getByText('处理告警闭环')).toBeInTheDocument();
|
expect(screen.getByText('处理告警闭环')).toBeInTheDocument();
|
||||||
expect(screen.getByText('复核统计口径')).toBeInTheDocument();
|
expect(screen.getByText('复核统计口径')).toBeInTheDocument();
|
||||||
expect(screen.getByText('车辆在线状态、最新时间、有效坐标均可解释。')).toBeInTheDocument();
|
expect(screen.getByText('车辆在线状态、最新时间、有效坐标均可解释。')).toBeInTheDocument();
|
||||||
@@ -10853,8 +10853,8 @@ test('shows unified service overview on vehicle detail', async () => {
|
|||||||
expect(screen.getAllByText('1 项').length).toBeGreaterThan(0);
|
expect(screen.getAllByText('1 项').length).toBeGreaterThan(0);
|
||||||
expect(screen.getByText('7 条统计')).toBeInTheDocument();
|
expect(screen.getByText('7 条统计')).toBeInTheDocument();
|
||||||
expect(screen.getByText('2/3')).toBeInTheDocument();
|
expect(screen.getByText('2/3')).toBeInTheDocument();
|
||||||
expect(screen.getByText('历史 12 / RAW 34 / 里程 7')).toBeInTheDocument();
|
expect(screen.getByText('轨迹 12 / 原始记录 34 / 里程 7')).toBeInTheDocument();
|
||||||
fireEvent.click(screen.getByRole('button', { name: '单车作业台 RAW 证据 查看 RAW' }));
|
fireEvent.click(screen.getByRole('button', { name: '单车运营工作台 原始记录 查看原始记录' }));
|
||||||
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&tab=raw');
|
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&tab=raw');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -11338,7 +11338,7 @@ test('copies vehicle service diagnostic summary', async () => {
|
|||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
expect(await screen.findByText('统一车辆服务视图')).toBeInTheDocument();
|
expect(await screen.findByText('车辆运营总览')).toBeInTheDocument();
|
||||||
expect(screen.getByRole('button', { name: /复制归一摘要/ })).toBeInTheDocument();
|
expect(screen.getByRole('button', { name: /复制归一摘要/ })).toBeInTheDocument();
|
||||||
expect(screen.getByText('车辆身份')).toBeInTheDocument();
|
expect(screen.getByText('车辆身份')).toBeInTheDocument();
|
||||||
expect(screen.getByText('实时主来源')).toBeInTheDocument();
|
expect(screen.getByText('实时主来源')).toBeInTheDocument();
|
||||||
@@ -11358,7 +11358,7 @@ test('copies vehicle service diagnostic summary', async () => {
|
|||||||
expect(unifiedCopied).toContain('实时主来源:JT808');
|
expect(unifiedCopied).toContain('实时主来源:JT808');
|
||||||
expect(unifiedCopied).toContain('来源在线:1/2');
|
expect(unifiedCopied).toContain('来源在线:1/2');
|
||||||
expect(unifiedCopied).toContain('业务可用性:在线可用');
|
expect(unifiedCopied).toContain('业务可用性:在线可用');
|
||||||
expect(unifiedCopied).toContain('数据资产:实时 2 / 轨迹 12 / RAW 34 / 里程 3 / 告警 1');
|
expect(unifiedCopied).toContain('服务数据:实时 2 / 轨迹 12 / 原始记录 34 / 里程 3 / 告警 1');
|
||||||
expect(unifiedCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(unifiedCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
fireEvent.click(screen.getByRole('button', { name: /复制档案/ }));
|
fireEvent.click(screen.getByRole('button', { name: /复制档案/ }));
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -11370,7 +11370,7 @@ test('copies vehicle service diagnostic summary', async () => {
|
|||||||
expect(dossierCopied).toContain('当前范围:单一来源:JT808');
|
expect(dossierCopied).toContain('当前范围:单一来源:JT808');
|
||||||
expect(dossierCopied).toContain('主来源:JT808');
|
expect(dossierCopied).toContain('主来源:JT808');
|
||||||
expect(dossierCopied).toContain('来源在线:1/2');
|
expect(dossierCopied).toContain('来源在线:1/2');
|
||||||
expect(dossierCopied).toContain('资产概览:实时 2 / 轨迹 12 / RAW 34 / 里程 3 / 告警 1');
|
expect(dossierCopied).toContain('资产概览:实时 2 / 轨迹 12 / 原始记录 34 / 里程 3 / 告警 1');
|
||||||
expect(dossierCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(dossierCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
fireEvent.click(screen.getByRole('button', { name: /复制诊断摘要/ }));
|
fireEvent.click(screen.getByRole('button', { name: /复制诊断摘要/ }));
|
||||||
|
|
||||||
@@ -11387,13 +11387,13 @@ test('copies vehicle service diagnostic summary', async () => {
|
|||||||
expect(copied).toContain('定位证据:2 个来源有位置');
|
expect(copied).toContain('定位证据:2 个来源有位置');
|
||||||
expect(copied).toContain('里程差异:12.6 km');
|
expect(copied).toContain('里程差异:12.6 km');
|
||||||
expect(copied).toContain('时间差异:90 秒');
|
expect(copied).toContain('时间差异:90 秒');
|
||||||
expect(copied).toContain('数据规模:实时 2 / 历史 12 / RAW 34 / 里程 3 / 质量 1');
|
expect(copied).toContain('服务数据:实时 2 / 轨迹 12 / 原始记录 34 / 里程 3 / 告警 1');
|
||||||
expect(copied).toContain('一致性:来源不完整');
|
expect(copied).toContain('一致性:来源不完整');
|
||||||
expect(copied).toContain('下一步:');
|
expect(copied).toContain('下一步:');
|
||||||
expect(copied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(copied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(copied).toContain('实时:http://localhost:3000/#/realtime?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(copied).toContain('实时:http://localhost:3000/#/realtime?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(copied).toContain('轨迹:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(copied).toContain('轨迹:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(copied).toContain('RAW:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
|
expect(copied).toContain('原始记录:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
|
||||||
expect(copied).toContain('里程:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(copied).toContain('里程:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: /复制运营摘要/ }));
|
fireEvent.click(screen.getByRole('button', { name: /复制运营摘要/ }));
|
||||||
@@ -11415,25 +11415,25 @@ test('copies vehicle service diagnostic summary', async () => {
|
|||||||
expect(operationsCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(operationsCopied).toContain('车辆服务:http://localhost:3000/#/detail?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(operationsCopied).toContain('实时监控:http://localhost:3000/#/realtime?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(operationsCopied).toContain('实时监控:http://localhost:3000/#/realtime?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(operationsCopied).toContain('轨迹回放:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(operationsCopied).toContain('轨迹回放:http://localhost:3000/#/history?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(operationsCopied).toContain('RAW证据:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
|
expect(operationsCopied).toContain('原始记录:http://localhost:3000/#/history-query?keyword=VIN-SUMMARY-001&protocol=JT808&tab=raw&includeFields=true');
|
||||||
expect(operationsCopied).toContain('里程统计:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(operationsCopied).toContain('里程统计:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole('button', { name: '复制Runbook' }));
|
fireEvent.click(screen.getByRole('button', { name: '复制处理清单' }));
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(writeText.mock.calls.some((call) => String(call[0]).includes('【单车服务闭环Runbook】'))).toBe(true);
|
expect(writeText.mock.calls.some((call) => String(call[0]).includes('【单车服务处理清单】'))).toBe(true);
|
||||||
});
|
});
|
||||||
const runbookCopied = String(writeText.mock.lastCall?.[0] ?? '');
|
const runbookCopied = String(writeText.mock.lastCall?.[0] ?? '');
|
||||||
expect(runbookCopied).toContain('【单车服务闭环Runbook】');
|
expect(runbookCopied).toContain('【单车服务处理清单】');
|
||||||
expect(runbookCopied).toContain('车辆:粤A诊断1 / VIN-SUMMARY-001');
|
expect(runbookCopied).toContain('车辆:粤A诊断1 / VIN-SUMMARY-001');
|
||||||
expect(runbookCopied).toContain('当前范围:单一来源:JT808');
|
expect(runbookCopied).toContain('当前范围:单一来源:JT808');
|
||||||
expect(runbookCopied).toContain('1. 确认实时状态');
|
expect(runbookCopied).toContain('1. 确认实时状态');
|
||||||
expect(runbookCopied).toContain('2. 回放轨迹断点');
|
expect(runbookCopied).toContain('2. 回放轨迹断点');
|
||||||
expect(runbookCopied).toContain('3. 核对 RAW 字段');
|
expect(runbookCopied).toContain('3. 核对原始记录');
|
||||||
expect(runbookCopied).toContain('4. 处理告警闭环');
|
expect(runbookCopied).toContain('4. 处理告警闭环');
|
||||||
expect(runbookCopied).toContain('5. 复核统计口径');
|
expect(runbookCopied).toContain('5. 复核统计口径');
|
||||||
expect(runbookCopied).toContain('验收:车辆在线状态、最新时间、有效坐标均可解释。');
|
expect(runbookCopied).toContain('验收:车辆在线状态、最新时间、有效坐标均可解释。');
|
||||||
expect(runbookCopied).toContain('告警事件:http://localhost:3000/#/alert-events?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(runbookCopied).toContain('告警事件:http://localhost:3000/#/alert-events?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
expect(runbookCopied).toContain('统计查询:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
expect(runbookCopied).toContain('里程统计:http://localhost:3000/#/mileage?keyword=VIN-SUMMARY-001&protocol=JT808');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('exports vehicle detail service dossier as csv', async () => {
|
test('exports vehicle detail service dossier as csv', async () => {
|
||||||
@@ -11600,7 +11600,7 @@ test('opens history with the currently selected vehicle detail source', async ()
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole('button', { name: '仅看 JT808' }));
|
fireEvent.click(await screen.findByRole('button', { name: '仅看 JT808' }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: '查看历史' }));
|
fireEvent.click(screen.getAllByRole('button', { name: '轨迹回放' })[0]);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(window.location.hash).toBe('#/history?keyword=VIN001&protocol=JT808');
|
expect(window.location.hash).toBe('#/history?keyword=VIN001&protocol=JT808');
|
||||||
@@ -11676,7 +11676,7 @@ test('opens raw history with the currently selected vehicle detail source', asyn
|
|||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole('button', { name: '仅看 JT808' }));
|
fireEvent.click(await screen.findByRole('button', { name: '仅看 JT808' }));
|
||||||
fireEvent.click(screen.getByRole('button', { name: '查看 RAW' }));
|
fireEvent.click(screen.getAllByRole('button', { name: '数据导出' })[0]);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&protocol=JT808&tab=raw');
|
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&protocol=JT808&tab=raw');
|
||||||
@@ -11827,7 +11827,7 @@ test('opens source-specific history directly from vehicle detail source card', a
|
|||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole('button', { name: '查看 JT808 历史' }));
|
fireEvent.click(await screen.findByRole('button', { name: '查看 JT808 轨迹' }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(window.location.hash).toBe('#/history?keyword=VIN001&protocol=JT808');
|
expect(window.location.hash).toBe('#/history?keyword=VIN001&protocol=JT808');
|
||||||
@@ -11902,7 +11902,7 @@ test('opens source-specific raw history directly from vehicle detail source evid
|
|||||||
|
|
||||||
render(<App />);
|
render(<App />);
|
||||||
|
|
||||||
fireEvent.click(await screen.findByRole('button', { name: '查看 JT808 RAW' }));
|
fireEvent.click(await screen.findByRole('button', { name: '查看 JT808 原始记录' }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&protocol=JT808&tab=raw');
|
expect(window.location.hash).toBe('#/history-query?keyword=VIN001&protocol=JT808&tab=raw');
|
||||||
|
|||||||
Reference in New Issue
Block a user