feat(platform): export dashboard snapshot
This commit is contained in:
@@ -9,6 +9,7 @@ import { StatusTag } from '../components/StatusTag';
|
||||
import { VehicleMap, type VehicleMapPoint } from '../components/VehicleMap';
|
||||
import { isAMapConfigured } from '../config/appConfig';
|
||||
import { buildAppHash } from '../domain/appRoute';
|
||||
import { buildCsv, downloadCsv, type CsvColumn } from '../domain/csvExport';
|
||||
import { qualityIssueLabel, qualityProtocolLabel } from '../domain/qualityIssue';
|
||||
import { qualityIssueVehicleLookup } from '../domain/vehicleLookup';
|
||||
|
||||
@@ -40,6 +41,20 @@ const missingProtocolTitle: Record<string, string> = {
|
||||
YUTONG_MQTT: '缺 YUTONG_MQTT'
|
||||
};
|
||||
|
||||
type DashboardSnapshotRow = {
|
||||
section: string;
|
||||
item: string;
|
||||
value: string;
|
||||
detail: string;
|
||||
};
|
||||
|
||||
const dashboardSnapshotColumns: CsvColumn<DashboardSnapshotRow>[] = [
|
||||
{ title: '模块', value: (row) => row.section },
|
||||
{ title: '指标', value: (row) => row.item },
|
||||
{ title: '值', value: (row) => row.value },
|
||||
{ title: '说明', value: (row) => row.detail }
|
||||
];
|
||||
|
||||
function formatLag(value?: number | null) {
|
||||
return value == null ? '未接入' : value.toLocaleString();
|
||||
}
|
||||
@@ -517,6 +532,112 @@ export function Dashboard({
|
||||
].filter(Boolean);
|
||||
copyText(lines.join('\n'), '运营交接摘要');
|
||||
};
|
||||
const buildDashboardSnapshotRows = () => {
|
||||
const unhealthyLinks = (summary?.linkHealth ?? []).filter((item) => item.status !== 'ok');
|
||||
const priorityAction = serviceActionQueue[0];
|
||||
const rows: DashboardSnapshotRow[] = [
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '在线车辆',
|
||||
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} / ${formatCount(serviceSummary?.totalVehicles)}`,
|
||||
detail: '统一车辆服务在线态势'
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '多源覆盖车辆',
|
||||
value: formatCount(serviceSummary?.multiSourceVehicles),
|
||||
detail: '同一车辆可由多个协议交叉验证'
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '单源车辆',
|
||||
value: formatCount(serviceSummary?.singleSourceVehicles),
|
||||
detail: '仅有一个协议来源,需要持续补齐'
|
||||
},
|
||||
{
|
||||
section: '车辆服务',
|
||||
item: '暂无来源车辆',
|
||||
value: formatCount(serviceSummary?.noDataVehicles),
|
||||
detail: '车辆档案存在但暂无实时来源'
|
||||
},
|
||||
{
|
||||
section: '实时监控',
|
||||
item: '有效定位',
|
||||
value: commandLocatedCount.toLocaleString(),
|
||||
detail: `当前预览 ${locations.length.toLocaleString()} 条车辆实时数据`
|
||||
},
|
||||
{
|
||||
section: '实时监控',
|
||||
item: '来源异常',
|
||||
value: commandDegradedCount.toLocaleString(),
|
||||
detail: '当前预览中离线或来源不完整车辆'
|
||||
},
|
||||
{
|
||||
section: '历史数据',
|
||||
item: '今日帧量',
|
||||
value: formatCount(summary?.frameToday),
|
||||
detail: `Kafka Lag ${formatLag(summary?.kafkaLag)}`
|
||||
},
|
||||
{
|
||||
section: '告警事件',
|
||||
item: '告警车辆',
|
||||
value: formatCount(summary?.issueVehicles),
|
||||
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无最高优先级告警'
|
||||
},
|
||||
{
|
||||
section: '链路健康',
|
||||
item: '异常链路',
|
||||
value: unhealthyLinks.length.toLocaleString(),
|
||||
detail: unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'
|
||||
},
|
||||
{
|
||||
section: '地图能力',
|
||||
item: '高德地图',
|
||||
value: amapConfigured ? '已配置' : '待配置',
|
||||
detail: '用于实时监控和轨迹回放地图底座'
|
||||
},
|
||||
{
|
||||
section: '优先动作',
|
||||
item: priorityAction?.label ?? '暂无待办',
|
||||
value: priorityAction ? priorityAction.count.toLocaleString() : '0',
|
||||
detail: priorityAction?.detail ?? '当前没有必须立即处理的车辆服务事项'
|
||||
}
|
||||
];
|
||||
(serviceSummary?.protocols ?? summary?.protocols ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '协议来源',
|
||||
item: item.protocol,
|
||||
value: `${item.online.toLocaleString()} / ${item.total.toLocaleString()}`,
|
||||
detail: `在线率 ${formatProtocolRate(item)}`
|
||||
});
|
||||
});
|
||||
(serviceSummary?.serviceStatuses ?? summary?.serviceStatuses ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '服务状态',
|
||||
item: item.title || serviceStatusTitle[item.status] || item.status,
|
||||
value: item.count.toLocaleString(),
|
||||
detail: serviceStatusTitle[item.status] || item.status
|
||||
});
|
||||
});
|
||||
(summary?.linkHealth ?? []).forEach((item) => {
|
||||
rows.push({
|
||||
section: '链路健康',
|
||||
item: item.name,
|
||||
value: item.status,
|
||||
detail: item.detail ?? ''
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
};
|
||||
const exportDashboardSnapshot = () => {
|
||||
const rows = buildDashboardSnapshotRows();
|
||||
if (rows.length === 0) {
|
||||
Toast.warning('当前没有可导出的驾驶舱摘要');
|
||||
return;
|
||||
}
|
||||
downloadCsv('dashboard-snapshot.csv', buildCsv(dashboardSnapshotColumns, rows));
|
||||
Toast.success(`已导出 ${rows.length.toLocaleString()} 条驾驶舱摘要`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
@@ -544,6 +665,7 @@ export function Dashboard({
|
||||
<Button size="small" theme="light" type={(summary?.issueVehicles ?? 0) > 0 ? 'warning' : 'tertiary'} onClick={() => onOpenQuality()}>查看告警事件</Button>
|
||||
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
|
||||
<Button size="small" theme="solid" type="primary" onClick={copyOperationsHandoff}>复制运营交接摘要</Button>
|
||||
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card bordered title="车辆服务作业台" style={{ marginBottom: 16 }}>
|
||||
|
||||
Reference in New Issue
Block a user