feat(platform): refine mileage customer workflow
This commit is contained in:
@@ -75,7 +75,7 @@ function mileageActionRecommendation(row: DailyMileageRow) {
|
||||
return {
|
||||
label: '可用于日报统计',
|
||||
color: 'green' as const,
|
||||
detail: '该日里程未发现异常,可进入车辆服务查看来源证据。'
|
||||
detail: '该日里程未发现异常,可进入车辆服务查看统计依据。'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ function mileageSummaryText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const dateFrom = filters.dateFrom?.trim();
|
||||
const dateTo = filters.dateTo?.trim();
|
||||
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部日期';
|
||||
@@ -163,7 +163,7 @@ function mileageSummaryText({
|
||||
return [
|
||||
'【里程统计摘要】',
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`日期范围:${dateRange}`,
|
||||
`统计车辆:${summary.vehicleCount.toLocaleString()},记录:${summary.recordCount.toLocaleString()},来源:${summary.sourceCount.toLocaleString()}`,
|
||||
`累计里程:${formatKm(summary.totalMileageKm)} km`,
|
||||
@@ -207,7 +207,7 @@ function mileageDeliveryPackageText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const dateFrom = filters.dateFrom?.trim();
|
||||
const dateTo = filters.dateTo?.trim();
|
||||
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部日期';
|
||||
@@ -221,7 +221,7 @@ function mileageDeliveryPackageText({
|
||||
'【客户里程复核交付包】',
|
||||
`交付状态:${deliveryState}`,
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`日期范围:${dateRange}`,
|
||||
`累计里程:${formatKm(summary.totalMileageKm)} km`,
|
||||
`当前页明细合计:${formatKm(pageMileageTotal)} km`,
|
||||
@@ -235,7 +235,7 @@ function mileageDeliveryPackageText({
|
||||
'统计口径:日里程按首末总里程差值计算,区间总值应等于每日里程之和',
|
||||
`里程统计:${statisticsURL}`,
|
||||
`轨迹复核:${appURL(buildAppHash({ page: 'history', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}) } }))}`,
|
||||
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), tab: 'raw', includeFields: 'true' } }))}`,
|
||||
`历史明细:${appURL(buildAppHash({ page: 'history-query', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), tab: 'raw', includeFields: 'true' } }))}`,
|
||||
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
|
||||
].join('\n');
|
||||
}
|
||||
@@ -270,7 +270,7 @@ function mileageCustomerDecisionText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const dateFrom = filters.dateFrom?.trim();
|
||||
const dateTo = filters.dateTo?.trim();
|
||||
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部日期';
|
||||
@@ -284,7 +284,7 @@ function mileageCustomerDecisionText({
|
||||
'【客户里程决策说明】',
|
||||
`决策结论:${deliveryState}`,
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`时间范围:${dateRange}`,
|
||||
`区间总里程:${formatKm(summary.totalMileageKm)} km`,
|
||||
`当前明细合计:${formatKm(pageMileageTotal)} km`,
|
||||
@@ -298,11 +298,11 @@ function mileageCustomerDecisionText({
|
||||
'客户判断路径:',
|
||||
'1. 先看区间闭合:区间总值必须等于每日里程之和。',
|
||||
'2. 再看记录覆盖:发布前需要覆盖全量明细或导出全量。',
|
||||
'3. 再看异常记录:异常日必须回看轨迹和原始记录。',
|
||||
'3. 再看异常记录:异常日必须回看轨迹和历史明细。',
|
||||
'4. 最后看在线影响:离线车辆会影响当天里程解释。',
|
||||
`里程统计:${statisticsURL}`,
|
||||
`轨迹复核:${appURL(buildAppHash({ page: 'history', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}) } }))}`,
|
||||
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), tab: 'raw', includeFields: 'true' } }))}`,
|
||||
`历史明细:${appURL(buildAppHash({ page: 'history-query', keyword: filters.keyword?.trim() || '', protocol: filters.protocol?.trim() || '', filters: { ...(dateFrom ? { dateFrom } : {}), ...(dateTo ? { dateTo } : {}), tab: 'raw', includeFields: 'true' } }))}`,
|
||||
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
|
||||
].join('\n');
|
||||
}
|
||||
@@ -333,7 +333,7 @@ function mileageBIPublishText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const dateFrom = filters.dateFrom?.trim();
|
||||
const dateTo = filters.dateTo?.trim();
|
||||
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部日期';
|
||||
@@ -342,7 +342,7 @@ function mileageBIPublishText({
|
||||
'【BI里程发布口径】',
|
||||
`发布结论:${publishable ? '可发布' : '需复核后发布'}`,
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`日期范围:${dateRange}`,
|
||||
`统计口径:日里程按首末总里程差值计算,区间总值等于每日里程之和`,
|
||||
`累计里程:${formatKm(summary.totalMileageKm)} km`,
|
||||
@@ -350,9 +350,9 @@ function mileageBIPublishText({
|
||||
`闭合状态:${closureStatus}${closureDeltaKm == null ? '' : `,差值 ${formatKm(Math.abs(closureDeltaKm))} km`}`,
|
||||
`记录覆盖率:${formatPercent(pageCoverageRate)}`,
|
||||
`异常记录:${anomalyCount.toLocaleString()}`,
|
||||
`来源一致性:${sourceConsistencyText}`,
|
||||
`通道一致性:${sourceConsistencyText}`,
|
||||
`统计车辆:${summary.vehicleCount.toLocaleString()},记录:${summary.recordCount.toLocaleString()},来源:${summary.sourceCount.toLocaleString()}`,
|
||||
`发布风险:${publishable ? '当前筛选范围闭合且无异常记录' : '存在分页未全量、异常记录或闭合差值,需要先复核轨迹和原始记录'}`,
|
||||
`发布风险:${publishable ? '当前筛选范围闭合且无异常记录' : '存在分页未全量、异常记录或闭合差值,需要先复核轨迹和历史明细'}`,
|
||||
`统计查询:${statisticsURL}`,
|
||||
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
|
||||
].join('\n');
|
||||
@@ -382,14 +382,14 @@ function statisticsImpactText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const dateFrom = filters.dateFrom?.trim();
|
||||
const dateTo = filters.dateTo?.trim();
|
||||
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'} 至 ${dateTo || '-'}` : '全部日期';
|
||||
return [
|
||||
'【统计影响范围】',
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`日期范围:${dateRange}`,
|
||||
`影响车辆:${summary.vehicleCount.toLocaleString()} 辆`,
|
||||
`统计记录:${summary.recordCount.toLocaleString()} 条`,
|
||||
@@ -400,7 +400,7 @@ function statisticsImpactText({
|
||||
`可信度:${confidenceStatus}`,
|
||||
`异常记录:${anomalyCount.toLocaleString()}`,
|
||||
`当前页覆盖:${formatPercent(pageCoverageRate)}`,
|
||||
`业务影响:${publishAuditConclusion === '可发布' ? '当前统计范围可进入运营和BI口径' : '当前统计范围需要先复核轨迹、原始记录和离线车辆影响'}`,
|
||||
`业务影响:${publishAuditConclusion === '可发布' ? '当前统计范围可进入运营和BI口径' : '当前统计范围需要先复核轨迹、历史明细和离线车辆影响'}`,
|
||||
`统计查询:${statisticsURL}`,
|
||||
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
|
||||
].join('\n');
|
||||
@@ -444,13 +444,13 @@ function mileagePublishAuditReport({
|
||||
const review = items.filter((item) => item.status === 'review').length;
|
||||
const pass = items.filter((item) => item.status === 'pass').length;
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
return [
|
||||
'【统计发布审计】',
|
||||
`发布结论:${blocked > 0 ? '阻断发布' : review > 0 ? '复核后发布' : '可发布'}`,
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`统计规模:${summary.vehicleCount.toLocaleString()} 车 / ${summary.recordCount.toLocaleString()} 条 / ${summary.sourceCount.toLocaleString()} 来源`,
|
||||
`数据通道:${protocol}`,
|
||||
`统计规模:${summary.vehicleCount.toLocaleString()} 车 / ${summary.recordCount.toLocaleString()} 条 / ${summary.sourceCount.toLocaleString()} 数据通道`,
|
||||
`审计结果:通过 ${pass.toLocaleString()} / 复核 ${review.toLocaleString()} / 阻断 ${blocked.toLocaleString()}`,
|
||||
'',
|
||||
...items.map((item, index) => [
|
||||
@@ -473,9 +473,9 @@ function mileageEvidencePackageText(row: DailyMileageRow) {
|
||||
};
|
||||
const vehicle = row.plate?.trim() ? `${row.plate.trim()} / ${row.vin}` : row.vin;
|
||||
return [
|
||||
'【里程异常证据包】',
|
||||
'【里程异常复核包】',
|
||||
`车辆:${vehicle}`,
|
||||
`证据来源:${row.source || '未知来源'}`,
|
||||
`数据通道:${row.source || '未知通道'}`,
|
||||
`统计日期:${row.date || '-'}`,
|
||||
`起始里程:${formatKm(row.startMileageKm)} km`,
|
||||
`结束里程:${formatKm(row.endMileageKm)} km`,
|
||||
@@ -484,7 +484,7 @@ function mileageEvidencePackageText(row: DailyMileageRow) {
|
||||
`处置建议:${mileageActionRecommendation(row).detail}`,
|
||||
`车辆服务:${appURL(buildAppHash({ page: 'detail', keyword: row.vin, protocol: row.source }))}`,
|
||||
`轨迹证据:${appURL(buildAppHash({ page: 'history', keyword: row.vin, protocol: row.source, filters: evidenceFilters }))}`,
|
||||
`原始记录:${appURL(buildAppHash({ page: 'history-query', keyword: row.vin, protocol: row.source, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
|
||||
`历史明细:${appURL(buildAppHash({ page: 'history-query', keyword: row.vin, protocol: row.source, filters: { ...evidenceFilters, tab: 'raw', includeFields: 'true' } }))}`,
|
||||
`统计查询:${appURL(buildAppHash({ page: 'mileage', keyword: row.vin, protocol: row.source, filters: evidenceFilters }))}`
|
||||
].join('\n');
|
||||
}
|
||||
@@ -501,7 +501,7 @@ function onlineStatusHandoffText({
|
||||
total: number;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const handoffRows = Array.isArray(rows) ? rows : [];
|
||||
const offlineRows = handoffRows.filter((row) => !row.online);
|
||||
const protocolStats = Array.isArray(summary.protocolStats) ? summary.protocolStats : [];
|
||||
@@ -511,7 +511,7 @@ function onlineStatusHandoffText({
|
||||
return [
|
||||
'【车辆在线状态交接】',
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`在线概览:${summary.onlineVehicleCount.toLocaleString()} 在线 / ${summary.offlineVehicleCount.toLocaleString()} 离线 / 在线率 ${formatPercent(summary.onlineRatePercent)}`,
|
||||
`Redis 在线 Key:${summary.redisOnlineKeys == null ? '-' : summary.redisOnlineKeys.toLocaleString()}`,
|
||||
`来源在线:${protocolLines}`,
|
||||
@@ -545,7 +545,7 @@ function onlineOperationsReportText({
|
||||
vehicleURL?: string;
|
||||
}) {
|
||||
const keyword = filters.keyword?.trim() || '全部车辆';
|
||||
const protocol = filters.protocol?.trim() || '全部来源';
|
||||
const protocol = filters.protocol?.trim() || '全部数据通道';
|
||||
const protocolStats = Array.isArray(summary.protocolStats) ? summary.protocolStats : [];
|
||||
const offlineRows = onlineRows.filter((row) => !row.online);
|
||||
const longestOffline = offlineRows.reduce<OnlineVehicleStatusRow | undefined>((current, row) => {
|
||||
@@ -555,7 +555,7 @@ function onlineOperationsReportText({
|
||||
return [
|
||||
'【在线统计运营态势】',
|
||||
`车辆范围:${keyword}`,
|
||||
`证据来源:${protocol}`,
|
||||
`数据通道:${protocol}`,
|
||||
`在线概览:${summary.onlineVehicleCount.toLocaleString()} 在线 / ${summary.offlineVehicleCount.toLocaleString()} 离线 / 在线率 ${formatPercent(summary.onlineRatePercent)}`,
|
||||
`Redis 在线 Key:${summary.redisOnlineKeys == null ? '-' : summary.redisOnlineKeys.toLocaleString()}`,
|
||||
`统计证据:${summary.evidence || '-'}`,
|
||||
@@ -571,7 +571,7 @@ function onlineOperationsReportText({
|
||||
'',
|
||||
'处置建议:',
|
||||
'1. 在线率下降时先检查实时在线状态、数据接入和最后上报时间',
|
||||
'2. 离线车辆优先打开车辆服务,核对实时、轨迹、原始记录和告警证据',
|
||||
'2. 离线车辆优先打开车辆服务,核对实时、轨迹、历史明细和告警记录',
|
||||
'3. 统计发布前确认离线车辆是否影响当日里程和区间闭合',
|
||||
`统计查询:${statisticsURL}`,
|
||||
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
|
||||
@@ -641,8 +641,8 @@ export function Mileage({
|
||||
: '汇总总里程与明细合计不一致,请核对分页、来源和异常记录。';
|
||||
const confidenceStatus = anomalyRows.length > 0 || pageCoverageRate < 100 ? '需复核' : '可用于 BI';
|
||||
const confidenceColor = confidenceStatus === '可用于 BI' ? 'green' as const : 'orange' as const;
|
||||
const currentEvidenceText = `${summary.vehicleCount.toLocaleString()} 车 / ${summary.sourceCount.toLocaleString()} 来源 / ${rows.length.toLocaleString()} 条明细`;
|
||||
const sourceConsistencyText = summary.sourceCount > 1 ? '可做跨来源核对' : summary.sourceCount === 1 ? '单来源车辆需关注' : '等待来源证据';
|
||||
const currentEvidenceText = `${summary.vehicleCount.toLocaleString()} 车 / ${summary.sourceCount.toLocaleString()} 数据通道 / ${rows.length.toLocaleString()} 条明细`;
|
||||
const sourceConsistencyText = summary.sourceCount > 1 ? '可做跨通道核对' : summary.sourceCount === 1 ? '单通道车辆需关注' : '等待数据通道';
|
||||
const onlineVehicleCount = Number(onlineSummary.onlineVehicleCount ?? 0);
|
||||
const offlineVehicleCount = Number(onlineSummary.offlineVehicleCount ?? 0);
|
||||
const onlineRatePercent = Number(onlineSummary.onlineRatePercent ?? 0);
|
||||
@@ -681,15 +681,15 @@ export function Mileage({
|
||||
status: anomalyRows.length > 0 ? 'review' : 'pass',
|
||||
value: anomalyRows.length.toLocaleString(),
|
||||
detail: anomalyRows.length > 0 ? '存在日里程异常标记,需核对首末总里程、补传和断链。' : '当前筛选范围未发现异常里程记录。',
|
||||
action: anomalyRows.length > 0 ? '优先打开异常行的轨迹和原始记录复核。' : '继续检查来源一致性和在线证据。'
|
||||
action: anomalyRows.length > 0 ? '优先打开异常行的轨迹和历史明细复核。' : '继续检查通道一致性和在线状态。'
|
||||
},
|
||||
{
|
||||
key: 'source',
|
||||
label: '来源一致性',
|
||||
label: '通道一致性',
|
||||
status: summary.sourceCount > 1 ? 'pass' : summary.sourceCount === 1 ? 'review' : 'blocked',
|
||||
value: sourceConsistencyText,
|
||||
detail: summary.sourceCount > 1 ? '可使用多来源交叉验证车辆统计。' : summary.sourceCount === 1 ? '只有单一来源,统计可用但可信度低于多来源。' : '当前统计缺少来源证据。',
|
||||
action: summary.sourceCount > 1 ? '保留来源贡献作为审计附件。' : '核对 32960、808、MQTT 是否有缺失来源。'
|
||||
detail: summary.sourceCount > 1 ? '可使用多通道交叉验证车辆统计。' : summary.sourceCount === 1 ? '只有单一通道,统计可用但可信度低于多通道。' : '当前统计缺少数据通道。',
|
||||
action: summary.sourceCount > 1 ? '保留通道贡献作为审计附件。' : '核对 32960、808、MQTT 是否有缺失通道。'
|
||||
},
|
||||
{
|
||||
key: 'online',
|
||||
@@ -772,7 +772,7 @@ export function Mileage({
|
||||
value: `${anomalyRows.length.toLocaleString()} 条`,
|
||||
detail: anomalyRows[0] ? `${anomalyRows[0].plate || anomalyRows[0].vin} / ${anomalyRows[0].date} 需要回看轨迹。` : '当前明细未命中异常标记。',
|
||||
color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const,
|
||||
action: '异常证据',
|
||||
action: '异常明细',
|
||||
onClick: () => anomalyRows[0] ? copyMileageEvidence(anomalyRows[0]) : copyMileageDecision()
|
||||
},
|
||||
{
|
||||
@@ -788,7 +788,7 @@ export function Mileage({
|
||||
const maxSourceMileage = Math.max(...sourceSeries.map((item) => item.value), 0);
|
||||
const filterSummary = [
|
||||
currentVehicleKeyword ? `车辆:${currentVehicleKeyword}` : '',
|
||||
currentProtocol ? `证据来源:${currentProtocol}` : '',
|
||||
currentProtocol ? `数据通道:${currentProtocol}` : '',
|
||||
filters.dateFrom?.trim() ? `开始日期:${filters.dateFrom.trim()}` : '',
|
||||
filters.dateTo?.trim() ? `结束日期:${filters.dateTo.trim()}` : ''
|
||||
].filter(Boolean);
|
||||
@@ -812,7 +812,7 @@ export function Mileage({
|
||||
label: '异常记录',
|
||||
value: anomalyRows.length.toLocaleString(),
|
||||
color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const,
|
||||
detail: anomalyRows.length > 0 ? '建议先核对轨迹和原始记录' : '当前页未发现异常'
|
||||
detail: anomalyRows.length > 0 ? '建议先核对轨迹和历史明细' : '当前页未发现异常'
|
||||
},
|
||||
{
|
||||
label: '在线影响',
|
||||
@@ -833,7 +833,7 @@ export function Mileage({
|
||||
action: () => onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' })
|
||||
},
|
||||
{
|
||||
label: '查看原始记录',
|
||||
label: '查看历史明细',
|
||||
disabled: !currentVehicleKeyword || !onOpenRaw,
|
||||
action: () => onOpenRaw?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '', includeFields: 'true' })
|
||||
},
|
||||
@@ -848,7 +848,7 @@ export function Mileage({
|
||||
step: '01',
|
||||
title: '选择范围',
|
||||
value: currentVehicleKeyword || '全部车辆',
|
||||
detail: `时间范围 ${mileageRangeText},来源 ${currentProtocol || '全部来源'}。`,
|
||||
detail: `时间范围 ${mileageRangeText},数据通道 ${currentProtocol || '全部数据通道'}。`,
|
||||
action: '调整筛选',
|
||||
color: currentVehicleKeyword ? 'green' as const : 'blue' as const,
|
||||
disabled: false,
|
||||
@@ -881,7 +881,7 @@ export function Mileage({
|
||||
step: '04',
|
||||
title: '复核证据',
|
||||
value: anomalyRows.length > 0 ? `${anomalyRows.length.toLocaleString()} 异常` : '轨迹/原始',
|
||||
detail: anomalyRows.length > 0 ? '异常日优先回看轨迹和原始记录。' : '可从同一时间窗进入轨迹和原始记录。',
|
||||
detail: anomalyRows.length > 0 ? '异常日优先回看轨迹和历史明细。' : '可从同一时间窗进入轨迹和历史明细。',
|
||||
action: '轨迹复核',
|
||||
color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const,
|
||||
disabled: !currentVehicleKeyword || !onOpenHistory,
|
||||
@@ -889,7 +889,7 @@ export function Mileage({
|
||||
},
|
||||
{
|
||||
step: '05',
|
||||
title: '数据交付',
|
||||
title: '明细导出',
|
||||
value: `${rows.length.toLocaleString()} 当前页`,
|
||||
detail: '导出明细并附带统计口径,支撑BI、客户复盘和运营日报。',
|
||||
action: '导出明细',
|
||||
@@ -1144,7 +1144,7 @@ export function Mileage({
|
||||
detail: `范围 ${mileageRangeText},${closureActionText}`,
|
||||
color: closureStatusColor,
|
||||
primaryAction: '轨迹证据',
|
||||
secondaryAction: '原始记录',
|
||||
secondaryAction: '历史明细',
|
||||
disabled: !currentVehicleKeyword || !onOpenHistory,
|
||||
secondaryDisabled: !currentVehicleKeyword || !onOpenRaw,
|
||||
onPrimary: () => onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' }),
|
||||
@@ -1163,7 +1163,7 @@ export function Mileage({
|
||||
onSecondary: () => currentVehicleKeyword && onOpenVehicle(currentVehicleKeyword, currentProtocol)
|
||||
},
|
||||
{
|
||||
title: '数据交付',
|
||||
title: '明细导出',
|
||||
value: `${rows.length.toLocaleString()} 条当前页`,
|
||||
detail: `累计 ${formatKm(summary.totalMileageKm)} km,当前页 ${formatKm(pageMileageTotal)} km。`,
|
||||
color: 'blue' as const,
|
||||
@@ -1197,8 +1197,8 @@ export function Mileage({
|
||||
/>
|
||||
<div className="vp-scope-bar">
|
||||
<span className="vp-scope-label">当前车辆:{currentVehicleKeyword || '-'}</span>
|
||||
<Tag color={currentProtocol ? 'blue' : 'green'}>当前来源:{currentProtocol || '全部来源'}</Tag>
|
||||
<Typography.Text type="tertiary">里程统计按当前车辆与来源范围汇总。</Typography.Text>
|
||||
<Tag color={currentProtocol ? 'blue' : 'green'}>数据通道:{currentProtocol || '全部数据通道'}</Tag>
|
||||
<Typography.Text type="tertiary">里程统计按当前车辆与数据通道范围汇总。</Typography.Text>
|
||||
</div>
|
||||
<Card bordered className="vp-mileage-customer-query-board" bodyStyle={{ padding: 0 }}>
|
||||
<div className="vp-mileage-customer-query-summary">
|
||||
@@ -1209,7 +1209,7 @@ export function Mileage({
|
||||
</Space>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>先定车辆和时间范围,再判断里程能否交付</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
统计查询从车辆服务出发:选择车辆或车队、设定时间窗、判断区间里程和发布状态,再进入轨迹、原始记录和导出明细。
|
||||
统计查询从车辆服务出发:选择车辆或车队、设定时间窗、判断区间里程和发布状态,再进入轨迹、历史明细和导出明细。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" theme="solid" type="primary" onClick={copyStatisticsSummary}>复制统计摘要</Button>
|
||||
@@ -1263,13 +1263,13 @@ export function Mileage({
|
||||
title: '数据完整性',
|
||||
value: confidenceStatus,
|
||||
color: confidenceColor,
|
||||
detail: '围绕位置、里程、SOC、速度和来源新鲜度判断统计可信度。'
|
||||
detail: '围绕位置、里程、SOC、速度和数据通道新鲜度判断统计可信度。'
|
||||
},
|
||||
{
|
||||
title: '来源一致性',
|
||||
title: '通道一致性',
|
||||
value: sourceConsistencyText,
|
||||
color: summary.sourceCount > 1 ? 'green' as const : 'orange' as const,
|
||||
detail: 'GB32960、JT808、Yutong MQTT 只作为同一车辆服务的来源证据。'
|
||||
detail: 'GB32960、JT808、Yutong MQTT 只作为同一车辆服务的数据通道。'
|
||||
}
|
||||
].map((item) => (
|
||||
<div key={item.title} className="vp-monitor-metric">
|
||||
@@ -1292,14 +1292,14 @@ export function Mileage({
|
||||
<Tag color={publishAuditColor}>{publishAuditConclusion}</Tag>
|
||||
<Tag color={confidenceColor}>{confidenceStatus}</Tag>
|
||||
</Space>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>先判断能不能交付,再进入轨迹、原始记录和导出复核</Typography.Title>
|
||||
<Typography.Title heading={5} style={{ margin: 0 }}>先判断能不能交付,再进入轨迹、历史明细和导出复核</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
客户关心的是区间里程是否可信、异常是否解释清楚、离线是否影响统计。数据来源只作为可追溯证据,不作为页面主入口。
|
||||
客户关心的是区间里程是否可信、异常是否解释清楚、离线是否影响统计。数据通道只作为可追溯依据,不作为页面主入口。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" theme="solid" type="primary" onClick={copyMileageDecision}>复制决策说明</Button>
|
||||
<Button size="small" disabled={!currentVehicleKeyword || !onOpenHistory} onClick={() => onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' })}>轨迹复核</Button>
|
||||
<Button size="small" disabled={!currentVehicleKeyword || !onOpenRaw} onClick={() => onOpenRaw?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '', includeFields: 'true' })}>原始记录</Button>
|
||||
<Button size="small" disabled={!currentVehicleKeyword || !onOpenRaw} onClick={() => onOpenRaw?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '', includeFields: 'true' })}>历史明细</Button>
|
||||
<Button size="small" disabled={rows.length === 0} onClick={exportMileage}>导出明细</Button>
|
||||
</Space>
|
||||
</div>
|
||||
@@ -1330,7 +1330,7 @@ export function Mileage({
|
||||
</Space>
|
||||
<div className="vp-monitor-metric-value">{formatKm(summary.totalMileageKm)} km</div>
|
||||
<Typography.Text type="secondary">
|
||||
面向客户交付里程时,先确认区间里程、闭合状态、异常记录和在线影响,再导出明细或进入轨迹与原始记录复核。
|
||||
面向客户交付里程时,先确认区间里程、闭合状态、异常记录和在线影响,再导出明细或进入轨迹与历史明细复核。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" theme="solid" type="primary" onClick={copyMileageDeliveryPackage}>复制交付包</Button>
|
||||
@@ -1367,7 +1367,7 @@ export function Mileage({
|
||||
value: anomalyRows.length.toLocaleString(),
|
||||
detail: anomalyRows[0] ? `${anomalyRows[0].plate || anomalyRows[0].vin} / ${anomalyRows[0].date} 需要复核。` : '当前页没有异常里程记录。',
|
||||
color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const,
|
||||
action: '异常证据',
|
||||
action: '异常明细',
|
||||
onClick: () => anomalyRows[0] ? copyMileageEvidence(anomalyRows[0]) : copyMileageDeliveryPackage
|
||||
},
|
||||
{
|
||||
@@ -1467,7 +1467,7 @@ export function Mileage({
|
||||
applyFilters(nextFilters);
|
||||
}}>
|
||||
<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="JT808">JT808</Select.Option>
|
||||
<Select.Option value="YUTONG_MQTT">YUTONG_MQTT</Select.Option>
|
||||
@@ -1677,7 +1677,7 @@ export function Mileage({
|
||||
<div className="vp-stat-closure-action">
|
||||
<Tag color={closureStatusColor}>{closureStatus}</Tag>
|
||||
<Typography.Text type="secondary">
|
||||
{closurePassed && anomalyRows.length === 0 ? '当前统计口径可直接用于 BI 里程口径;若存在跨来源差异,再进入车辆服务查看来源证据。' : '统计进入 BI 前需要结合异常记录、轨迹回放和原始记录字段完成复核。'}
|
||||
{closurePassed && anomalyRows.length === 0 ? '当前统计口径可直接用于 BI 里程口径;若存在跨通道差异,再进入车辆服务查看统计依据。' : '统计进入 BI 前需要结合异常记录、轨迹回放和历史明细完成复核。'}
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" onClick={copyStatisticsSummary}>复制闭合摘要</Button>
|
||||
@@ -1706,14 +1706,14 @@ export function Mileage({
|
||||
{confidenceStatus === '可用于 BI' && closureStatus === '闭合通过' ? '可发布' : '需复核后发布'}
|
||||
</div>
|
||||
<Typography.Text type="secondary">
|
||||
日里程按首末总里程差值计算,区间总值必须等于每日里程之和;发布前同步检查异常记录、分页覆盖和来源一致性。
|
||||
日里程按首末总里程差值计算,区间总值必须等于每日里程之和;发布前同步检查异常记录、分页覆盖和通道一致性。
|
||||
</Typography.Text>
|
||||
</div>
|
||||
<div className="vp-bi-publish-grid">
|
||||
<div><span>闭合状态</span><strong>{closureStatus}</strong></div>
|
||||
<div><span>记录覆盖</span><strong>{formatPercent(pageCoverageRate)}</strong></div>
|
||||
<div><span>异常记录</span><strong>{anomalyRows.length.toLocaleString()}</strong></div>
|
||||
<div><span>来源一致性</span><strong>{sourceConsistencyText}</strong></div>
|
||||
<div><span>通道一致性</span><strong>{sourceConsistencyText}</strong></div>
|
||||
</div>
|
||||
<Space wrap>
|
||||
<Button size="small" onClick={copyBIPublishText}>复制BI发布口径</Button>
|
||||
@@ -1729,7 +1729,7 @@ export function Mileage({
|
||||
通过 {publishAuditItems.length - publishReviewCount - publishBlockedCount} / 复核 {publishReviewCount} / 阻断 {publishBlockedCount}
|
||||
</div>
|
||||
<Typography.Text type="secondary">
|
||||
发布前统一审计闭合、覆盖、异常、来源和在线证据;不满足时先回到轨迹和原始记录复核。
|
||||
发布前统一审计闭合、覆盖、异常、数据通道和在线状态;不满足时先回到轨迹和历史明细复核。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button size="small" onClick={copyPublishAudit}>复制统计发布审计</Button>
|
||||
@@ -1785,7 +1785,7 @@ export function Mileage({
|
||||
</div>
|
||||
<div className="vp-stat-insight-grid">
|
||||
{[
|
||||
{ label: '统计可信度', value: confidenceStatus, color: confidenceColor, detail: anomalyRows.length > 0 ? '存在异常里程记录,建议先核对来源证据。' : '当前分页未发现异常记录。' },
|
||||
{ label: '统计可信度', value: confidenceStatus, color: confidenceColor, detail: anomalyRows.length > 0 ? '存在异常里程记录,建议先核对统计依据。' : '当前分页未发现异常记录。' },
|
||||
{ label: '异常率', value: formatPercent(anomalyRate), color: anomalyRows.length > 0 ? 'orange' as const : 'green' as const, detail: `${anomalyRows.length.toLocaleString()} / ${rows.length.toLocaleString()} 条明细命中异常标记。` },
|
||||
{ label: '最大单日占比', value: formatPercent(peakMileageShare), color: peakMileageShare > 50 ? 'orange' as const : 'blue' as const, detail: peakMileage ? `${peakMileage.plate || peakMileage.vin} 贡献最高单日里程。` : '暂无明细。' },
|
||||
{ label: '当前页里程', value: `${formatKm(pageMileageTotal)} km`, color: 'blue' as const, detail: '用于快速核对当前分页明细合计。' },
|
||||
@@ -1885,7 +1885,7 @@ export function Mileage({
|
||||
</div>
|
||||
<div>
|
||||
<Tag color="orange">异常复核</Tag>
|
||||
<p>异常优先检查补传、来源切换、总里程回退和跨来源差异,必要时回到车辆服务查看原始记录。</p>
|
||||
<p>异常优先检查补传、通道切换、总里程回退和跨通道差异,必要时回到车辆服务查看历史明细。</p>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -2482,7 +2482,7 @@ test('shows and clears current vehicle service filters', async () => {
|
||||
|
||||
expect(await screen.findByText('当前车辆筛选')).toBeInTheDocument();
|
||||
expect(screen.getByText('关键词:粤A')).toBeInTheDocument();
|
||||
expect(screen.getByText('证据来源:JT808')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:JT808').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('车辆覆盖:多源')).toBeInTheDocument();
|
||||
expect(screen.getByText('缺失来源:YUTONG_MQTT')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务状态:来源不完整')).toBeInTheDocument();
|
||||
@@ -5437,7 +5437,7 @@ test('copies trajectory playback summary from history page', async () => {
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【轨迹回放摘要】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:VIN-HISTORY-SUMMARY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('查询范围:2026-07-03 至 2026-07-04'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹点:2,有效定位:2'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('区间里程:22.6 km'));
|
||||
@@ -5463,7 +5463,7 @@ test('copies trajectory playback summary from history page', async () => {
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【轨迹运营影响】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:VIN-HISTORY-SUMMARY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('业务状态:需要复核'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹范围:位置 2 / 有效定位 2 / 原始记录 1 / 字段明细 2'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('定位覆盖率:100%'));
|
||||
@@ -6376,7 +6376,7 @@ test('shows and clears current history filters while keeping vehicle scope', asy
|
||||
|
||||
expect(await screen.findByText('当前历史筛选')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆:VIN-HISTORY-001')).toBeInTheDocument();
|
||||
expect(screen.getByText('证据来源:JT808')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:JT808').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('开始时间:2026-07-01 00:00:00')).toBeInTheDocument();
|
||||
expect(screen.getByText('结束时间:2026-07-01 23:59:59')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('返回字段明细').length).toBeGreaterThanOrEqual(2);
|
||||
@@ -7249,7 +7249,7 @@ test('shows vehicle and source scope on mileage hash', async () => {
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText('当前车辆:VIN-MILEAGE-001')).toBeInTheDocument();
|
||||
expect(screen.getByText('当前来源:GB32960')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:GB32960').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('applies mileage date range from shareable hash to API requests', async () => {
|
||||
@@ -7294,7 +7294,7 @@ test('applies mileage date range from shareable hash to API requests', async ()
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('dateFrom=2026-07-01'), undefined);
|
||||
expect(fetchMock).toHaveBeenCalledWith(expect.stringContaining('dateTo=2026-07-03'), undefined);
|
||||
expect(screen.getByText('当前车辆:VIN-MILEAGE-002')).toBeInTheDocument();
|
||||
expect(screen.getByText('当前来源:JT808')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:JT808').length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test('shows and clears current mileage filters while keeping vehicle scope', async () => {
|
||||
@@ -7335,7 +7335,7 @@ test('shows and clears current mileage filters while keeping vehicle scope', asy
|
||||
|
||||
expect(await screen.findByText('当前里程筛选')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆:VIN-MILEAGE-002')).toBeInTheDocument();
|
||||
expect(screen.getByText('证据来源:JT808')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:JT808').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('开始日期:2026-07-01')).toBeInTheDocument();
|
||||
expect(screen.getByText('结束日期:2026-07-03')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '清空筛选' }));
|
||||
@@ -7384,7 +7384,7 @@ test('updates mileage hash when mileage filters are submitted', async () => {
|
||||
|
||||
await screen.findByRole('heading', { name: '里程统计' });
|
||||
fireEvent.change(screen.getByPlaceholderText('VIN / 车牌 / 手机号 / OEM'), { target: { value: '粤AG18312' } });
|
||||
fireEvent.click(screen.getByText('全部来源'));
|
||||
fireEvent.click(screen.getByText('全部数据通道'));
|
||||
fireEvent.click(await screen.findByText('JT808'));
|
||||
fireEvent.change(screen.getByPlaceholderText('2026-07-01'), { target: { value: '2026-07-01' } });
|
||||
fireEvent.change(screen.getByPlaceholderText('2026-07-03'), { target: { value: '2026-07-03' } });
|
||||
@@ -7454,7 +7454,7 @@ test('opens current vehicle service from mileage header with current source evid
|
||||
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText('当前来源:GB32960')).toBeInTheDocument();
|
||||
expect((await screen.findAllByText('数据通道:GB32960')).length).toBeGreaterThan(0);
|
||||
fireEvent.click(screen.getByRole('button', { name: '当前车辆服务' }));
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -7773,15 +7773,15 @@ test('shows mileage anomaly action guidance', async () => {
|
||||
expect(screen.getByText('日里程异常,优先核对当天首末总里程、来源断链和补传数据。')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getAllByRole('button', { name: '复制证据' })[0]);
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【里程异常证据包】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【里程异常复核包】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆:粤A异常1 / VIN-MILEAGE-ANOMALY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:GB32960'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:GB32960'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计日期:2026-07-03'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('日里程:-3.2 km'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('异常等级:warning'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆服务:http://localhost:3000/#/detail?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('轨迹证据:http://localhost:3000/#/history?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('原始记录:http://localhost:3000/#/history-query?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('历史明细:http://localhost:3000/#/history-query?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&tab=raw&dateFrom=2026-07-03&dateTo=2026-07-04&includeFields=true'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计查询:http://localhost:3000/#/mileage?keyword=VIN-MILEAGE-ANOMALY&protocol=GB32960&dateFrom=2026-07-03&dateTo=2026-07-04'));
|
||||
});
|
||||
|
||||
@@ -8001,28 +8001,28 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
|
||||
expect(screen.getByRole('button', { name: '客户统计查询 区间里程 复制摘要' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户统计查询 发布判断 发布审计' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户统计查询 复核证据 轨迹复核' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户统计查询 数据交付 导出明细' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户统计查询 明细导出 导出明细' })).toBeInTheDocument();
|
||||
expect(await screen.findByText('车辆统计服务总览')).toBeInTheDocument();
|
||||
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.getByText('三类数据源最终汇总为一个车辆服务统计视图')).toBeInTheDocument();
|
||||
expect(screen.getByText('当前统计证据')).toBeInTheDocument();
|
||||
expect(screen.getByText('3 车 / 2 来源 / 3 条明细')).toBeInTheDocument();
|
||||
expect(screen.getByText('3 车 / 2 数据通道 / 3 条明细')).toBeInTheDocument();
|
||||
expect(screen.getByText('客户里程决策台')).toBeInTheDocument();
|
||||
expect(screen.getByText('先判断能不能交付,再进入轨迹、原始记录和导出复核')).toBeInTheDocument();
|
||||
expect(screen.getByText('先判断能不能交付,再进入轨迹、历史明细和导出复核')).toBeInTheDocument();
|
||||
expect(screen.getAllByRole('button', { name: '复制决策说明' }).length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByRole('button', { name: '客户里程决策 交付结论 复制决策' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户里程决策 区间闭合 统计摘要' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户里程决策 覆盖率 导出明细' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '客户里程决策 异常复核 异常证据' })).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.getByRole('button', { name: '里程交付 区间里程 复制交付' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程交付 闭合校验 闭合摘要' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程交付 异常记录 异常证据' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程交付 异常记录 异常明细' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程交付 在线影响 在线态势' })).toBeInTheDocument();
|
||||
expect(screen.getByText('统计影响范围')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '复制影响范围' })).toBeInTheDocument();
|
||||
@@ -8033,11 +8033,11 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
|
||||
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.getByRole('button', { name: '里程服务任务 发布判断 BI口径' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程服务任务 区间复核 轨迹证据' })).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: '里程服务任务 异常处理 复制证据' })).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();
|
||||
@@ -8057,13 +8057,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
|
||||
fireEvent.click(screen.getByRole('button', { name: '复制影响范围' }));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【统计影响范围】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-STATS-SERVICE'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('影响车辆:3 辆'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('统计记录:6 条'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('累计里程:210 km'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线状态:3 在线 / 1 离线 / 在线率 75%'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('发布判断:复核后发布'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('业务影响:当前统计范围需要先复核轨迹、原始记录和离线车辆影响'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('业务影响:当前统计范围需要先复核轨迹、历史明细和离线车辆影响'));
|
||||
fireEvent.click(screen.getAllByRole('button', { name: '复制决策说明' })[0]);
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户里程决策说明】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('决策结论:复核后交付'));
|
||||
@@ -8072,13 +8072,13 @@ test('shows vehicle-first statistics domains for one vehicle service', async ()
|
||||
fireEvent.click(screen.getByRole('button', { name: '复制在线态势' }));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【在线统计运营态势】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-STATS-SERVICE'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('在线概览:3 在线 / 1 离线 / 在线率 75%'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('Redis 在线 Key:92'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前页车辆:2 / 当前筛选 2'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('最长离线:粤A离线1 / 2 小时 / 2026-07-03 18:12:10'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. JT808:2/3,在线率 66.7%'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('2. 离线车辆优先打开车辆服务,核对实时、轨迹、原始记录和告警证据'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('2. 离线车辆优先打开车辆服务,核对实时、轨迹、历史明细和告警记录'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '里程交付 区间里程 复制交付' }));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【客户里程复核交付包】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-STATS-SERVICE'));
|
||||
@@ -8165,7 +8165,7 @@ test('copies mileage statistics summary for operations reporting', async () => {
|
||||
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【里程统计摘要】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆范围:VIN-MILEAGE-COPY'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('证据来源:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('数据通道:JT808'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('日期范围:2026-07-01 至 2026-07-03'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('累计里程:140 km'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('当前页里程:140 km'));
|
||||
@@ -8181,8 +8181,8 @@ test('copies mileage statistics summary for operations reporting', async () => {
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('闭合状态:分页抽样'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('记录覆盖率:50%'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('异常记录:1'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('来源一致性:可做跨来源核对'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('发布风险:存在分页未全量、异常记录或闭合差值,需要先复核轨迹和原始记录'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('通道一致性:可做跨通道核对'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('发布风险:存在分页未全量、异常记录或闭合差值,需要先复核轨迹和历史明细'));
|
||||
fireEvent.click(screen.getByRole('button', { name: '复制统计发布审计' }));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【统计发布审计】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('发布结论:复核后发布'));
|
||||
@@ -8764,7 +8764,7 @@ test('opens current vehicle service from history header with current source evid
|
||||
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText('当前来源:JT808')).toBeInTheDocument();
|
||||
expect((await screen.findAllByText('数据通道:JT808')).length).toBeGreaterThan(0);
|
||||
fireEvent.click(screen.getByRole('button', { name: '当前车辆服务' }));
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -10319,7 +10319,7 @@ test('shows and clears current realtime service filters', async () => {
|
||||
|
||||
expect(await screen.findByText('当前实时筛选')).toBeInTheDocument();
|
||||
expect(screen.getByText('关键词:粤ART002')).toBeInTheDocument();
|
||||
expect(screen.getByText('数据通道:JT808')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('数据通道:JT808').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('在线:在线')).toBeInTheDocument();
|
||||
expect(screen.getByText('服务状态:来源不完整')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: '清空筛选' }));
|
||||
|
||||
Reference in New Issue
Block a user