feat(platform): add mileage BI publish package

This commit is contained in:
lingniu
2026-07-04 21:18:13 +08:00
parent 0fff9e3890
commit cd932dc097
3 changed files with 160 additions and 9 deletions

View File

@@ -177,6 +177,57 @@ function mileageSummaryText({
].join('\n');
}
function mileageBIPublishText({
filters,
summary,
pageMileageTotal,
pageCoverageRate,
anomalyCount,
closureStatus,
closureDeltaKm,
confidenceStatus,
sourceConsistencyText,
statisticsURL,
vehicleURL
}: {
filters: Record<string, string>;
summary: MileageSummary;
pageMileageTotal: number;
pageCoverageRate: number;
anomalyCount: number;
closureStatus: string;
closureDeltaKm?: number;
confidenceStatus: string;
sourceConsistencyText: string;
statisticsURL: string;
vehicleURL?: string;
}) {
const keyword = filters.keyword?.trim() || '全部车辆';
const protocol = filters.protocol?.trim() || '全部来源';
const dateFrom = filters.dateFrom?.trim();
const dateTo = filters.dateTo?.trim();
const dateRange = dateFrom || dateTo ? `${dateFrom || '-'}${dateTo || '-'}` : '全部日期';
const publishable = confidenceStatus === '可用于 BI' && closureStatus === '闭合通过';
return [
'【BI里程发布口径】',
`发布结论:${publishable ? '可发布' : '需复核后发布'}`,
`车辆范围:${keyword}`,
`数据来源:${protocol}`,
`日期范围:${dateRange}`,
`统计口径:日里程按首末总里程差值计算,区间总值等于每日里程之和`,
`累计里程:${formatKm(summary.totalMileageKm)} km`,
`明细合计:${formatKm(pageMileageTotal)} km`,
`闭合状态:${closureStatus}${closureDeltaKm == null ? '' : `,差值 ${formatKm(Math.abs(closureDeltaKm))} km`}`,
`记录覆盖率:${formatPercent(pageCoverageRate)}`,
`异常记录:${anomalyCount.toLocaleString()}`,
`来源一致性:${sourceConsistencyText}`,
`统计车辆:${summary.vehicleCount.toLocaleString()},记录:${summary.recordCount.toLocaleString()},来源:${summary.sourceCount.toLocaleString()}`,
`发布风险:${publishable ? '当前筛选范围闭合且无异常记录' : '存在分页未全量、异常记录或闭合差值,需要先复核轨迹和 RAW 证据'}`,
`统计查询:${statisticsURL}`,
...(vehicleURL ? [`车辆服务:${vehicleURL}`] : [])
].join('\n');
}
function mileageEvidencePackageText(row: DailyMileageRow) {
const dateFrom = row.date?.trim() ?? '';
const dateTo = nextDate(dateFrom);
@@ -215,9 +266,11 @@ function onlineStatusHandoffText({
}) {
const keyword = filters.keyword?.trim() || '全部车辆';
const protocol = filters.protocol?.trim() || '全部来源';
const offlineRows = rows.filter((row) => !row.online);
const protocolLines = (summary.protocolStats ?? []).length > 0
? summary.protocolStats.map((item) => `${item.protocol} ${item.online.toLocaleString()}/${item.total.toLocaleString()}`).join('')
const handoffRows = Array.isArray(rows) ? rows : [];
const offlineRows = handoffRows.filter((row) => !row.online);
const protocolStats = Array.isArray(summary.protocolStats) ? summary.protocolStats : [];
const protocolLines = protocolStats.length > 0
? protocolStats.map((item) => `${item.protocol} ${item.online.toLocaleString()}/${item.total.toLocaleString()}`).join('')
: '-';
return [
'【车辆在线状态交接】',
@@ -339,7 +392,7 @@ export function Mileage({
setLoading(true);
api.dailyMileage(mileageParams(values, pageSize, (page - 1) * pageSize))
.then((nextPage) => {
setRows(nextPage.items);
setRows(Array.isArray(nextPage.items) ? nextPage.items : []);
setPagination({ currentPage: page, pageSize, total: nextPage.total });
})
.catch((error: Error) => Toast.error(error.message))
@@ -350,7 +403,7 @@ export function Mileage({
setOnlineLoading(true);
api.onlineVehicleStatuses(mileageParams(values, pageSize, (page - 1) * pageSize))
.then((nextPage) => {
setOnlineRows(nextPage.items);
setOnlineRows(Array.isArray(nextPage.items) ? nextPage.items : []);
setOnlinePagination({ currentPage: page, pageSize, total: nextPage.total });
})
.catch((error: Error) => Toast.error(error.message))
@@ -417,6 +470,27 @@ export function Mileage({
'统计摘要'
);
};
const copyBIPublishText = () => {
const vehicleURL = currentVehicleKeyword
? appURL(buildAppHash({ page: 'detail', keyword: currentVehicleKeyword, protocol: currentProtocol }))
: undefined;
copyText(
mileageBIPublishText({
filters,
summary,
pageMileageTotal,
pageCoverageRate,
anomalyCount: anomalyRows.length,
closureStatus,
closureDeltaKm,
confidenceStatus,
sourceConsistencyText,
statisticsURL: appURL(window.location.hash || buildAppHash({ page: 'mileage', keyword: currentVehicleKeyword, protocol: currentProtocol })),
vehicleURL
}),
'BI里程发布口径'
);
};
const copyOnlineStatusHandoff = () => {
copyText(
onlineStatusHandoffText({
@@ -580,6 +654,29 @@ export function Mileage({
</Space>
</div>
</Card>
<Card bordered title="BI发布口径" style={{ marginTop: 16 }}>
<div className="vp-bi-publish-board">
<div className="vp-bi-publish-main">
<Tag color={confidenceColor}>{confidenceStatus}</Tag>
<div className="vp-monitor-metric-value">
{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>
<Space wrap>
<Button size="small" onClick={copyBIPublishText}>BI发布口径</Button>
<Button size="small" onClick={copyStatisticsSummary}></Button>
</Space>
</div>
</Card>
<div className="vp-stat-workspace">
<Card bordered title="区间趋势">
<div className="vp-stat-chart">