feat(platform): add vehicle monitoring command center
This commit is contained in:
@@ -1044,6 +1044,48 @@ export function Dashboard({
|
||||
downloadCsv('dashboard-snapshot.csv', buildCsv(dashboardSnapshotColumns, rows));
|
||||
Toast.success(`已导出 ${rows.length.toLocaleString()} 条驾驶舱摘要`);
|
||||
};
|
||||
const commandCenterItems = [
|
||||
{
|
||||
title: '全域车辆监控',
|
||||
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
|
||||
detail: `有效定位 ${commandLocatedCount.toLocaleString()} 辆,先从地图确认车辆分布、在线状态和最新位置。`,
|
||||
color: 'green' as const,
|
||||
primaryAction: '打开车辆地图',
|
||||
secondaryAction: '在线车辆',
|
||||
onPrimary: () => onOpenMap({ online: 'online' }),
|
||||
onSecondary: () => onOpenVehicles({ online: 'online' })
|
||||
},
|
||||
{
|
||||
title: '异常车辆闭环',
|
||||
value: `${formatCount(summary?.issueVehicles)} 告警`,
|
||||
detail: highPriorityIssue ? `最高优先级:${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '当前没有高优先级告警,保持实时巡检。',
|
||||
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
|
||||
primaryAction: '告警事件',
|
||||
secondaryAction: '通知规则',
|
||||
onPrimary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}),
|
||||
onSecondary: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {})
|
||||
},
|
||||
{
|
||||
title: '自定义时间复盘',
|
||||
value: taskTimeMonitorSummary,
|
||||
detail: '一个时间窗贯穿轨迹回放、里程统计、历史查询和告警复盘,适合客户问询与 BI 核对。',
|
||||
color: 'blue' as const,
|
||||
primaryAction: '轨迹复盘',
|
||||
secondaryAction: '里程统计',
|
||||
onPrimary: () => openTimeMonitorHistory(),
|
||||
onSecondary: () => openTimeMonitorMileage()
|
||||
},
|
||||
{
|
||||
title: '数据交付与导出',
|
||||
value: `${formatCount(summary?.frameToday)} 今日数据`,
|
||||
detail: '按车辆、时间、字段裁剪导出位置、原始记录和字段证据,交付给业务或客户复盘。',
|
||||
color: 'blue' as const,
|
||||
primaryAction: '历史导出',
|
||||
secondaryAction: '导出概览',
|
||||
onPrimary: () => openTimeMonitorRaw(),
|
||||
onSecondary: () => exportDashboardSnapshot()
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
@@ -1086,6 +1128,39 @@ export function Dashboard({
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="车辆监控指挥台" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-command-center-grid">
|
||||
{commandCenterItems.map((item) => (
|
||||
<div key={item.title} className="vp-command-center-item">
|
||||
<div className="vp-command-center-head">
|
||||
<Tag color={item.color}>{item.title}</Tag>
|
||||
<strong>{item.value}</strong>
|
||||
</div>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button
|
||||
size="small"
|
||||
theme="solid"
|
||||
type="primary"
|
||||
aria-label={`车辆监控指挥台 ${item.title} ${item.primaryAction}`}
|
||||
onClick={item.onPrimary}
|
||||
>
|
||||
{item.primaryAction}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
theme="light"
|
||||
type="primary"
|
||||
aria-label={`车辆监控指挥台 ${item.title} ${item.secondaryAction}`}
|
||||
onClick={item.onSecondary}
|
||||
>
|
||||
{item.secondaryAction}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="客户常用工作流" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-customer-workflow-grid">
|
||||
{customerHeroActions.map((item) => (
|
||||
|
||||
@@ -410,6 +410,41 @@ body {
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.vp-command-center-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-command-center-item {
|
||||
min-height: 166px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
display: grid;
|
||||
align-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-command-center-head {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vp-command-center-head strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.vp-command-center-item .semi-typography {
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.vp-customer-task-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||
@@ -3176,6 +3211,7 @@ button.vp-realtime-command-item:focus-visible {
|
||||
.vp-map-ops-readiness,
|
||||
.vp-map-ops-work,
|
||||
.vp-time-monitor-grid,
|
||||
.vp-command-center-grid,
|
||||
.vp-customer-task-grid,
|
||||
.vp-current-service-board,
|
||||
.vp-current-service-grid,
|
||||
|
||||
@@ -788,6 +788,15 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
||||
await renderDashboard();
|
||||
expect(screen.getByText('车辆运营监控台')).toBeInTheDocument();
|
||||
expect(screen.getByText('先看车辆服务')).toBeInTheDocument();
|
||||
expect(screen.getByText('车辆监控指挥台')).toBeInTheDocument();
|
||||
expect(screen.getByText('全域车辆监控')).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.getByText('客户常用工作流')).toBeInTheDocument();
|
||||
expect(screen.getByText('实时车辆地图')).toBeInTheDocument();
|
||||
expect(screen.getByText('历史数据导出')).toBeInTheDocument();
|
||||
|
||||
Reference in New Issue
Block a user