refactor(ui): refine semi mobile workspace launcher

This commit is contained in:
lingniu
2026-07-19 21:40:31 +08:00
parent d0c2bb3316
commit f8baaf53ba
4 changed files with 201 additions and 11 deletions

View File

@@ -197,11 +197,12 @@ test('uses the compact mobile navigation and opens secondary modules in a Semi S
expect(screen.getByRole('navigation', { name: '平台治理' })).toBeInTheDocument();
expect(moreSheet).toHaveTextContent('历史证据与业务告警');
expect(moreSheet).toHaveTextContent('接入、账号与运维质量');
expect(screen.getByRole('link', { name: '历史数据' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: '告警中心' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: '接入管理' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: '账号管理' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: '运维质量' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: '历史数据' })).toHaveTextContent('按时间回溯原始上报');
expect(screen.getByRole('link', { name: '告警中心' })).toHaveTextContent('集中查看并闭环风险');
expect(screen.getByRole('link', { name: '接入管理' })).toHaveTextContent('核对身份、协议与差异');
expect(screen.getByRole('link', { name: '账号管理' })).toHaveTextContent('管理客户与车辆权限');
expect(screen.getByRole('link', { name: '运维质量' })).toHaveTextContent('诊断链路与数据质量');
expect(moreSheet.querySelectorAll('.v2-mobile-more-item-copy')).toHaveLength(5);
fireEvent.click(screen.getByLabelText('关闭更多功能'));
expect(screen.getByRole('button', { name: '更多功能' })).toHaveAttribute('aria-expanded', 'false');

View File

@@ -4,6 +4,7 @@ import {
IconBox,
IconChevronDown,
IconChevronLeft,
IconChevronRight,
IconHelpCircle,
IconHome,
IconLock,
@@ -283,13 +284,20 @@ const mobileMoreGroups = [
key: 'insight',
title: '分析与处置',
description: '历史证据与业务告警',
items: [navigation[3], navigation[5]]
items: [
{ ...navigation[3], description: '按时间回溯原始上报' },
{ ...navigation[5], description: '集中查看并闭环风险' }
]
},
{
key: 'governance',
title: '平台治理',
description: '接入、账号与运维质量',
items: [navigation[6], navigation[7], operationsNavigation]
items: [
{ ...navigation[6], description: '核对身份、协议与差异' },
{ ...navigation[7], description: '管理客户与车辆权限' },
{ ...operationsNavigation, description: '诊断链路与数据质量' }
]
}
];
const mobileMoreNavigation = mobileMoreGroups.flatMap((group) => group.items);
@@ -307,14 +315,19 @@ function MobileNavigation() {
const itemCount = primary.length + (more.length ? 1 : 0);
const warmRoute = (path: string) => { if (shouldPreloadRouteOnIntent()) void preloadRoute(path); };
useEffect(() => setMoreOpen(false), [location.pathname]);
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>;
const primaryLink = ({ to, label, icon: Icon }: (typeof navigation)[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>;
const moreLink = ({ to, label, description, icon: Icon }: (typeof mobileMoreNavigation)[number]) => <NavLink key={to} to={to} aria-label={label} onPointerDown={() => warmRoute(to)} className={({ isActive }) => `v2-mobile-nav-item v2-mobile-more-item ${isActive ? 'is-active' : ''}`}>
<Icon className="v2-mobile-more-item-icon" size="large" />
<span className="v2-mobile-more-item-copy"><strong>{label}</strong><small>{description}</small></span>
<IconChevronRight className="v2-mobile-more-item-chevron" aria-hidden="true" />
</NavLink>;
return <>
{more.length ? <WorkspaceSideSheet
className="v2-mobile-more-sidesheet"
variant="action"
visible={moreOpen}
placement="bottom"
height="min(54dvh, 460px)"
height="min(60dvh, 500px)"
icon={<IconMore />}
title="更多功能"
description="数据分析、业务处置与平台治理"
@@ -329,12 +342,12 @@ function MobileNavigation() {
<div><strong id={`v2-mobile-more-${group.key}`}>{group.title}</strong><span>{group.description}</span></div>
<Tag size="small" color="blue">{group.items.length} </Tag>
</header>
<nav aria-label={group.title}>{group.items.map(link)}</nav>
<nav aria-label={group.title}>{group.items.map(moreLink)}</nav>
</section>)}
</div>
</WorkspaceSideSheet> : null}
<nav className="v2-mobile-navigation" aria-label="主导航" style={{ '--v2-mobile-nav-count': Math.max(itemCount, 1) } as CSSProperties}>
{primary.map(link)}
{primary.map(primaryLink)}
{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}
</nav>
</>;

View File

@@ -22476,11 +22476,138 @@
padding: 8px 9px 10px;
}
.v2-mobile-more-sidesheet .v2-mobile-more-groups {
gap: 14px;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section {
overflow: visible;
border: 0;
border-radius: 0;
background: transparent;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section > header {
min-height: 38px;
border: 0;
padding: 2px 2px 7px;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section > header strong {
color: #1f3047;
font-size: 12px;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section > header span {
font-size: 9px;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section > nav,
.v2-mobile-more-sidesheet .v2-mobile-more-section:first-child > nav {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
padding: 0;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section .v2-mobile-nav-item.v2-mobile-more-item {
display: grid;
min-height: 68px;
grid-template-columns: 34px minmax(0, 1fr) 12px;
align-items: center;
justify-content: initial;
gap: 8px;
border: 1px solid #e0e7f0;
border-radius: 12px;
background: #fff;
padding: 10px;
color: #506178;
text-align: left;
box-shadow: 0 5px 16px rgba(31, 54, 83, .045);
}
.v2-mobile-more-sidesheet .v2-mobile-more-section .v2-mobile-nav-item.v2-mobile-more-item:hover,
.v2-mobile-more-sidesheet .v2-mobile-more-section .v2-mobile-nav-item.v2-mobile-more-item:active {
border-color: #cbdaf0;
background: #f8fbff;
}
.v2-mobile-more-sidesheet .v2-mobile-more-section .v2-mobile-nav-item.v2-mobile-more-item.is-active {
border-color: #a9c9fb;
background: #eef5ff;
color: #1268f3;
box-shadow: 0 7px 20px rgba(18, 104, 243, .1);
}
.v2-mobile-more-sidesheet .v2-mobile-more-item.is-active::after {
display: none;
}
.v2-mobile-more-item-icon {
display: grid;
width: 34px;
height: 34px;
place-items: center;
border: 1px solid #dce7f6;
border-radius: 10px;
background: #f3f7fc;
color: #58708d;
font-size: 16px;
}
.v2-mobile-more-item.is-active .v2-mobile-more-item-icon {
border-color: #c8dcfb;
background: #fff;
color: #1268f3;
}
.v2-mobile-more-item-copy {
display: grid;
min-width: 0;
gap: 3px;
}
.v2-mobile-more-item-copy strong,
.v2-mobile-more-item-copy small {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.v2-mobile-more-item-copy strong {
color: #263951;
font-size: 11px;
font-weight: 750;
line-height: 1.2;
}
.v2-mobile-more-item-copy small {
color: #8794a7;
font-size: 8px;
line-height: 1.25;
}
.v2-mobile-more-item.is-active .v2-mobile-more-item-copy strong {
color: #1268f3;
}
.v2-mobile-more-item-chevron {
color: #a5b0bf;
font-size: 11px;
}
.v2-vehicle-mobile-actions-sidesheet .v2-workspace-config-content {
padding: 9px 11px;
}
}
@media (max-width: 600px) {
:root .v2-mobile-navigation .v2-mobile-nav-item > span,
:root .v2-mobile-navigation > .semi-button.v2-mobile-nav-item .semi-button-content {
font-size: 10px;
letter-spacing: .01em;
}
}
@media (max-width: 900px) and (max-height: 480px) and (orientation: landscape) {
.v2-workspace-action-sidesheet.v2-vehicle-mobile-actions-sidesheet.semi-sidesheet-bottom .semi-sidesheet-inner {
height: min(100dvh, 380px) !important;