feat: unify global action workbenches
This commit is contained in:
@@ -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('历史证据与业务告警');
|
||||
|
||||
@@ -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<string, { summary: string; tips: string[] }> = {
|
||||
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 <SideSheet
|
||||
return <WorkspaceSideSheet
|
||||
className="v2-help-sheet"
|
||||
variant="help"
|
||||
visible={visible}
|
||||
aria-label={title}
|
||||
ariaLabel={title}
|
||||
closeLabel="关闭帮助"
|
||||
dialogId="v2-context-help"
|
||||
placement={mobile ? 'bottom' : 'right'}
|
||||
width={410}
|
||||
title={<div className="v2-help-sheet-title"><span><IconHelpCircle /></span><div><Text type="tertiary" size="small">当前页面帮助</Text><Title heading={4}>{title}</Title></div></div>}
|
||||
height={mobile ? 'min(70dvh, 620px)' : undefined}
|
||||
icon={<IconHelpCircle />}
|
||||
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={<Button block theme="light" type="primary" onClick={onClose}>关闭帮助</Button>}
|
||||
>
|
||||
<div className="v2-help-panel">
|
||||
<Card className="v2-help-overview" bodyStyle={{ padding: 0 }}>
|
||||
<Tag color="blue">当前模块</Tag>
|
||||
<Tag color="blue">页面目标</Tag>
|
||||
<Text type="secondary">{content.summary}</Text>
|
||||
</Card>
|
||||
<div className="v2-help-section-label"><Text strong>建议操作</Text><Text type="tertiary" size="small">{content.tips.length} 项</Text></div>
|
||||
<ol>{content.tips.map((tip, index) => <li key={tip}><span>{index + 1}</span>{tip}</li>)}</ol>
|
||||
<footer><kbd>Esc</kbd><span>关闭帮助</span></footer>
|
||||
</div>
|
||||
</SideSheet>;
|
||||
</WorkspaceSideSheet>;
|
||||
}
|
||||
|
||||
function AccountMenu({
|
||||
@@ -189,7 +202,7 @@ export function AppShell() {
|
||||
</Header>
|
||||
<Content key={location.pathname} className={`v2-content${section === 'tracks' ? ' is-track-workspace' : ''}`}><Outlet /></Content>
|
||||
</Layout>
|
||||
<ContextHelp section={section} visible={helpOpen} onClose={() => setHelpOpen(false)} />
|
||||
<ContextHelp section={section} visible={helpOpen} mobile={mobileLayout} onClose={() => setHelpOpen(false)} />
|
||||
{passwordOpen ? <PasswordDialog onClose={() => setPasswordOpen(false)} onChanged={logout} /> : null}
|
||||
</Layout>
|
||||
);
|
||||
@@ -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]) => <NavLink key={to} to={to} aria-label={label} onPointerDown={() => warmRoute(to)} className={({ isActive }) => `v2-mobile-nav-item ${isActive ? 'is-active' : ''}`}><Icon size="large" /><span>{label}</span></NavLink>;
|
||||
return <>
|
||||
{more.length ? <SideSheet
|
||||
{more.length ? <WorkspaceSideSheet
|
||||
className="v2-mobile-more-sidesheet"
|
||||
variant="action"
|
||||
visible={moreOpen}
|
||||
placement="bottom"
|
||||
height="auto"
|
||||
title={<div><strong>更多功能</strong><span>数据分析与系统管理</span></div>}
|
||||
aria-label="更多功能"
|
||||
footer={null}
|
||||
height="min(54dvh, 460px)"
|
||||
icon={<IconMore />}
|
||||
title="更多功能"
|
||||
description="数据分析、业务处置与平台治理"
|
||||
badge={`${more.length} 个入口`}
|
||||
ariaLabel="更多功能"
|
||||
dialogId="v2-mobile-more"
|
||||
onCancel={() => setMoreOpen(false)}
|
||||
>
|
||||
<div className="v2-mobile-more-groups">
|
||||
@@ -304,7 +319,7 @@ function MobileNavigation() {
|
||||
<nav aria-label={group.title}>{group.items.map(link)}</nav>
|
||||
</section>)}
|
||||
</div>
|
||||
</SideSheet> : null}
|
||||
</WorkspaceSideSheet> : null}
|
||||
<nav className="v2-mobile-navigation" aria-label="主导航" style={{ '--v2-mobile-nav-count': Math.max(itemCount, 1) } as CSSProperties}>
|
||||
{primary.map(link)}
|
||||
{more.length ? <Button theme="borderless" className={`v2-mobile-nav-item${moreActive ? ' is-active' : ''}`} aria-label="更多功能" aria-expanded={moreOpen} aria-controls="v2-mobile-more" icon={<IconMore size="large" />} onClick={() => setMoreOpen((value) => !value)}><span>更多</span></Button> : null}
|
||||
|
||||
Reference in New Issue
Block a user