feat(platform): refocus dashboard on vehicle operations
This commit is contained in:
@@ -38,10 +38,15 @@ const navGroups = [
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '告警运维',
|
||||
title: '告警通知',
|
||||
items: [
|
||||
{ itemKey: 'alert-events', text: '告警事件', icon: <IconHistogram /> },
|
||||
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> },
|
||||
{ itemKey: 'notification-rules', text: '通知规则', icon: <IconBell /> }
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '系统运维',
|
||||
items: [
|
||||
{ itemKey: 'ops-quality', text: '运维质量', icon: <IconPulse /> }
|
||||
]
|
||||
}
|
||||
@@ -170,17 +175,13 @@ export function AppShell({
|
||||
一致性:{currentVehicleConsistency.title || `${currentVehicleConsistency.onlineSourceCount}/${currentVehicleConsistency.sourceCount} 来源`}
|
||||
</Tag>
|
||||
) : null}
|
||||
<Tag color="blue">生产环境</Tag>
|
||||
{platformRelease ? <Tag color="blue">版本 {platformRelease}</Tag> : null}
|
||||
<Tag color="grey">多源数据归并</Tag>
|
||||
<Tag color="blue">车辆运营</Tag>
|
||||
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图就绪' : '地图待配置'}</Tag>
|
||||
<Button size="small" aria-label="顶部地图态势" onClick={() => onChange('map')}>实时地图</Button>
|
||||
<Button size="small" aria-label="顶部实时监控" onClick={() => onChange('realtime')}>实时监控</Button>
|
||||
<Button size="small" aria-label="顶部轨迹回放" onClick={() => onChange('history')}>轨迹回放</Button>
|
||||
<Button size="small" aria-label="顶部历史查询" onClick={() => onChange('history-query')}>历史查询</Button>
|
||||
<Button size="small" aria-label="顶部统计查询" onClick={() => onChange('mileage')}>统计查询</Button>
|
||||
<Button size="small" aria-label="顶部通知规则" onClick={() => onChange('notification-rules')}>通知规则</Button>
|
||||
<Button size="small" aria-label="顶部运维质量" onClick={() => onChange('ops-quality')}>运维质量</Button>
|
||||
<Button
|
||||
size="small"
|
||||
aria-label={`顶部${alertLabel}`}
|
||||
@@ -189,6 +190,7 @@ export function AppShell({
|
||||
>
|
||||
{alertLabel}
|
||||
</Button>
|
||||
{platformRelease ? <Tag color="grey">版本 {platformRelease}</Tag> : null}
|
||||
</Space>
|
||||
</header>
|
||||
{children}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, T
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../api/client';
|
||||
import type { DashboardSummary, LinkHealth, OpsHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, SourceReadinessPlan, SourceReadinessRow, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||
import type { DashboardSummary, LinkHealth, OpsHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, VehicleCoverageRow, VehicleRealtimeRow, VehicleServiceSummary } from '../api/types';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||
import { StatusTag } from '../components/StatusTag';
|
||||
@@ -93,15 +93,6 @@ function formatProtocolRate(row: ProtocolStat) {
|
||||
return `${Math.round((row.online / row.total) * 100)}%`;
|
||||
}
|
||||
|
||||
function normalizeSourceReadiness(plan: SourceReadinessPlan | null): SourceReadinessPlan | null {
|
||||
return plan && Array.isArray(plan.sources) ? plan : null;
|
||||
}
|
||||
|
||||
function sourceReadinessColor(row: SourceReadinessRow): 'green' | 'orange' | 'red' {
|
||||
if (row.severity === 'error') return 'red';
|
||||
return row.severity === 'warning' ? 'orange' : 'green';
|
||||
}
|
||||
|
||||
function rowServiceStatus(row: { serviceStatus?: { title: string; severity: string }; onlineSourceCount: number; sourceCount: number }) {
|
||||
if (row.serviceStatus) {
|
||||
return {
|
||||
@@ -268,7 +259,6 @@ export function Dashboard({
|
||||
const [locations, setLocations] = useState<VehicleRealtimeRow[]>([]);
|
||||
const [qualityIssues, setQualityIssues] = useState<QualityIssueRow[]>([]);
|
||||
const [opsHealth, setOpsHealth] = useState<OpsHealth | null>(null);
|
||||
const [sourceReadiness, setSourceReadiness] = useState<SourceReadinessPlan | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [coverageLoading, setCoverageLoading] = useState(false);
|
||||
const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState('');
|
||||
@@ -308,8 +298,7 @@ export function Dashboard({
|
||||
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
|
||||
api.vehicleRealtime(new URLSearchParams({ limit: '8' })).then((page) => setLocations(page.items)),
|
||||
api.alertEvents(new URLSearchParams({ limit: '5' })).then((page) => setQualityIssues(page.items)),
|
||||
api.opsHealth().then(setOpsHealth),
|
||||
api.sourceReadiness().then((plan) => setSourceReadiness(normalizeSourceReadiness(plan)))
|
||||
api.opsHealth().then(setOpsHealth)
|
||||
];
|
||||
Promise.allSettled(tasks)
|
||||
.then((results) => {
|
||||
@@ -394,6 +383,66 @@ export function Dashboard({
|
||||
online: row.online,
|
||||
title: `${row.plate || row.vin || '-'} ${row.primaryProtocol || ''} ${row.lastSeen || ''}`
|
||||
}));
|
||||
const customerHeroActions = [
|
||||
{
|
||||
title: '实时车辆地图',
|
||||
value: `${commandLocatedCount.toLocaleString()} 辆有定位`,
|
||||
detail: '按车查看实时位置、在线状态、速度和最新上报时间。',
|
||||
action: '进入地图',
|
||||
onClick: () => onOpenMap({ online: 'online' })
|
||||
},
|
||||
{
|
||||
title: '轨迹回放',
|
||||
value: `${formatCount(summary?.activeToday)} 今日活跃`,
|
||||
detail: '选择车辆和时间范围,回放历史路线并核对断点。',
|
||||
action: '回放轨迹',
|
||||
onClick: () => onOpenHistory()
|
||||
},
|
||||
{
|
||||
title: '里程统计',
|
||||
value: `${formatCount(serviceSummary?.totalVehicles)} 辆可统计`,
|
||||
detail: '按自定义时间查询区间里程、日里程和异常记录。',
|
||||
action: '查看统计',
|
||||
onClick: () => onOpenMileage({})
|
||||
},
|
||||
{
|
||||
title: '历史数据导出',
|
||||
value: `${formatCount(summary?.frameToday)} 今日数据`,
|
||||
detail: '按车辆、时间和字段裁剪查询历史位置与 RAW 证据。',
|
||||
action: '查询导出',
|
||||
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
|
||||
}
|
||||
];
|
||||
const customerHealthItems = [
|
||||
{
|
||||
label: '在线车辆',
|
||||
value: formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles),
|
||||
detail: vehicleServiceOnlineText(serviceSummary, summary),
|
||||
color: 'green' as const,
|
||||
onClick: () => onOpenVehicles({ online: 'online' })
|
||||
},
|
||||
{
|
||||
label: '有效定位',
|
||||
value: commandLocatedCount.toLocaleString(),
|
||||
detail: `当前抽样 ${locations.length.toLocaleString()} 辆,定位可用于地图和回放。`,
|
||||
color: commandLocatedCount > 0 ? 'blue' as const : 'orange' as const,
|
||||
onClick: () => onOpenMap({ online: 'online' })
|
||||
},
|
||||
{
|
||||
label: '需要关注',
|
||||
value: formatCount(summary?.issueVehicles),
|
||||
detail: highPriorityIssue ? `${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)}` : '暂无高优先级告警。',
|
||||
color: (summary?.issueVehicles ?? 0) > 0 ? 'orange' as const : 'green' as const,
|
||||
onClick: () => onOpenQuality()
|
||||
},
|
||||
{
|
||||
label: '时间范围监控',
|
||||
value: formatCount(summary?.activeToday),
|
||||
detail: '支持按任意时间窗查询轨迹、历史数据、里程和导出。',
|
||||
color: 'blue' as const,
|
||||
onClick: () => onOpenHistory()
|
||||
}
|
||||
];
|
||||
const capabilities = [
|
||||
{
|
||||
title: '实时地图',
|
||||
@@ -765,36 +814,6 @@ export function Dashboard({
|
||||
].filter(Boolean);
|
||||
copyText(lines.join('\n'), '运营交接摘要');
|
||||
};
|
||||
const copySourceReadinessHandoff = () => {
|
||||
const plan = sourceReadiness;
|
||||
if (!plan?.sources?.length) {
|
||||
Toast.warning('暂无数据源就绪度数据');
|
||||
return;
|
||||
}
|
||||
const lines = [
|
||||
'【三源接入到车辆服务交接摘要】',
|
||||
`车辆规模:${formatCount(plan.onlineVehicles)} / ${formatCount(plan.totalVehicles)} 在线`,
|
||||
`接入运行:连接 ${formatCount(plan.activeConnections)};Redis 在线 Key ${formatCount(plan.redisOnlineKeys)};Kafka Lag ${formatLag(plan.kafkaLag)}`,
|
||||
`平台版本:${plan.platformRelease || '-'}`,
|
||||
'',
|
||||
...plan.sources.map((item, index) => [
|
||||
`${index + 1}. ${item.protocol} - ${item.status}`,
|
||||
` 角色:${item.role}`,
|
||||
` 在线:${formatCount(item.online)} / ${formatCount(item.total)},在线率 ${item.onlineRate.toLocaleString(undefined, { maximumFractionDigits: 1 })}%`,
|
||||
` 缺失车辆:${formatCount(item.missingVehicles)}`,
|
||||
` 证据:${item.evidence}`,
|
||||
` 动作:${item.action}`,
|
||||
` 验收:${item.acceptance}`,
|
||||
` 实时:${appURL(item.realtimeHash)}`,
|
||||
` 车辆:${appURL(item.vehiclesHash)}`,
|
||||
` RAW:${appURL(item.historyHash)}`,
|
||||
` 告警:${appURL(item.alertHash)}`
|
||||
].join('\n')),
|
||||
'',
|
||||
`驾驶舱:${appURL(buildAppHash({ page: 'dashboard' }))}`
|
||||
];
|
||||
copyText(lines.join('\n'), '三源接入交接摘要');
|
||||
};
|
||||
const copyScenarioBlueprint = () => {
|
||||
const lines = [
|
||||
'【车辆数据中台功能蓝图】',
|
||||
@@ -968,8 +987,59 @@ export function Dashboard({
|
||||
|
||||
return (
|
||||
<div className="vp-page">
|
||||
<PageHeader title="运营驾驶舱" description="三个数据源最终汇总为一个车辆服务,统一承载实时监控、轨迹回放、历史查询、告警通知、统计查询和链路质量" />
|
||||
<PageHeader title="车辆运营监控台" description="面向客户的车辆地图、实时状态、轨迹回放、里程统计、历史查询和数据导出入口" />
|
||||
<Spin spinning={loading}>
|
||||
<Card bordered className="vp-customer-hero" bodyStyle={{ padding: 0 }}>
|
||||
<div className="vp-customer-hero-map">
|
||||
<div className="vp-customer-hero-copy">
|
||||
<Typography.Title heading={3} style={{ margin: 0 }}>先看车,再看数据来源</Typography.Title>
|
||||
<Typography.Text type="secondary">
|
||||
车辆是主对象。32960、808 和宇通 MQTT 只是证据来源,客户每天需要先确认车辆在哪里、是否在线、今天跑了多少、异常能否追溯和导出。
|
||||
</Typography.Text>
|
||||
<Space wrap>
|
||||
<Button theme="solid" type="primary" onClick={() => onOpenMap({ online: 'online' })}>打开实时地图</Button>
|
||||
<Button theme="light" type="primary" onClick={() => onOpenHistory()}>轨迹回放</Button>
|
||||
<Button theme="light" type="primary" onClick={() => onOpenMileage({})}>里程统计</Button>
|
||||
<Button theme="light" onClick={exportDashboardSnapshot}>导出概览</Button>
|
||||
</Space>
|
||||
</div>
|
||||
<VehicleMap
|
||||
points={commandMapPoints}
|
||||
maxFallbackPoints={80}
|
||||
heightClassName="vp-customer-hero-map-canvas"
|
||||
fallbackLabel="显示车辆实时坐标预览"
|
||||
/>
|
||||
</div>
|
||||
<div className="vp-customer-hero-side">
|
||||
<div className="vp-customer-hero-side-head">
|
||||
<Tag color={amapConfigured ? 'green' : 'orange'}>{amapConfigured ? '地图可用' : '地图待配置'}</Tag>
|
||||
<Tag color={(summary?.issueVehicles ?? 0) > 0 ? 'orange' : 'green'}>{formatCount(summary?.issueVehicles)} 个告警</Tag>
|
||||
</div>
|
||||
<div className="vp-customer-health-grid">
|
||||
{customerHealthItems.map((item) => (
|
||||
<button key={item.label} type="button" className="vp-customer-health-item" onClick={item.onClick} aria-label={`车辆运营 ${item.label} ${item.value}`}>
|
||||
<Tag color={item.color}>{item.label}</Tag>
|
||||
<strong>{item.value}</strong>
|
||||
<span>{item.detail}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="客户常用工作流" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-customer-workflow-grid">
|
||||
{customerHeroActions.map((item) => (
|
||||
<button key={item.title} type="button" className="vp-customer-workflow-item" onClick={item.onClick} aria-label={`客户工作流 ${item.title}`}>
|
||||
<div>
|
||||
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
|
||||
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||
</div>
|
||||
<strong>{item.value}</strong>
|
||||
<Tag color="blue">{item.action}</Tag>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
<div className="vp-kpi-grid">
|
||||
{kpis.map((item) => (
|
||||
<Card key={item.label} bordered className="vp-kpi-card" bodyStyle={{ padding: 0 }}>
|
||||
@@ -995,52 +1065,6 @@ export function Dashboard({
|
||||
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
<Card
|
||||
bordered
|
||||
title={<Space wrap><span>数据源到车辆服务就绪度</span><Button size="small" theme="light" disabled={!sourceReadiness?.sources?.length} onClick={copySourceReadinessHandoff}>复制接入交接</Button></Space>}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<div className="vp-source-readiness-grid">
|
||||
{(sourceReadiness?.sources ?? []).map((item) => (
|
||||
<div key={item.protocol} className="vp-source-readiness-item">
|
||||
<div className="vp-source-readiness-head">
|
||||
<Space wrap>
|
||||
<Tag color={sourceReadinessColor(item)}>{item.protocol}</Tag>
|
||||
<Tag color={sourceReadinessColor(item)}>{item.status}</Tag>
|
||||
</Space>
|
||||
<Typography.Text type="secondary">{item.role}</Typography.Text>
|
||||
</div>
|
||||
<div className="vp-source-readiness-metrics">
|
||||
<div>
|
||||
<span>在线</span>
|
||||
<strong>{formatCount(item.online)} / {formatCount(item.total)}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>在线率</span>
|
||||
<strong>{item.onlineRate.toLocaleString(undefined, { maximumFractionDigits: 1 })}%</strong>
|
||||
</div>
|
||||
<div>
|
||||
<span>缺失车辆</span>
|
||||
<strong>{formatCount(item.missingVehicles)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<Typography.Text type="secondary">{item.evidence}</Typography.Text>
|
||||
<div className="vp-source-readiness-actions">
|
||||
<Button size="small" theme="light" onClick={() => { window.location.hash = item.realtimeHash; }}>实时</Button>
|
||||
<Button size="small" theme="light" onClick={() => { window.location.hash = item.vehiclesHash; }}>车辆</Button>
|
||||
<Button size="small" theme="light" onClick={() => { window.location.hash = item.historyHash; }}>RAW</Button>
|
||||
<Button size="small" theme="light" type={item.severity === 'ok' ? 'tertiary' : 'warning'} onClick={() => { window.location.hash = item.alertHash; }}>告警</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{sourceReadiness?.sources?.length ? null : (
|
||||
<div className="vp-source-readiness-empty">
|
||||
<Tag color="grey">待接入</Tag>
|
||||
<Typography.Text type="secondary">数据源就绪度加载后会展示 32960、808 和宇通 MQTT 对车辆服务的支撑状态。</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
<Card bordered title="地图运营能力" style={{ marginBottom: 16 }}>
|
||||
<div className="vp-map-ops-board">
|
||||
<div className="vp-map-ops-readiness">
|
||||
|
||||
@@ -235,6 +235,136 @@ body {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.vp-customer-hero {
|
||||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vp-customer-hero .semi-card-body {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.6fr) minmax(360px, 0.7fr);
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
.vp-customer-hero-map {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
min-height: 420px;
|
||||
background: #eef5ff;
|
||||
}
|
||||
|
||||
.vp-customer-hero-copy {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
left: 24px;
|
||||
top: 24px;
|
||||
max-width: 560px;
|
||||
padding: 18px;
|
||||
border: 1px solid rgba(22, 100, 255, 0.16);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
box-shadow: 0 12px 32px rgba(16, 24, 40, 0.12);
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-customer-hero-map-canvas {
|
||||
min-height: 420px;
|
||||
height: 420px;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.vp-customer-hero-side {
|
||||
min-width: 0;
|
||||
padding: 22px;
|
||||
background: #ffffff;
|
||||
border-left: 1px solid var(--vp-border);
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.vp-customer-hero-side-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vp-customer-health-grid {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.vp-customer-health-item {
|
||||
min-height: 78px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: 8px;
|
||||
background: #fbfcff;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 6px 10px;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.vp-customer-health-item:hover,
|
||||
.vp-customer-health-item:focus-visible {
|
||||
border-color: rgba(22, 100, 255, 0.45);
|
||||
background: #f5f9ff;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vp-customer-health-item strong {
|
||||
justify-self: end;
|
||||
color: var(--vp-text);
|
||||
font-size: 22px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.vp-customer-health-item span {
|
||||
grid-column: 1 / -1;
|
||||
color: var(--vp-text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.vp-customer-workflow-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-customer-workflow-item {
|
||||
min-height: 150px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--vp-border);
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
display: grid;
|
||||
align-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vp-customer-workflow-item:hover,
|
||||
.vp-customer-workflow-item:focus-visible {
|
||||
border-color: rgba(22, 100, 255, 0.45);
|
||||
box-shadow: var(--vp-shadow-sm);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.vp-customer-workflow-item strong {
|
||||
color: var(--vp-text);
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.vp-result-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
|
||||
@@ -24,7 +24,8 @@ test('renders vehicle platform shell', () => {
|
||||
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(1);
|
||||
});
|
||||
|
||||
@@ -593,7 +594,7 @@ test('dashboard presents one vehicle service operating posture', async () => {
|
||||
render(<App />);
|
||||
|
||||
expect(await screen.findByText('统一车辆服务入口')).toBeInTheDocument();
|
||||
expect(screen.getByText('208 / 1,033 在线')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('208 / 1,033 在线').length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText('181 多源覆盖')).toBeInTheDocument();
|
||||
expect(screen.getByText('7 告警事件')).toBeInTheDocument();
|
||||
});
|
||||
@@ -790,8 +791,12 @@ 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.getByText('地图运营能力')).toBeInTheDocument();
|
||||
expect(screen.getByText('数据流转作业链')).toBeInTheDocument();
|
||||
expect(screen.getByText('车联网场景导航')).toBeInTheDocument();
|
||||
@@ -799,11 +804,6 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
||||
expect(screen.getByText('服务端 API')).toBeInTheDocument();
|
||||
expect(screen.getByText('安全代理')).toBeInTheDocument();
|
||||
expect(screen.getByText('安全码暴露')).toBeInTheDocument();
|
||||
expect(screen.getByText('GB32960')).toBeInTheDocument();
|
||||
expect(screen.getByText('JT808')).toBeInTheDocument();
|
||||
expect(screen.getByText('YUTONG_MQTT')).toBeInTheDocument();
|
||||
expect(screen.getByText('整车与氢能实时数据主来源')).toBeInTheDocument();
|
||||
expect(screen.getByText('在线 73/340,缺失车辆 693,Kafka Lag 0')).toBeInTheDocument();
|
||||
expect(screen.getByText('协议接入')).toBeInTheDocument();
|
||||
expect(screen.getByText('统一解析')).toBeInTheDocument();
|
||||
expect(screen.getByText('实时投影')).toBeInTheDocument();
|
||||
@@ -843,19 +843,15 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
||||
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(writeText).toHaveBeenCalledWith(expect.stringContaining('【三源接入到车辆服务交接摘要】'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. GB32960 - 覆盖不足'));
|
||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('RAW:http://localhost:3000/#/history-query?protocol=GB32960&tab=raw&includeFields=true'));
|
||||
|
||||
fireEvent.click(screen.getAllByRole('button', { name: '实时' })[0]);
|
||||
expect(window.location.hash).toBe('#/realtime?protocol=GB32960&online=online');
|
||||
fireEvent.click(screen.getByRole('button', { name: '客户工作流 实时车辆地图' }));
|
||||
expect(window.location.hash).toBe('#/map?online=online');
|
||||
cleanup();
|
||||
|
||||
await renderDashboard();
|
||||
fireEvent.click(screen.getAllByRole('button', { name: 'RAW' })[0]);
|
||||
fireEvent.click(screen.getByRole('button', { name: '客户工作流 历史数据导出' }));
|
||||
expect(window.location.hash.startsWith('#/history-query')).toBe(true);
|
||||
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('protocol')).toBe('GB32960');
|
||||
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('tab')).toBe('raw');
|
||||
expect(new URLSearchParams(window.location.hash.split('?')[1] ?? '').get('includeFields')).toBe('true');
|
||||
cleanup();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user