{workflowSteps.map((item, index) => (
diff --git a/vehicle-data-platform/apps/web/src/styles/global.css b/vehicle-data-platform/apps/web/src/styles/global.css
index c3fb4651..e977a2f7 100644
--- a/vehicle-data-platform/apps/web/src/styles/global.css
+++ b/vehicle-data-platform/apps/web/src/styles/global.css
@@ -1160,6 +1160,42 @@ button.vp-realtime-command-item:focus-visible {
line-height: 32px;
}
+.vp-scenario-grid {
+ display: grid;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ gap: 12px;
+}
+
+.vp-scenario-item {
+ min-height: 220px;
+ padding: 14px;
+ border: 1px solid var(--vp-border);
+ border-radius: var(--vp-radius);
+ background: #fbfcff;
+ display: grid;
+ gap: 12px;
+ align-content: space-between;
+}
+
+.vp-scenario-main {
+ display: grid;
+ gap: 8px;
+}
+
+.vp-scenario-main .semi-typography {
+ line-height: 20px;
+}
+
+.vp-scenario-evidence {
+ min-height: 42px;
+ padding: 10px;
+ border-radius: 6px;
+ background: rgba(22, 100, 255, 0.06);
+ color: var(--vp-text);
+ font-size: 12px;
+ line-height: 18px;
+}
+
.vp-operation-step {
min-height: 176px;
padding: 14px;
@@ -1317,6 +1353,7 @@ button.vp-realtime-command-item:focus-visible {
.vp-realtime-command-board,
.vp-realtime-command-grid,
.vp-workbench-grid,
+ .vp-scenario-grid,
.vp-operation-flow,
.vp-service-model-grid,
.vp-capability-grid,
diff --git a/vehicle-data-platform/apps/web/src/test/App.test.tsx b/vehicle-data-platform/apps/web/src/test/App.test.tsx
index da22d2b3..08ee8470 100644
--- a/vehicle-data-platform/apps/web/src/test/App.test.tsx
+++ b/vehicle-data-platform/apps/web/src/test/App.test.tsx
@@ -408,6 +408,11 @@ test('dashboard presents one vehicle service operating posture', async () => {
test('dashboard exposes vehicle data center capability matrix', async () => {
window.history.replaceState(null, '', '/#/dashboard');
+ const writeText = vi.fn(() => Promise.resolve());
+ Object.defineProperty(navigator, 'clipboard', {
+ configurable: true,
+ value: { writeText }
+ });
vi.spyOn(globalThis, 'fetch').mockImplementation(async (input) => {
const path = String(input);
if (path.includes('/api/ops/health')) {
@@ -508,6 +513,7 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
await renderDashboard();
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);
@@ -525,11 +531,31 @@ test('dashboard exposes vehicle data center capability matrix', async () => {
expect(screen.getAllByText('今日帧 1,286,320').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('告警 7').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('统计车辆 1,033').length).toBeGreaterThanOrEqual(1);
+ expect(screen.getByText('确认车辆是否在线、位置是否可信、三类来源是否齐全。')).toBeInTheDocument();
+ expect(screen.getByText('按车辆复盘位置、速度、里程和断点,定位平台转发或车辆异常。')).toBeInTheDocument();
+ expect(screen.getByText('优先返回必要字段,避免大 JSON 拖慢查询')).toBeInTheDocument();
+ fireEvent.click(screen.getByRole('button', { name: '复制功能蓝图' }));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('【车辆数据中台功能蓝图】'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('32960 / 808 / 宇通 MQTT 都只是车辆服务证据源'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('车辆规模:1,033;在线:208;告警:7'));
+ expect(writeText).toHaveBeenCalledWith(expect.stringContaining('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).toBe('#/realtime?online=online');
+ cleanup();
+
+ await renderDashboard();
+ fireEvent.click(screen.getByRole('button', { name: '场景导航 轨迹回放 RAW证据' }));
+ 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('#/realtime?online=online');