refactor(ui): refine semi mobile workspace launcher
This commit is contained in:
@@ -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');
|
||||
|
||||
@@ -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>
|
||||
</>;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -47,4 +47,53 @@
|
||||
- 同视口并排对比确认地图在首屏获得约 `83px` 的额外可见空间,车型和里程信息完整。
|
||||
- 未发现 P0、P1 或 P2 视觉与交互问题。
|
||||
|
||||
## 全站移动导航 Semi UI Design QA
|
||||
|
||||
- source visual truth: ECS 生产端 `/monitor?mode=list`
|
||||
- source screenshots:
|
||||
- shell baseline: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/before-monitor-mobile.png`
|
||||
- more baseline: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/before-more-sheet.png`
|
||||
- help baseline: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/before-help-sheet.png`
|
||||
- statistics filter baseline: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/before-statistics-filter.png`
|
||||
- implementation screenshots:
|
||||
- more launcher: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/after-more-sheet-local.png`
|
||||
- active module: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/after-more-active-fixed-local.png`
|
||||
- comparison evidence:
|
||||
- before/after: `/Users/lingniu/project/ai-coding/lingniu-vehicle-ingest/.codex-audit/semi-ui-shell/compare-more-before-after.png`
|
||||
- viewport and state:
|
||||
- mobile `390 × 844`
|
||||
- 管理员权限、全局监控列表模式
|
||||
|
||||
### Fidelity Ledger
|
||||
|
||||
| Surface | Required fidelity | Implementation evidence | Result |
|
||||
| --- | --- | --- | --- |
|
||||
| Permission model | 客户与管理员仍只能看到已授权菜单。 | 继续使用 `hasMenu()` 过滤主导航和更多功能分组,未新增绕过入口。 | Preserved. |
|
||||
| Navigation hierarchy | 高频任务保留底栏,低频任务必须有清晰业务分组。 | 全局监控、车辆查询、轨迹回放、里程查询仍在底栏;历史/告警与平台治理维持两组。 | Passed. |
|
||||
| Module recognition | 更多功能不能只靠小图标和短标签辨认。 | 每个模块增加真实任务说明、独立 Semi 图标容器和进入指示。 | Improved. |
|
||||
| Current state | 位于二级模块时,重新打开更多功能必须识别当前位置。 | 当前模块使用蓝色边框、浅蓝底色和图标强调,未叠加重复装饰线。 | Passed. |
|
||||
| Touch density | 入口需要适合移动端点击,同时单屏展示完整模块集合。 | 模块卡片最小高度 `68px`,两列布局在 `390 × 844` 下完整显示五个入口。 | Passed. |
|
||||
| Shared shell | 帮助、筛选与更多功能应属于同一 Semi UI 工作台体系。 | 继续复用 `WorkspaceSideSheet` 的标题、徽标、遮罩、圆角和滚动边界。 | Preserved. |
|
||||
| Asset fidelity | 不使用临时图形或自制 SVG。 | 全部使用 `@douyinfe/semi-icons` 与 Semi UI `SideSheet`、`Tag`、`Button`。 | Preserved. |
|
||||
|
||||
### Interaction And Accessibility Checks
|
||||
|
||||
- 底栏仍为命名为“主导航”的可访问导航区域;
|
||||
- 更多按钮继续暴露 `aria-expanded` 和 `aria-controls`;
|
||||
- 两个业务分组均为独立命名导航;
|
||||
- 五个模块入口保留链接语义与预加载行为;
|
||||
- 当前模块继续由 React Router 输出 `aria-current="page"`;
|
||||
- 关闭按钮仍使用“关闭更多功能”的可访问名称;
|
||||
- `390 × 844` 同视口前后对照未出现横向溢出、裁切或不可达入口;
|
||||
- 页面帮助和里程筛选基线检查未发现本次共享样式导致的回退。
|
||||
|
||||
### Comparison History
|
||||
|
||||
#### Pass 1 — fixed
|
||||
|
||||
- 旧版入口卡片依赖小图标和短标签,任务意图不够明确,分组又被大面积边框切割。
|
||||
- 新版保持原路由与权限边界,用两列 Semi UI 模块卡片展示任务说明,并减弱分组容器噪声。
|
||||
- 首次视觉复核发现当前模块仍继承底栏的底部装饰线;已在更多功能作用域内移除,保留单一高亮语义。
|
||||
- 同尺寸并排对比确认五个入口均可见,层级更清晰,未发现 P0、P1 或 P2 视觉与交互问题。
|
||||
|
||||
final result: passed
|
||||
|
||||
Reference in New Issue
Block a user