feat(platform): add mileage acceptance checklist

This commit is contained in:
lingniu
2026-07-06 03:51:30 +08:00
parent ab8ae961e6
commit 6f97181ec5
3 changed files with 229 additions and 1 deletions

View File

@@ -1117,6 +1117,71 @@ export function Mileage({
onClick: () => copyMileageDeliveryPackage()
}
];
const mileageAcceptanceItems = [
{
label: '范围确认',
value: currentVehicleKeyword || '全部车辆',
detail: `${mileageRangeText} / ${currentProtocol || '全部数据通道'}`,
color: currentVehicleKeyword ? 'blue' as const : 'orange' as const,
disabled: false,
onClick: () => copyStatisticsSummary()
},
{
label: '区间闭合',
value: closureStatus,
detail: closureDeltaKm == null ? '当前为分页抽样,交付前需要拉齐全量明细。' : `闭合差值 ${formatKm(Math.abs(closureDeltaKm))} km。`,
color: closureStatusColor,
disabled: false,
onClick: () => copyPublishAudit()
},
{
label: '明细覆盖',
value: formatPercent(pageCoverageRate),
detail: `${rows.length.toLocaleString()} / ${summary.recordCount.toLocaleString()} 条记录,覆盖全量后再交付更稳。`,
color: pageCoverageRate >= 100 ? 'green' as const : 'orange' as const,
disabled: rows.length === 0,
onClick: () => exportMileage()
},
{
label: '异常复核',
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,
disabled: false,
onClick: () => anomalyRows[0] ? openMileageEvidence(anomalyRows[0]) : copyMileageDecision()
},
{
label: '在线影响',
value: `${offlineVehicleCount.toLocaleString()} 离线`,
detail: offlineVehicleCount > 0 ? '离线车辆会影响当日解释和客户通知。' : '在线状态未形成主要统计风险。',
color: offlineVehicleCount > 0 ? 'orange' as const : 'green' as const,
disabled: false,
onClick: () => copyOnlineOperationsReport()
}
];
const mileageAcceptanceActions = [
{
label: '复制验收',
action: '交付说明',
color: mileageDeliveryColor,
disabled: false,
onClick: () => copyMileageDeliveryPackage()
},
{
label: '复核轨迹',
action: '轨迹',
color: currentVehicleKeyword ? 'blue' as const : 'grey' as const,
disabled: !currentVehicleKeyword || !onOpenHistory,
onClick: () => onOpenHistory?.({ keyword: currentVehicleKeyword, protocol: currentProtocol, dateFrom: filters.dateFrom ?? '', dateTo: filters.dateTo ?? '' })
},
{
label: '导出明细',
action: 'CSV',
color: rows.length > 0 ? 'blue' as const : 'grey' as const,
disabled: rows.length === 0,
onClick: () => exportMileage()
}
];
const loadSummary = (values: Record<string, string> = filters) => {
setSummaryLoading(true);
@@ -1953,6 +2018,52 @@ export function Mileage({
</div>
</div>
</Card>
<section className="vp-mileage-acceptance-strip" aria-label="里程客户验收清单">
<div className="vp-mileage-acceptance-copy">
<Space wrap>
<Tag color="blue"></Tag>
<Tag color={mileageDeliveryColor}>{mileageDeliveryState}</Tag>
<Tag color={publishAuditColor}>{publishAuditConclusion}</Tag>
</Space>
<Typography.Title heading={5} style={{ margin: 0 }}>
线
</Typography.Title>
<Typography.Text type="secondary">
</Typography.Text>
</div>
<div className="vp-mileage-acceptance-grid">
{mileageAcceptanceItems.map((item) => (
<button
key={item.label}
type="button"
className="vp-mileage-acceptance-item"
disabled={item.disabled}
onClick={item.onClick}
aria-label={`里程客户验收 ${item.label} ${item.value}`}
>
<Tag color={item.color}>{item.label}</Tag>
<strong>{item.value}</strong>
<span>{item.detail}</span>
</button>
))}
</div>
<div className="vp-mileage-acceptance-actions">
{mileageAcceptanceActions.map((item) => (
<button
key={item.label}
type="button"
className="vp-mileage-acceptance-action"
disabled={item.disabled}
onClick={item.onClick}
aria-label={`里程验收动作 ${item.label} ${item.action}`}
>
<Tag color={item.color}>{item.label}</Tag>
<span>{item.action}</span>
</button>
))}
</div>
</section>
<Card bordered title="客户里程问题" style={{ marginTop: 16 }}>
<div className="vp-mileage-question-grid">
{mileageCustomerQuestions.map((item) => (