feat(platform): add vehicle service workbench
This commit is contained in:
@@ -432,6 +432,63 @@ export function Dashboard({
|
|||||||
onClick: () => onOpenMileage()
|
onClick: () => onOpenMileage()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const operationWorkbench = [
|
||||||
|
{
|
||||||
|
title: '实时监控',
|
||||||
|
value: `${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)} 在线`,
|
||||||
|
meta: `有效定位 ${commandLocatedCount.toLocaleString()}`,
|
||||||
|
color: 'green' as const,
|
||||||
|
detail: '面向值班人员确认车辆是否在线、坐标是否有效、来源是否完整。',
|
||||||
|
actions: [
|
||||||
|
{ label: '打开实时地图', onClick: () => onOpenMap({ online: 'online' }) },
|
||||||
|
{ label: '查看在线车辆', onClick: () => onOpenRealtime({ online: 'online' }) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '轨迹回放',
|
||||||
|
value: `${formatCount(summary?.activeToday)} 今日活跃`,
|
||||||
|
meta: '高德轨迹证据',
|
||||||
|
color: 'blue' as const,
|
||||||
|
detail: '按车辆回看位置、速度、里程和断点,用于定位异常复盘。',
|
||||||
|
actions: [
|
||||||
|
{ label: '打开轨迹回放', onClick: () => onOpenHistory() },
|
||||||
|
{ label: '查 RAW 证据', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '历史数据查询',
|
||||||
|
value: `${formatCount(summary?.frameToday)} 今日帧`,
|
||||||
|
meta: `Kafka Lag ${formatLag(summary?.kafkaLag)}`,
|
||||||
|
color: (summary?.kafkaLag ?? 0) > 0 ? 'orange' as const : 'blue' as const,
|
||||||
|
detail: '查询位置历史、RAW 帧和解析字段,为车辆服务提供可追溯证据。',
|
||||||
|
actions: [
|
||||||
|
{ label: '查询历史数据', onClick: () => onOpenHistory({ tab: 'location' }) },
|
||||||
|
{ label: '查询解析字段', onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' }) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '告警事件与通知',
|
||||||
|
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
|
||||||
|
meta: highPriorityIssue ? `${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)}` : '暂无高优先级',
|
||||||
|
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
|
||||||
|
detail: '把断链、无来源、VIN 缺失、字段缺失转成可通知、可闭环的事件。',
|
||||||
|
actions: [
|
||||||
|
{ label: '查看告警事件', onClick: () => onOpenQuality() },
|
||||||
|
{ label: '处理最高优先级', onClick: () => onOpenQuality(highPriorityIssue?.issueType ? { issueType: highPriorityIssue.issueType } : {}) }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '统计查询',
|
||||||
|
value: `${formatCount(serviceSummary?.totalVehicles)} 车辆口径`,
|
||||||
|
meta: '区间闭合复核',
|
||||||
|
color: 'green' as const,
|
||||||
|
detail: '里程等指标先按车辆口径闭合,再回溯轨迹和 RAW 证据。',
|
||||||
|
actions: [
|
||||||
|
{ label: '打开统计查询', onClick: () => onOpenMileage() },
|
||||||
|
{ label: '查看车辆中心', onClick: () => onOpenVehicles({}) }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="vp-page">
|
<div className="vp-page">
|
||||||
@@ -460,6 +517,34 @@ export function Dashboard({
|
|||||||
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
|
<Tag color={(summary?.kafkaLag ?? 0) > 0 ? 'orange' : 'green'}>Kafka Lag {formatLag(summary?.kafkaLag)}</Tag>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card bordered title="车辆服务作业台" style={{ marginBottom: 16 }}>
|
||||||
|
<div className="vp-workbench-grid">
|
||||||
|
{operationWorkbench.map((item) => (
|
||||||
|
<div key={item.title} className="vp-workbench-item">
|
||||||
|
<div className="vp-workbench-head">
|
||||||
|
<Tag color={item.color}>{item.title}</Tag>
|
||||||
|
<Typography.Text type="secondary">{item.meta}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
<div className="vp-workbench-value">{item.value}</div>
|
||||||
|
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||||
|
<Space wrap>
|
||||||
|
{item.actions.map((action) => (
|
||||||
|
<Button
|
||||||
|
key={action.label}
|
||||||
|
size="small"
|
||||||
|
theme="light"
|
||||||
|
type="primary"
|
||||||
|
aria-label={`作业台 ${item.title} ${action.label}`}
|
||||||
|
onClick={action.onClick}
|
||||||
|
>
|
||||||
|
{action.label}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
<Card bordered title="一车一服务模型" style={{ marginBottom: 16 }}>
|
<Card bordered title="一车一服务模型" style={{ marginBottom: 16 }}>
|
||||||
<div className="vp-service-model-grid">
|
<div className="vp-service-model-grid">
|
||||||
{vehicleServicePrinciples.map((item) => (
|
{vehicleServicePrinciples.map((item) => (
|
||||||
|
|||||||
@@ -1093,6 +1093,43 @@ button.vp-realtime-command-item:focus-visible {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-workbench-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-workbench-item {
|
||||||
|
min-height: 190px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid var(--vp-border);
|
||||||
|
border-radius: var(--vp-radius);
|
||||||
|
background: #fbfcff;
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-workbench-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-workbench-head .semi-typography {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 18px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-workbench-value {
|
||||||
|
color: var(--vp-text);
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
.vp-operation-flow {
|
.vp-operation-flow {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||||
@@ -1279,6 +1316,7 @@ button.vp-realtime-command-item:focus-visible {
|
|||||||
.vp-current-service-grid,
|
.vp-current-service-grid,
|
||||||
.vp-realtime-command-board,
|
.vp-realtime-command-board,
|
||||||
.vp-realtime-command-grid,
|
.vp-realtime-command-grid,
|
||||||
|
.vp-workbench-grid,
|
||||||
.vp-operation-flow,
|
.vp-operation-flow,
|
||||||
.vp-service-model-grid,
|
.vp-service-model-grid,
|
||||||
.vp-capability-grid,
|
.vp-capability-grid,
|
||||||
|
|||||||
@@ -456,12 +456,18 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await renderDashboard();
|
await renderDashboard();
|
||||||
|
expect(screen.getByText('车辆服务作业台')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('实时地图').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('实时地图').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('实时监控').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('实时监控').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('轨迹回放').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('轨迹回放').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('历史数据查询').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('历史数据查询').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('告警事件触发与通知').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('告警事件触发与通知').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('统计查询').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('统计查询').length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(screen.getByText('告警事件与通知')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('区间闭合复核')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('高德轨迹证据')).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('Kafka Lag 0').length).toBeGreaterThanOrEqual(1);
|
||||||
|
expect(screen.getByText('1,033 车辆口径')).toBeInTheDocument();
|
||||||
expect(screen.getAllByText('在线 208').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('在线 208').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('在线态势 208').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('在线态势 208').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('有效定位 0').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('有效定位 0').length).toBeGreaterThanOrEqual(1);
|
||||||
@@ -469,6 +475,11 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
|||||||
expect(screen.getAllByText('告警 7').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('告警 7').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('统计车辆 1,033').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('统计车辆 1,033').length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '作业台 实时监控 打开实时地图' }));
|
||||||
|
expect(window.location.hash).toBe('#/map?online=online');
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
await renderDashboard();
|
||||||
fireEvent.click(screen.getByRole('button', { name: '能力入口 实时监控' }));
|
fireEvent.click(screen.getByRole('button', { name: '能力入口 实时监控' }));
|
||||||
expect(window.location.hash).toBe('#/realtime?online=online');
|
expect(window.location.hash).toBe('#/realtime?online=online');
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|||||||
Reference in New Issue
Block a user