feat(platform): add dashboard map operations panel
This commit is contained in:
@@ -2,7 +2,7 @@ import { Button, Card, Col, Form, Row, Select, Space, Spin, Table, Tag, Toast, T
|
|||||||
import { IconSearch } from '@douyinfe/semi-icons';
|
import { IconSearch } from '@douyinfe/semi-icons';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
import type { DashboardSummary, LinkHealth, ProtocolStat, QualityIssueRow, ServiceStatusStat, 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 { PageHeader } from '../components/PageHeader';
|
||||||
import { SourceStatusTags } from '../components/SourceStatusTags';
|
import { SourceStatusTags } from '../components/SourceStatusTags';
|
||||||
import { StatusTag } from '../components/StatusTag';
|
import { StatusTag } from '../components/StatusTag';
|
||||||
@@ -258,11 +258,16 @@ export function Dashboard({
|
|||||||
const [coverage, setCoverage] = useState<VehicleCoverageRow[]>([]);
|
const [coverage, setCoverage] = useState<VehicleCoverageRow[]>([]);
|
||||||
const [locations, setLocations] = useState<VehicleRealtimeRow[]>([]);
|
const [locations, setLocations] = useState<VehicleRealtimeRow[]>([]);
|
||||||
const [qualityIssues, setQualityIssues] = useState<QualityIssueRow[]>([]);
|
const [qualityIssues, setQualityIssues] = useState<QualityIssueRow[]>([]);
|
||||||
|
const [opsHealth, setOpsHealth] = useState<OpsHealth | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [coverageLoading, setCoverageLoading] = useState(false);
|
const [coverageLoading, setCoverageLoading] = useState(false);
|
||||||
const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState('');
|
const [coverageServiceStatusTitle, setCoverageServiceStatusTitle] = useState('');
|
||||||
const [coverageFilters, setCoverageFilters] = useState<Record<string, string>>({});
|
const [coverageFilters, setCoverageFilters] = useState<Record<string, string>>({});
|
||||||
const amapConfigured = isAMapConfigured();
|
const localAMapConfigured = isAMapConfigured();
|
||||||
|
const amapConfigured = opsHealth?.runtime?.amapWebJsConfigured ?? localAMapConfigured;
|
||||||
|
const amapApiConfigured = opsHealth?.runtime?.amapApiConfigured ?? false;
|
||||||
|
const amapSecurityProxyEnabled = opsHealth?.runtime?.amapSecurityProxyEnabled ?? false;
|
||||||
|
const amapSecurityCodeExposed = opsHealth?.runtime?.amapSecurityCodeExposed ?? false;
|
||||||
|
|
||||||
const loadCoverage = (values?: Record<string, string>) => {
|
const loadCoverage = (values?: Record<string, string>) => {
|
||||||
const nextValues = values ?? {};
|
const nextValues = values ?? {};
|
||||||
@@ -292,7 +297,8 @@ export function Dashboard({
|
|||||||
api.vehicleServiceSummary().then(setServiceSummary),
|
api.vehicleServiceSummary().then(setServiceSummary),
|
||||||
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
|
api.vehicleCoverage(new URLSearchParams({ limit: '8' })).then((page) => setCoverage(page.items)),
|
||||||
api.vehicleRealtime(new URLSearchParams({ limit: '8' })).then((page) => setLocations(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.alertEvents(new URLSearchParams({ limit: '5' })).then((page) => setQualityIssues(page.items)),
|
||||||
|
api.opsHealth().then(setOpsHealth)
|
||||||
];
|
];
|
||||||
Promise.allSettled(tasks)
|
Promise.allSettled(tasks)
|
||||||
.then((results) => {
|
.then((results) => {
|
||||||
@@ -499,6 +505,62 @@ export function Dashboard({
|
|||||||
onClick: () => onOpenMileage()
|
onClick: () => onOpenMileage()
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
const mapReadiness = [
|
||||||
|
{
|
||||||
|
title: '高德 Web JS',
|
||||||
|
value: amapConfigured ? '已配置' : '待配置',
|
||||||
|
color: amapConfigured ? 'green' as const : 'orange' as const,
|
||||||
|
detail: amapConfigured ? '实时监控和轨迹回放可加载高德底图。' : '缺少 Web JS Key 时降级为坐标预览。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '安全代理',
|
||||||
|
value: amapSecurityProxyEnabled ? '已启用' : '未启用',
|
||||||
|
color: amapSecurityProxyEnabled ? 'green' as const : 'orange' as const,
|
||||||
|
detail: amapSecurityProxyEnabled ? '安全码由服务端代理追加,不下发到浏览器。' : '生产环境建议启用 /_AMapService。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '服务端 API',
|
||||||
|
value: amapApiConfigured ? '已配置' : '待配置',
|
||||||
|
color: amapApiConfigured ? 'green' as const : 'orange' as const,
|
||||||
|
detail: amapApiConfigured ? '逆地理、路线、围栏等服务端地图能力可扩展。' : '后端地图服务会降级。'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '安全码暴露',
|
||||||
|
value: amapSecurityCodeExposed ? '需处理' : '未暴露',
|
||||||
|
color: amapSecurityCodeExposed ? 'red' as const : 'green' as const,
|
||||||
|
detail: amapSecurityCodeExposed ? '浏览器端可见安全码,需要切换到安全代理。' : '符合生产密钥收敛要求。'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const mapWorkItems = [
|
||||||
|
{
|
||||||
|
title: '实时监控',
|
||||||
|
value: `${commandLocatedCount.toLocaleString()} 个有效坐标`,
|
||||||
|
detail: '在线车辆进入地图态势,点击车辆后继续查看车辆服务和实时字段。',
|
||||||
|
action: '实时地图',
|
||||||
|
onClick: () => onOpenMap({ online: 'online' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '轨迹回放',
|
||||||
|
value: `${formatCount(summary?.activeToday)} 今日活跃`,
|
||||||
|
detail: '按车辆和时间窗回放历史位置,支持打开高德线路和 RAW 证据。',
|
||||||
|
action: '轨迹回放',
|
||||||
|
onClick: () => onOpenHistory()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '历史证据',
|
||||||
|
value: `${formatCount(summary?.frameToday)} 今日帧`,
|
||||||
|
detail: '地图点位异常时直接回查位置历史、解析字段和原始帧。',
|
||||||
|
action: '历史查询',
|
||||||
|
onClick: () => onOpenHistory({ tab: 'raw', includeFields: 'true' })
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '告警通知',
|
||||||
|
value: `${formatCount(summary?.issueVehicles)} 告警车辆`,
|
||||||
|
detail: '断链、无来源、坐标异常进入告警队列并触发通知闭环。',
|
||||||
|
action: '告警事件',
|
||||||
|
onClick: () => onOpenQuality()
|
||||||
|
}
|
||||||
|
];
|
||||||
const operationWorkbench = [
|
const operationWorkbench = [
|
||||||
{
|
{
|
||||||
title: '实时监控',
|
title: '实时监控',
|
||||||
@@ -621,7 +683,7 @@ export function Dashboard({
|
|||||||
`实时地图有效定位:${commandLocatedCount.toLocaleString()}`,
|
`实时地图有效定位:${commandLocatedCount.toLocaleString()}`,
|
||||||
`告警车辆:${formatCount(summary?.issueVehicles)}`,
|
`告警车辆:${formatCount(summary?.issueVehicles)}`,
|
||||||
`Kafka Lag:${formatLag(summary?.kafkaLag)}`,
|
`Kafka Lag:${formatLag(summary?.kafkaLag)}`,
|
||||||
`高德地图:${amapConfigured ? '已配置' : '待配置'}`,
|
`高德地图:Web JS ${amapConfigured ? '已配置' : '待配置'} / 服务端 API ${amapApiConfigured ? '已配置' : '待配置'} / 安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'}`,
|
||||||
`链路健康:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'}`,
|
`链路健康:${unhealthyLinks.length > 0 ? unhealthyLinks.map((item) => `${item.name}=${item.status}`).join(';') : '正常'}`,
|
||||||
`优先动作:${priorityAction ? `${priorityAction.label} ${priorityAction.count.toLocaleString()}辆 - ${priorityAction.detail}` : '暂无待办'}`,
|
`优先动作:${priorityAction ? `${priorityAction.label} ${priorityAction.count.toLocaleString()}辆 - ${priorityAction.detail}` : '暂无待办'}`,
|
||||||
highPriorityIssue ? `最高告警:${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)} / ${highPriorityIssue.lastSeen || '-'}` : '最高告警:暂无',
|
highPriorityIssue ? `最高告警:${highPriorityIssue.severity === 'error' ? 'P0' : 'P1'} ${qualityIssueLabel(highPriorityIssue.issueType)} / ${priorityIssueVehicleLabel(highPriorityIssue)} / ${highPriorityIssue.lastSeen || '-'}` : '最高告警:暂无',
|
||||||
@@ -640,7 +702,7 @@ export function Dashboard({
|
|||||||
const lines = [
|
const lines = [
|
||||||
'【车辆数据中台功能蓝图】',
|
'【车辆数据中台功能蓝图】',
|
||||||
'定位:32960 / 808 / 宇通 MQTT 都只是车辆服务证据源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。',
|
'定位:32960 / 808 / 宇通 MQTT 都只是车辆服务证据源,最终围绕一辆车提供实时、轨迹、历史、告警、统计能力。',
|
||||||
`高德地图:${amapConfigured ? '已配置,前端只使用运行时配置和安全代理' : '待配置'}`,
|
`高德地图:Web JS ${amapConfigured ? '已配置' : '待配置'};服务端 API ${amapApiConfigured ? '已配置' : '待配置'};安全代理 ${amapSecurityProxyEnabled ? '已启用' : '未启用'};安全码${amapSecurityCodeExposed ? '已暴露' : '未暴露'}`,
|
||||||
`车辆规模:${formatCount(serviceSummary?.totalVehicles)};在线:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)};告警:${formatCount(summary?.issueVehicles)}`,
|
`车辆规模:${formatCount(serviceSummary?.totalVehicles)};在线:${formatCount(serviceSummary?.onlineVehicles ?? summary?.onlineVehicles)};告警:${formatCount(summary?.issueVehicles)}`,
|
||||||
'',
|
'',
|
||||||
...scenarioNavigation.map((item, index) => [
|
...scenarioNavigation.map((item, index) => [
|
||||||
@@ -814,6 +876,31 @@ export function Dashboard({
|
|||||||
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
<Button size="small" theme="light" type="primary" onClick={exportDashboardSnapshot}>导出驾驶舱 CSV</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
<Card bordered title="地图运营能力" style={{ marginBottom: 16 }}>
|
||||||
|
<div className="vp-map-ops-board">
|
||||||
|
<div className="vp-map-ops-readiness">
|
||||||
|
{mapReadiness.map((item) => (
|
||||||
|
<div key={item.title} className="vp-map-ops-readiness-item">
|
||||||
|
<Tag color={item.color}>{item.title}</Tag>
|
||||||
|
<div className="vp-map-ops-value">{item.value}</div>
|
||||||
|
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="vp-map-ops-work">
|
||||||
|
{mapWorkItems.map((item) => (
|
||||||
|
<button key={item.title} className="vp-map-ops-work-item" type="button" onClick={item.onClick} aria-label={`地图运营 ${item.title}`}>
|
||||||
|
<div className="vp-map-ops-work-head">
|
||||||
|
<Typography.Title heading={6} style={{ margin: 0 }}>{item.title}</Typography.Title>
|
||||||
|
<Tag color="blue">{item.action}</Tag>
|
||||||
|
</div>
|
||||||
|
<div className="vp-map-ops-work-value">{item.value}</div>
|
||||||
|
<Typography.Text type="secondary">{item.detail}</Typography.Text>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
{focusVehicle ? (
|
{focusVehicle ? (
|
||||||
<Card
|
<Card
|
||||||
bordered
|
bordered
|
||||||
|
|||||||
@@ -1300,6 +1300,68 @@ button.vp-realtime-command-item:focus-visible {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-board {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(320px, 0.9fr) minmax(0, 1.1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-readiness,
|
||||||
|
.vp-map-ops-work {
|
||||||
|
display: grid;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-readiness {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-readiness-item,
|
||||||
|
.vp-map-ops-work-item {
|
||||||
|
min-height: 128px;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid var(--vp-border);
|
||||||
|
border-radius: var(--vp-radius);
|
||||||
|
background: #fbfcff;
|
||||||
|
display: grid;
|
||||||
|
gap: 9px;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-value,
|
||||||
|
.vp-map-ops-work-value {
|
||||||
|
color: var(--vp-text);
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-work {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-work-item {
|
||||||
|
width: 100%;
|
||||||
|
color: inherit;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.16s ease, box-shadow 0.16s ease, background 0.16s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-map-ops-work-item:hover,
|
||||||
|
.vp-map-ops-work-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-map-ops-work-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.vp-runbook-grid {
|
.vp-runbook-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, minmax(0, 1fr));
|
grid-template-columns: repeat(5, minmax(0, 1fr));
|
||||||
@@ -1714,6 +1776,9 @@ button.vp-realtime-command-item:focus-visible {
|
|||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.vp-evidence-grid,
|
.vp-evidence-grid,
|
||||||
.vp-action-grid,
|
.vp-action-grid,
|
||||||
|
.vp-map-ops-board,
|
||||||
|
.vp-map-ops-readiness,
|
||||||
|
.vp-map-ops-work,
|
||||||
.vp-current-service-board,
|
.vp-current-service-board,
|
||||||
.vp-current-service-grid,
|
.vp-current-service-grid,
|
||||||
.vp-realtime-command-board,
|
.vp-realtime-command-board,
|
||||||
|
|||||||
@@ -342,7 +342,23 @@ test('dashboard presents one vehicle service operating posture', async () => {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: async () => ({
|
json: async () => ({
|
||||||
data: { linkHealth: [], kafkaLag: 0, activeConnections: 0, capacityFindings: [], redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } },
|
data: {
|
||||||
|
linkHealth: [],
|
||||||
|
kafkaLag: 0,
|
||||||
|
activeConnections: 0,
|
||||||
|
capacityFindings: [],
|
||||||
|
redisOnlineKeys: 0,
|
||||||
|
tdengineWritable: true,
|
||||||
|
mysqlWritable: true,
|
||||||
|
runtime: {
|
||||||
|
requestTimeoutMs: 5000,
|
||||||
|
amapWebJsConfigured: true,
|
||||||
|
amapApiConfigured: true,
|
||||||
|
amapSecurityProxyEnabled: true,
|
||||||
|
amapSecurityCodeExposed: false,
|
||||||
|
amapSecurityServiceHost: '/_AMapService'
|
||||||
|
}
|
||||||
|
},
|
||||||
traceId: 'trace-test',
|
traceId: 'trace-test',
|
||||||
timestamp: 1783094400000
|
timestamp: 1783094400000
|
||||||
})
|
})
|
||||||
@@ -419,7 +435,23 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
|||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
json: async () => ({
|
json: async () => ({
|
||||||
data: { linkHealth: [], kafkaLag: 0, activeConnections: 0, capacityFindings: [], redisOnlineKeys: 0, tdengineWritable: true, mysqlWritable: true, runtime: { requestTimeoutMs: 5000 } },
|
data: {
|
||||||
|
linkHealth: [],
|
||||||
|
kafkaLag: 0,
|
||||||
|
activeConnections: 0,
|
||||||
|
capacityFindings: [],
|
||||||
|
redisOnlineKeys: 0,
|
||||||
|
tdengineWritable: true,
|
||||||
|
mysqlWritable: true,
|
||||||
|
runtime: {
|
||||||
|
requestTimeoutMs: 5000,
|
||||||
|
amapWebJsConfigured: true,
|
||||||
|
amapApiConfigured: true,
|
||||||
|
amapSecurityProxyEnabled: true,
|
||||||
|
amapSecurityCodeExposed: false,
|
||||||
|
amapSecurityServiceHost: '/_AMapService'
|
||||||
|
}
|
||||||
|
},
|
||||||
traceId: 'trace-test',
|
traceId: 'trace-test',
|
||||||
timestamp: 1783094400000
|
timestamp: 1783094400000
|
||||||
})
|
})
|
||||||
@@ -513,7 +545,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('高德 Web JS')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('服务端 API')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('安全代理')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('安全码暴露')).toBeInTheDocument();
|
||||||
|
expect(screen.getAllByText('已配置').length).toBeGreaterThanOrEqual(2);
|
||||||
|
expect(screen.getByText('已启用')).toBeInTheDocument();
|
||||||
|
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);
|
||||||
@@ -538,9 +578,21 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
|
|||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台功能蓝图】'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台功能蓝图】'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('32960 / 808 / 宇通 MQTT 都只是车辆服务证据源'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('32960 / 808 / 宇通 MQTT 都只是车辆服务证据源'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆规模:1,033;在线:208;告警:7'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆规模:1,033;在线:208;告警:7'));
|
||||||
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('高德地图:Web JS 已配置;服务端 API 已配置;安全代理 已启用;安全码未暴露'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 实时监控'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('1. 实时监控'));
|
||||||
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('SLA:目标 0-1 秒内进入实时视图'));
|
expect(writeText).toHaveBeenCalledWith(expect.stringContaining('SLA:目标 0-1 秒内进入实时视图'));
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: '地图运营 实时监控' }));
|
||||||
|
expect(window.location.hash).toBe('#/map?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: '作业台 实时监控 打开实时地图' }));
|
fireEvent.click(screen.getByRole('button', { name: '作业台 实时监控 打开实时地图' }));
|
||||||
expect(window.location.hash).toBe('#/map?online=online');
|
expect(window.location.hash).toBe('#/map?online=online');
|
||||||
cleanup();
|
cleanup();
|
||||||
@@ -1002,7 +1054,7 @@ test('dashboard exposes end-to-end operations workflow entries', async () => {
|
|||||||
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.getAllByText('历史证据').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getAllByText('告警事件').length).toBeGreaterThanOrEqual(1);
|
expect(screen.getAllByText('告警事件').length).toBeGreaterThanOrEqual(1);
|
||||||
expect(screen.getByText('统计复核')).toBeInTheDocument();
|
expect(screen.getByText('统计复核')).toBeInTheDocument();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user