diff --git a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx index 3e2bf9a4..28799950 100644 --- a/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx +++ b/vehicle-data-platform/apps/web/src/pages/VehicleDetail.tsx @@ -58,7 +58,7 @@ function formatSeconds(value?: number) { } function sourceServiceRole(source: VehicleSourceStatus, primaryProtocol?: string) { - if (source.protocol === primaryProtocol) return { label: '主来源', color: 'blue' as const }; + if (source.protocol === primaryProtocol) return { label: '主证据', color: 'blue' as const }; if (source.online) return { label: '在线证据', color: 'green' as const }; if (!source.lastSeen && !source.hasRealtime && !source.hasHistory && !source.hasRaw && !source.hasMileage) { return { label: '暂无来源', color: 'grey' as const }; @@ -70,7 +70,7 @@ function sourceFusionRow(source: VehicleSourceStatus, realtime: RealtimeLocation const hasLocation = !!realtime && isFiniteNumber(realtime.longitude) && isFiniteNumber(realtime.latitude) && realtime.longitude !== 0 && realtime.latitude !== 0; const hasMileage = isFiniteNumber(realtime?.totalMileageKm) || source.hasMileage; const contribution = [ - source.protocol === primaryProtocol ? '主实时来源' : '', + source.protocol === primaryProtocol ? '主实时证据' : '', source.online ? '在线心跳' : '', source.hasRealtime || realtime ? '实时字段' : '', hasLocation ? '定位证据' : '', @@ -284,9 +284,10 @@ export function VehicleDetail({ (detail?.sourceStatus ?? []).map((source) => sourceFusionRow(source, latestRealtimeByProtocol.get(source.protocol), summary?.primaryProtocol)) ), [detail?.sourceStatus, latestRealtimeByProtocol, summary?.primaryProtocol]); const realtimeSources = new Set(realtimeRows.map((row) => row.protocol).filter(Boolean)); - const sourceCount = Math.max(detail?.sourceStatus?.length ?? 0, realtimeSources.size, summary?.sourceCount ?? 0, protocols.length); - const onlineSourceCount = detail?.sourceStatus?.length - ? detail.sourceStatus.filter((source) => source.online).length + const sourceStatusRows = detail?.sourceStatus ?? []; + const sourceCount = Math.max(sourceStatusRows.length, realtimeSources.size, summary?.sourceCount ?? 0, protocols.length); + const onlineSourceCount = sourceStatusRows.length + ? sourceStatusRows.filter((source) => source.online).length : summary?.onlineSourceCount ?? 0; const locatedSourceCount = realtimeRows.filter((row) => isFiniteNumber(row.longitude) && isFiniteNumber(row.latitude)).length; const mileageValues = realtimeRows.map((row) => row.totalMileageKm).filter(isFiniteNumber); @@ -410,8 +411,8 @@ export function VehicleDetail({ `服务判断:${serviceConclusion.status}`, `主要风险:${serviceConclusion.risk}`, `下一步:${serviceConclusion.nextStep}`, - `来源证据:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线` : '-'}`, - `定位证据:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源有位置` : '暂无位置'}`, + `证据来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '-'}`, + `定位证据:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个证据有位置` : '暂无位置'}`, `里程差异:${formatCompactNumber(evidenceMileageDelta, ' km')}`, `时间差异:${formatSeconds(evidenceTimeDeltaSeconds)}`, `服务数据:实时 ${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}`, @@ -427,16 +428,16 @@ export function VehicleDetail({ }; const copySourceFusionReport = () => { if (!detail || sourceFusionRows.length === 0) { - Toast.warning('当前没有可复制的多源归并矩阵'); + Toast.warning('当前没有可复制的多证据归并矩阵'); return; } const vehicleName = [archivePlate, displayVIN].filter((item) => item && item !== '-').join(' / ') || displayLookupKey; const lines = [ - '【单车多源归并矩阵】', + '【单车多证据归并矩阵】', `车辆:${vehicleName}`, `当前范围:${scopeText}`, - `主来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, - `来源覆盖:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '-'}`, + `主证据来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, + `证据覆盖:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '-'}`, `一致性:${consistencyTitle}`, '', ...sourceFusionRows.map((row, index) => [ @@ -455,7 +456,7 @@ export function VehicleDetail({ `原始记录:${vehicleServiceURL(vehicleServiceHash('history-query', { tab: 'raw', includeFields: 'true' }))}`, `里程统计:${vehicleServiceURL(vehicleServiceHash('mileage'))}` ]; - copyText(lines.join('\n'), '多源归并矩阵'); + copyText(lines.join('\n'), '多证据归并矩阵'); }; const qualityIssueAction = (count: number) => { if (count <= 0) { @@ -478,7 +479,7 @@ export function VehicleDetail({ actions.push({ key: 'identity', label: '维护身份绑定', - description: `关键词 ${displayLookupKey} 暂未解析到 VIN,先补齐绑定后再看跨来源服务。`, + description: `关键词 ${displayLookupKey} 暂未解析到 VIN,先补齐绑定后再看跨证据服务。`, color: 'orange', onClick: () => onOpenVehicles?.({ bindingStatus: 'unbound' }) }); @@ -486,8 +487,8 @@ export function VehicleDetail({ for (const item of missingProtocols) { actions.push({ key: `missing-${item}`, - label: `补齐 ${item} 来源`, - description: `${item} 当前没有形成有效来源证据,会影响这辆车的统一服务可信度。`, + label: `补齐 ${item} 证据`, + description: `${item} 当前没有形成有效证据来源,会影响这辆车的统一服务可信度。`, color: 'orange', onClick: () => onOpenVehicles?.({ serviceStatus: 'degraded', missingProtocol: item }) }); @@ -526,13 +527,13 @@ export function VehicleDetail({ { label: '实时', value: formatCompactNumber(overview?.realtimeCount ?? detail?.realtime?.length ?? 0), - detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线` : '暂无来源', + detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '暂无来源', color: evidenceOnlineSourceCount > 0 ? 'green' as const : 'orange' as const }, { label: '轨迹', value: formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0), - detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置来源` : '暂无位置', + detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置证据` : '暂无位置', color: (overview?.historyCount ?? detail?.history?.total ?? 0) > 0 ? 'blue' as const : 'grey' as const }, { @@ -568,9 +569,9 @@ export function VehicleDetail({ color: serviceConclusion.color }, { - label: '实时主来源', + label: '实时主证据来源', value: overview?.primaryProtocol || summary?.primaryProtocol || '-', - detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线` : '暂无来源', + detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '暂无来源', color: evidenceOnlineSourceCount > 0 ? 'green' as const : 'orange' as const }, { @@ -593,8 +594,8 @@ export function VehicleDetail({ `车辆身份:${hasResolvedVIN ? displayVIN : '待绑定'}`, `服务状态:${serviceStatus?.title ?? serviceConclusion.status}`, `服务说明:${serviceStatus?.detail ?? serviceConclusion.risk}`, - `实时主来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, - `来源在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, + `实时主证据来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, + `证据在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, `业务可用性:${online ? '在线可用' : hasResolvedVIN ? '离线待查' : '待绑定'}`, `服务数据:实时 ${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}`, @@ -618,8 +619,8 @@ export function VehicleDetail({ `查询关键词:${displayLookupKey}`, `当前范围:${scopeText}`, `档案完整度:${archiveCompleteness}${archiveMissingLabels.length > 0 ? `(${archiveMissingLabels.join('、')})` : ''}`, - `主来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, - `来源在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, + `主证据来源:${overview?.primaryProtocol || summary?.primaryProtocol || '-'}`, + `证据在线:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, `服务状态:${serviceStatus?.title ?? serviceConclusion.status}`, `服务说明:${serviceStatus?.detail ?? serviceConclusion.risk}`, `资产概览:${serviceAssetItems.map((item) => `${item.label} ${item.value}`).join(' / ')}`, @@ -647,8 +648,8 @@ export function VehicleDetail({ `服务状态:${serviceStatus?.title ?? serviceConclusion.status}`, `业务影响:${serviceConclusion.risk}`, `建议动作:${serviceConclusion.nextStep}`, - `在线来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, - `定位来源:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount}` : '0'}`, + `在线证据:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, + `定位证据:${evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount}` : '0'}`, `质量问题:${qualityCount.toLocaleString()} 项`, `服务数据:轨迹 ${overview?.historyCount ?? detail.history.total ?? 0} / 原始记录 ${overview?.rawCount ?? detail.raw.total ?? 0} / 里程 ${overview?.mileageCount ?? detail.mileage.total ?? 0}`, `下一步清单:${actionLines.length > 0 ? '' : '暂无明确处置项,持续观察实时状态。'}`, @@ -665,9 +666,9 @@ export function VehicleDetail({ { title: '实时定位', value: lastSeen, - meta: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线` : '暂无来源', + meta: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '暂无来源', color: online ? 'green' as const : 'orange' as const, - detail: '查看该车最新位置、速度、SOC、总里程和多来源实时字段。', + detail: '查看该车最新位置、速度、SOC、总里程和多证据实时字段。', disabled: !hasResolvedVIN, actions: [ { label: '查看实时', onClick: () => onOpenRealtime(resolvedVIN, activeProtocol) } @@ -676,7 +677,7 @@ export function VehicleDetail({ { title: '轨迹回放', value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条历史`, - meta: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置来源` : '暂无位置', + meta: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置证据` : '暂无位置', color: evidenceLocatedSourceCount > 0 ? 'blue' as const : 'grey' as const, detail: '回放历史轨迹,复核定位断点、速度变化和里程连续性。', disabled: !hasResolvedVIN, @@ -711,7 +712,7 @@ export function VehicleDetail({ value: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条统计`, meta: formatCompactNumber(evidenceMileageDelta, ' km 差异'), color: isFiniteNumber(evidenceMileageDelta) && Math.abs(evidenceMileageDelta) > 1 ? 'orange' as const : 'green' as const, - detail: '核对统计口径,确认区间总值、每日里程与来源证据是否闭合。', + detail: '核对统计口径,确认区间总值、每日里程与证据来源是否闭合。', disabled: !hasResolvedVIN, actions: [ { label: '查看里程', onClick: () => onOpenMileage(resolvedVIN, activeProtocol) } @@ -721,7 +722,7 @@ export function VehicleDetail({ const serviceRunbookSteps = [ { title: '确认实时状态', - evidence: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线,最后上报 ${lastSeen}` : '暂无来源在线证据', + evidence: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线,最后上报 ${lastSeen}` : '暂无在线证据', acceptance: '车辆在线状态、最新时间、有效坐标均可解释。', action: '打开实时', disabled: !hasResolvedVIN, @@ -729,7 +730,7 @@ export function VehicleDetail({ }, { title: '回放轨迹断点', - evidence: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源有位置,时间差 ${formatSeconds(evidenceTimeDeltaSeconds)}` : '暂无可回放位置', + evidence: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个证据有位置,时间差 ${formatSeconds(evidenceTimeDeltaSeconds)}` : '暂无可回放位置', acceptance: '轨迹点、速度和总里程变化连续,异常点可定位到时间段。', action: '轨迹回放', disabled: !hasResolvedVIN, @@ -753,7 +754,7 @@ export function VehicleDetail({ }, { title: '复核统计口径', - evidence: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条统计,来源里程差 ${formatCompactNumber(evidenceMileageDelta, ' km')}`, + evidence: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条统计,证据里程差 ${formatCompactNumber(evidenceMileageDelta, ' km')}`, acceptance: '区间里程、每日里程和轨迹总里程证据可以互相解释。', action: '里程统计', disabled: !hasResolvedVIN, @@ -790,7 +791,7 @@ export function VehicleDetail({ { title: '看实时位置', value: lastSeen, - detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线,先确认车辆当前是否仍在上报。` : '暂无来源在线证据,优先确认数据接入。', + detail: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线,先确认车辆当前是否仍在上报。` : '暂无在线证据,优先确认数据接入。', color: online ? 'green' as const : 'orange' as const, primaryAction: '实时监控', secondaryAction: '复制摘要', @@ -801,7 +802,7 @@ export function VehicleDetail({ { title: '回放轨迹证据', value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条历史`, - detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置来源,可回放定位、速度和里程断点。` : '暂无有效位置来源,先检查实时坐标。', + detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个位置证据,可回放定位、速度和里程断点。` : '暂无有效位置证据,先检查实时坐标。', color: evidenceLocatedSourceCount > 0 ? 'blue' as const : 'grey' as const, primaryAction: '轨迹回放', secondaryAction: '处理清单', @@ -823,7 +824,7 @@ export function VehicleDetail({ { title: '复核里程统计', value: `${formatCompactNumber(overview?.mileageCount ?? detail?.mileage?.total ?? 0)} 条统计`, - detail: `来源里程差 ${formatCompactNumber(evidenceMileageDelta, ' km')},确认区间和日统计能闭合。`, + detail: `证据里程差 ${formatCompactNumber(evidenceMileageDelta, ' km')},确认区间和日统计能闭合。`, color: isFiniteNumber(evidenceMileageDelta) && Math.abs(evidenceMileageDelta) > 1 ? 'orange' as const : 'green' as const, primaryAction: '里程统计', secondaryAction: '诊断摘要', @@ -856,7 +857,7 @@ export function VehicleDetail({ { title: '轨迹回放', value: `${formatCompactNumber(overview?.historyCount ?? detail?.history?.total ?? 0)} 条`, - detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源可定位` : '暂无可用定位证据', + detail: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个证据可定位` : '暂无可用定位证据', color: evidenceLocatedSourceCount > 0 ? 'blue' as const : 'grey' as const, action: '回放轨迹', disabled: !hasResolvedVIN, @@ -903,11 +904,11 @@ export function VehicleDetail({ `平台能力:实时监控 / 轨迹回放 / 历史数据查询 / 告警通知 / 统计查询`, `服务状态:${serviceStatus?.title ?? serviceConclusion.status}`, `服务说明:${serviceStatus?.detail ?? serviceConclusion.risk}`, - `在线来源:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, + `在线证据:${evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount}` : '-'}`, `最新上报:${lastSeen}`, `轨迹证据:${overview?.historyCount ?? detail.history.total ?? 0} 条`, `原始证据:${overview?.rawCount ?? detail.raw.total ?? 0} 帧`, - `里程统计:${overview?.mileageCount ?? detail.mileage.total ?? 0} 条,来源差异 ${formatCompactNumber(evidenceMileageDelta, ' km')}`, + `里程统计:${overview?.mileageCount ?? detail.mileage.total ?? 0} 条,证据差异 ${formatCompactNumber(evidenceMileageDelta, ' km')}`, `告警事件:${qualityCount.toLocaleString()} 项`, `车辆服务:${vehicleServiceURL(vehicleServiceHash('detail'))}`, `实时监控:${vehicleServiceURL(vehicleServiceHash('realtime'))}`, @@ -949,8 +950,8 @@ export function VehicleDetail({ { section: '服务结论', item: '服务判断', value: serviceConclusion.status }, { section: '服务结论', item: '主要风险', value: serviceConclusion.risk }, { section: '服务结论', item: '下一步', value: serviceConclusion.nextStep }, - { section: '证据链', item: '来源证据', value: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 来源在线` : '-' }, - { section: '证据链', item: '定位证据', value: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个来源有位置` : '暂无位置' }, + { section: '证据链', item: '证据来源', value: evidenceSourceCount > 0 ? `${evidenceOnlineSourceCount}/${evidenceSourceCount} 在线` : '-' }, + { section: '证据链', item: '定位证据', value: evidenceLocatedSourceCount > 0 ? `${evidenceLocatedSourceCount} 个证据有位置` : '暂无位置' }, { section: '证据链', item: '里程差异', value: formatCompactNumber(evidenceMileageDelta, ' km') }, { section: '证据链', item: '时间差异', value: formatSeconds(evidenceTimeDeltaSeconds) }, { section: '证据链', item: '一致性结论', value: consistencyTitle }, @@ -962,9 +963,9 @@ export function VehicleDetail({ ]; for (const source of detail.sourceStatus ?? []) { rows.push( - { section: `来源 ${source.protocol}`, item: '在线', value: source.online ? '在线' : '离线' }, - { section: `来源 ${source.protocol}`, item: '最后时间', value: reportText(source.lastSeen) }, - { section: `来源 ${source.protocol}`, item: '能力', value: sourceCapabilities.filter((item) => source[item.key]).map((item) => item.label).join('|') || '-' } + { section: `证据来源 ${source.protocol}`, item: '在线', value: source.online ? '在线' : '离线' }, + { section: `证据来源 ${source.protocol}`, item: '最后时间', value: reportText(source.lastSeen) }, + { section: `证据来源 ${source.protocol}`, item: '能力', value: sourceCapabilities.filter((item) => source[item.key]).map((item) => item.label).join('|') || '-' } ); } downloadCsv(vehicleReportFileName(displayVIN, activeProtocol), buildCsv(vehicleReportColumns, rows)); @@ -982,7 +983,7 @@ export function VehicleDetail({ load(nextQuery); }}>