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));
|
downloadCsv('dashboard-snapshot.csv', buildCsv(dashboardSnapshotColumns, rows));
|
||||||
Toast.success(`已导出 ${rows.length.toLocaleString()} 条驾驶舱摘要`);
|
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 (
|
return (
|
||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
@@ -1086,6 +1128,39 @@ export function Dashboard({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</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 }}>
|
<Card bordered title="客户常用工作流" style={{ marginBottom: 16 }}>
|
||||||
<div className="vp-customer-workflow-grid">
|
<div className="vp-customer-workflow-grid">
|
||||||
{customerHeroActions.map((item) => (
|
{customerHeroActions.map((item) => (
|
||||||
|
|||||||
@@ -410,6 +410,41 @@ body {
|
|||||||
line-height: 26px;
|
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 {
|
.vp-customer-task-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
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-readiness,
|
||||||
.vp-map-ops-work,
|
.vp-map-ops-work,
|
||||||
.vp-time-monitor-grid,
|
.vp-time-monitor-grid,
|
||||||
|
.vp-command-center-grid,
|
||||||
.vp-customer-task-grid,
|
.vp-customer-task-grid,
|
||||||
.vp-current-service-board,
|
.vp-current-service-board,
|
||||||
.vp-current-service-grid,
|
.vp-current-service-grid,
|
||||||
|
|||||||
@@ -788,6 +788,15 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
|||||||
await renderDashboard();
|
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.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();
|
expect(screen.getByText('实时车辆地图')).toBeInTheDocument();
|
||||||
expect(screen.getByText('历史数据导出')).toBeInTheDocument();
|
expect(screen.getByText('历史数据导出')).toBeInTheDocument();
|
||||||
|
|||||||
Reference in New Issue
Block a user