From af608b35e0c144e6d6bf623f7df2cfad2d09f366 Mon Sep 17 00:00:00 2001 From: lingniu Date: Sun, 19 Jul 2026 07:13:09 +0800 Subject: [PATCH] feat: unify global action workbenches --- .../apps/web/src/v2/layout/AppShell.test.tsx | 8 +- .../apps/web/src/v2/layout/AppShell.tsx | 53 +++-- .../apps/web/src/v2/pages/VehicleActions.tsx | 24 ++- .../web/src/v2/pages/VehiclePage.test.tsx | 2 +- .../apps/web/src/v2/productionEntry.test.ts | 8 + .../src/v2/shared/WorkspaceSideSheet.test.tsx | 30 ++- .../web/src/v2/shared/WorkspaceSideSheet.tsx | 10 +- .../apps/web/src/v2/styles/workspace.css | 194 ++++++++++++++++++ 8 files changed, 295 insertions(+), 34 deletions(-) diff --git a/vehicle-data-platform/apps/web/src/v2/layout/AppShell.test.tsx b/vehicle-data-platform/apps/web/src/v2/layout/AppShell.test.tsx index bf37eaec..75ce78ee 100644 --- a/vehicle-data-platform/apps/web/src/v2/layout/AppShell.test.tsx +++ b/vehicle-data-platform/apps/web/src/v2/layout/AppShell.test.tsx @@ -99,6 +99,11 @@ test('opens contextual help for the active module and closes it without navigati fireEvent.click(help); expect(screen.getByRole('dialog', { name: '全局监控' })).toHaveTextContent('筛选会自动生效'); + expect(document.querySelector('.v2-help-sheet')).toHaveClass('v2-workspace-help-sidesheet'); + const helpSummary = screen.getByRole('list', { name: '全局监控摘要' }); + expect(helpSummary).toHaveTextContent('页面目标快速完成'); + expect(helpSummary).toHaveTextContent('建议步骤3 项'); + expect(helpSummary).toHaveTextContent('快捷关闭Esc'); expect(document.querySelector('.v2-help-overview.semi-card')).toBeInTheDocument(); expect(screen.getByText('建议操作')).toBeInTheDocument(); expect(help).toHaveAttribute('aria-expanded', 'true'); @@ -186,7 +191,8 @@ test('uses the compact mobile navigation and opens secondary modules in a Semi S fireEvent.click(screen.getByRole('button', { name: '更多功能' })); const moreSheet = screen.getByRole('dialog', { name: '更多功能' }); - expect(moreSheet).toHaveTextContent('数据分析与系统管理'); + expect(moreSheet.closest('.semi-sidesheet')).toHaveClass('v2-workspace-action-sidesheet', 'v2-mobile-more-sidesheet'); + expect(moreSheet).toHaveTextContent('数据分析、业务处置与平台治理'); expect(screen.getByRole('navigation', { name: '分析与处置' })).toBeInTheDocument(); expect(screen.getByRole('navigation', { name: '平台治理' })).toBeInTheDocument(); expect(moreSheet).toHaveTextContent('历史证据与业务告警'); diff --git a/vehicle-data-platform/apps/web/src/v2/layout/AppShell.tsx b/vehicle-data-platform/apps/web/src/v2/layout/AppShell.tsx index da1b79b7..0a2d0971 100644 --- a/vehicle-data-platform/apps/web/src/v2/layout/AppShell.tsx +++ b/vehicle-data-platform/apps/web/src/v2/layout/AppShell.tsx @@ -15,16 +15,16 @@ import { IconUser, IconExit } from '@douyinfe/semi-icons'; -import { Avatar, Banner, Button, Card, Dropdown, Layout, Modal, Nav, SideSheet, Tag, Typography } from '@douyinfe/semi-ui'; +import { Avatar, Banner, Button, Card, Dropdown, Layout, Modal, Nav, Tag, Typography } from '@douyinfe/semi-ui'; import { type CSSProperties, FormEvent, type MouseEvent, useEffect, useLayoutEffect, useState } from 'react'; import { NavLink, Outlet, useLocation, useNavigate } from 'react-router-dom'; import { api } from '../../api/client'; import { usePlatformSession } from '../auth/AuthGate'; import { hasMenu } from '../auth/session'; import { useMobileLayout } from '../hooks/useMobileLayout'; -import { useSideSheetA11y } from '../hooks/useSideSheetA11y'; import { preloadRoute, scheduleIdleRoutePreloads, shouldPreloadRouteOnIntent } from '../routing/routeModules'; import { PasswordInput } from '../shared/PasswordInput'; +import { WorkspaceSideSheet } from '../shared/WorkspaceSideSheet'; const { Header, Sider, Content } = Layout; const { Text, Title } = Typography; @@ -64,29 +64,42 @@ const pageHelp: Record = { users: { summary: '创建客户账号并分配菜单和车辆数据范围。', tips: ['客户只能使用四个对外菜单中的已分配项。', '停用账号或重置密码会立即撤销其旧会话。', '车辆权限修改最多在 30 秒内对活跃会话生效。'] } }; -function ContextHelp({ section, visible, onClose }: { section: string; visible: boolean; onClose: () => void }) { +function ContextHelp({ section, visible, mobile, onClose }: { section: string; visible: boolean; mobile: boolean; onClose: () => void }) { const content = pageHelp[section] ?? { summary: '查看当前模块的操作说明。', tips: ['通过左侧导航切换模块,页面状态会在当前任务中保留。'] }; const title = pageNames[section] ?? '车辆数据中台'; - useSideSheetA11y(visible, '.v2-help-sheet', 'v2-context-help', title, '关闭帮助'); - return
当前页面帮助{title}
} + height={mobile ? 'min(70dvh, 620px)' : undefined} + icon={} + title={title} + description="当前页面帮助与推荐操作" + badge={`${content.tips.length} 项建议`} + summaryItems={[ + { label: '页面目标', value: '快速完成', detail: '保留当前任务状态', tone: 'primary' }, + { label: '建议步骤', value: `${content.tips.length} 项`, detail: '按顺序执行' }, + { label: '快捷关闭', value: 'Esc', detail: '随时返回页面' } + ]} + footerNote="关闭帮助不会改变当前筛选、地图或任务状态" + primaryAction={{ label: '返回当前页面', onClick: onClose }} onCancel={onClose} - footer={} >
- 当前模块 + 页面目标 {content.summary}
建议操作{content.tips.length} 项
    {content.tips.map((tip, index) =>
  1. {index + 1}{tip}
  2. )}
Esc关闭帮助
-
; + ; } function AccountMenu({ @@ -189,7 +202,7 @@ export function AppShell() { - setHelpOpen(false)} /> + setHelpOpen(false)} /> {passwordOpen ? setPasswordOpen(false)} onChanged={logout} /> : null} ); @@ -281,18 +294,20 @@ function MobileNavigation() { const itemCount = primary.length + (more.length ? 1 : 0); const warmRoute = (path: string) => { if (shouldPreloadRouteOnIntent()) void preloadRoute(path); }; useEffect(() => setMoreOpen(false), [location.pathname]); - useSideSheetA11y(moreOpen, '.v2-mobile-more-sidesheet', 'v2-mobile-more', '更多功能', '关闭更多功能'); - const link = ({ to, label, icon: Icon }: (typeof mobileMoreNavigation)[number]) => warmRoute(to)} className={({ isActive }) => `v2-mobile-nav-item ${isActive ? 'is-active' : ''}`}>{label}; return <> - {more.length ? 更多功能数据分析与系统管理} - aria-label="更多功能" - footer={null} + height="min(54dvh, 460px)" + icon={} + title="更多功能" + description="数据分析、业务处置与平台治理" + badge={`${more.length} 个入口`} + ariaLabel="更多功能" + dialogId="v2-mobile-more" onCancel={() => setMoreOpen(false)} >
@@ -304,7 +319,7 @@ function MobileNavigation() { )}
-
: null} + : null}