feat(platform): add vehicle data flow workbench

This commit is contained in:
lingniu
2026-07-04 23:10:42 +08:00
parent ee93d149e2
commit bf106a9730
3 changed files with 178 additions and 0 deletions

View File

@@ -618,6 +618,62 @@ export function Dashboard({
]
}
];
const dataFlowStages = [
{
stage: '01',
title: '协议接入',
owner: '网关接收',
evidence: `32960 / 808 / MQTT连接 ${formatCount(opsHealth?.activeConnections)}`,
detail: '只接收真实实时数据帧和必要鉴权注册帧,协议不是最终产品。',
action: '运维质量',
onClick: () => onOpenQuality()
},
{
stage: '02',
title: '统一解析',
owner: '字段映射',
evidence: `今日帧 ${formatCount(summary?.frameToday)}Kafka Lag ${formatLag(summary?.kafkaLag)}`,
detail: '一次解析生成扁平字段、原始证据和车辆身份关联,避免三处重复解析。',
action: '历史字段',
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
},
{
stage: '03',
title: '实时投影',
owner: 'Redis KV',
evidence: `在线 Key ${formatCount(opsHealth?.redisOnlineKeys)},在线车辆 ${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)}`,
detail: '快速写入车辆最新状态、在线 TTL 和全量协议字段,支撑 0-1 秒监控。',
action: '实时监控',
onClick: () => onOpenRealtime({ online: 'online' })
},
{
stage: '04',
title: '车辆归并',
owner: 'Vehicle Service',
evidence: `多源 ${formatCount(serviceSummary?.multiSourceVehicles)},单源 ${formatCount(serviceSummary?.singleSourceVehicles)}`,
detail: '以 VIN 合并来源证据,车牌、手机号和 OEM 只是车辆服务的检索键。',
action: '车辆中心',
onClick: () => onOpenVehicles({})
},
{
stage: '05',
title: '历史证据',
owner: 'TDengine / MySQL',
evidence: `轨迹 ${formatCount(summary?.activeToday)} 活跃RAW ${formatCount(summary?.frameToday)}`,
detail: '位置、RAW 和解析字段可分页查询,并能回跳轨迹、车辆服务和告警。',
action: '轨迹回放',
onClick: () => onOpenHistory()
},
{
stage: '06',
title: '运营闭环',
owner: '告警 / 统计',
evidence: `告警 ${formatCount(summary?.issueVehicles)},统计车辆 ${formatCount(serviceSummary?.totalVehicles)}`,
detail: '断链、缺字段、里程异常和离线时长进入通知、统计和复核工作流。',
action: '统计查询',
onClick: () => onOpenMileage()
}
];
const scenarioNavigation = [
{
title: '实时监控',
@@ -716,6 +772,28 @@ export function Dashboard({
];
copyText(lines.join('\n'), '功能蓝图');
};
const copyDataFlowBlueprint = () => {
const lines = [
'【车辆数据中台数据流转图】',
'原则32960 / 808 / 宇通 MQTT 是接入证据,最终都归并为同一辆车的车辆服务。',
`运行态:连接 ${formatCount(opsHealth?.activeConnections)};在线 Key ${formatCount(opsHealth?.redisOnlineKeys)}Kafka Lag ${formatLag(summary?.kafkaLag)};今日帧 ${formatCount(summary?.frameToday)}`,
'',
...dataFlowStages.map((item) => [
`${item.stage}. ${item.title} / ${item.owner}`,
` 证据:${item.evidence}`,
` 说明:${item.detail}`
].join('\n')),
'',
`实时监控:${appURL(buildAppHash({ page: 'realtime', filters: { online: 'online' } }))}`,
`车辆中心:${appURL(buildAppHash({ page: 'vehicles' }))}`,
`轨迹回放:${appURL(buildAppHash({ page: 'history' }))}`,
`历史查询:${appURL(buildAppHash({ page: 'history-query', filters: { tab: 'raw', includeFields: 'true' } }))}`,
`告警事件:${appURL(buildAppHash({ page: 'alert-events' }))}`,
`统计查询:${appURL(buildAppHash({ page: 'mileage' }))}`,
`运维质量:${appURL(buildAppHash({ page: 'ops-quality' }))}`
];
copyText(lines.join('\n'), '数据流转图');
};
const copyFocusVehicleService = () => {
if (!focusVehicle) {
Toast.warning('当前没有重点车辆服务可复制');
@@ -976,6 +1054,26 @@ export function Dashboard({
))}
</div>
</Card>
<Card
bordered
title={<Space><span></span><Button size="small" onClick={copyDataFlowBlueprint}></Button></Space>}
style={{ marginBottom: 16 }}
>
<div className="vp-data-flow-grid">
{dataFlowStages.map((item) => (
<button key={item.stage} className="vp-data-flow-item" type="button" onClick={item.onClick} aria-label={`数据流转 ${item.title}`}>
<div className="vp-data-flow-head">
<span>{item.stage}</span>
<Tag color="blue">{item.owner}</Tag>
</div>
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
<div className="vp-data-flow-evidence">{item.evidence}</div>
<Typography.Text type="secondary">{item.detail}</Typography.Text>
<Tag color="grey">{item.action}</Tag>
</button>
))}
</div>
</Card>
<Card
bordered
title={<Space><span></span><Button size="small" onClick={copyScenarioBlueprint}></Button></Space>}

View File

@@ -1980,6 +1980,64 @@ button.vp-realtime-command-item:focus-visible {
line-height: 32px;
}
.vp-data-flow-grid {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 12px;
}
.vp-data-flow-item {
min-height: 216px;
padding: 14px;
border: 1px solid var(--vp-border);
border-radius: var(--vp-radius);
background: #fbfcff;
color: inherit;
display: grid;
gap: 10px;
align-content: start;
text-align: left;
cursor: pointer;
transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
}
.vp-data-flow-item:hover,
.vp-data-flow-item:focus-visible {
border-color: rgba(22, 100, 255, 0.42);
box-shadow: 0 0 0 3px rgba(22, 100, 255, 0.08);
outline: none;
}
.vp-data-flow-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.vp-data-flow-head > span {
width: 30px;
height: 30px;
border-radius: 7px;
background: rgba(22, 100, 255, 0.09);
color: #1664ff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 700;
}
.vp-data-flow-evidence {
min-height: 54px;
padding: 10px;
border-radius: 6px;
background: rgba(10, 168, 107, 0.08);
color: var(--vp-text);
font-size: 12px;
line-height: 18px;
}
.vp-scenario-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
@@ -2252,6 +2310,7 @@ button.vp-realtime-command-item:focus-visible {
.vp-scenario-grid,
.vp-operation-flow,
.vp-service-model-grid,
.vp-data-flow-grid,
.vp-capability-grid,
.vp-conclusion-grid,
.vp-monitor-layout,

View File

@@ -546,11 +546,18 @@ 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('高德 Web JS')).toBeInTheDocument();
expect(screen.getByText('服务端 API')).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.getAllByText('历史证据').length).toBeGreaterThanOrEqual(1);
expect(screen.getByText('运营闭环')).toBeInTheDocument();
expect(screen.getAllByText('已配置').length).toBeGreaterThanOrEqual(2);
expect(screen.getByText('已启用')).toBeInTheDocument();
expect(screen.getByText('未暴露')).toBeInTheDocument();
@@ -581,6 +588,9 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('高德地图Web JS 已配置;服务端 API 已配置;安全代理 已启用;安全码未暴露'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 实时监控'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('SLA目标 0-1 秒内进入实时视图'));
fireEvent.click(screen.getByRole('button', { name: '复制流转图' }));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台数据流转图】'));
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('03. 实时投影 / Redis KV'));
fireEvent.click(screen.getByRole('button', { name: '地图运营 实时监控' }));
expect(window.location.hash).toBe('#/map?online=online');
@@ -592,6 +602,17 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('includeFields')).toBe('true');
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '数据流转 实时投影' }));
expect(window.location.hash).toBe('#/realtime?online=online');
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '数据流转 统一解析' }));
expect(window.location.hash.startsWith('#/history-query')).toBe(true);
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('includeFields')).toBe('true');
cleanup();
await renderDashboard();
fireEvent.click(screen.getByRole('button', { name: '作业台 实时监控 打开实时地图' }));
expect(window.location.hash).toBe('#/map?online=online');