Files
docs/superpowers/specs/2026-07-27-client-navigation-design.md
T

7.4 KiB
Raw Blame History

Client Web 导航分级与移动侧栏设计

背景

apps/client-web 当前把业务总览、企业资料、企业成员、角色管理、菜单权限和头程订单全部渲染为同级侧栏链接。权威原型 docs/悦/头程下单/柏悦客户端.html 采用“一级业务域 + 二级具体任务”的导航结构,现有实现没有延续这一信息架构,也无法承接后续业务扩展。

Impeccable 验收还发现一个阻断性问题:720px 以下侧栏被隐藏,但页面上的菜单按钮没有打开移动导航的行为,导致移动端无法切换模块。

目标

  • 将客户工作台的平铺菜单改为可扩展的两级分组导航。
  • 保留现有路由、页面、文案、品牌颜色与 Element Plus 技术体系。
  • 修复移动端导航不可用的问题。
  • 让菜单节点显式携带图标与权限信息,避免依赖数组下标。
  • 为后续真实权限集合接入保留清晰边界,不在本次伪造授权数据。

非目标

  • 不新增原型中尚未实现的一键导入、快速下单、海外仓或财务结算入口。
  • 不改变现有路由地址,不拆分当前 ModuleView.vue 页面。
  • 不照搬原型的紫色渐变视觉,不重做登录、注册或业务内容页。
  • 不实现服务端权限校验,也不把前端菜单隐藏当作安全控制。
  • 不新增测试框架;当前项目尚未声明测试脚本。

导航信息架构

采用两级分组,业务总览保留直接入口:

业务总览                         /

头程业务                         可展开分组
└── 头程订单                     /orders/first-leg

企业管理                         可展开分组
├── 企业资料                     /company/profile
├── 企业成员                     /organization/members
├── 角色管理                     /organization/roles
└── 菜单权限                     /organization/permissions

分组遵循原型的结构原则,但只呈现当前真实存在的客户端路由。后续功能只有在页面与路由实现后,才加入对应业务分组。

组件与数据结构

导航配置

新增客户端本地导航配置 src/navigation/clientNavigation.ts。定义客户端专用的 ClientNavigationNode,包含:

  • 稳定 key;
  • 用户可见 title;
  • Lucide 图标组件;
  • 可选 path;
  • 可选 permission;
  • 可选 children。

共享包中的 NavigationItem 仍保持单层公共模型,避免为了单个应用的壳层结构扩大跨项目契约。

侧栏菜单组件

新增独立的客户端侧栏菜单组件,使用 Element Plus 的 el-menu、el-sub-menu 与 el-menu-item:

  • active-path 由当前路由提供;
  • 父分组可展开和收起;
  • 当前路由所在分组默认展开;
  • 叶子菜单负责实际路由跳转;
  • 图标来自节点配置,不依赖渲染顺序;
  • 桌面与移动端复用同一组件和同一份导航数据。

本轮不额外实现展开状态持久化;Element Plus 在当前会话中维护用户开合状态,且当前路由所在分组始终可识别。持久化不是修复平铺结构与移动阻断所必需的能力。

应用壳层

AppLayout.vue 继续负责品牌、租户、用户和页面标题,同时增加:

  • mobileMenuOpen 状态;
  • 移动菜单按钮的 aria-expanded;
  • 遮罩关闭;
  • 路由切换后自动关闭;
  • Escape 键关闭;
  • 打开时阻止背景页面滚动;
  • 退出确认取消时不产生未处理异常。

桌面侧栏保持固定宽度和现有绿色服务型视觉,不引入后台的深色侧栏,也不增加桌面折叠模式。

交互与状态流

桌面端

  1. 路由加载后,菜单以 route.path 高亮对应叶子。
  2. “业务总览”为直接入口。
  3. “头程业务”和“企业管理”为分组触发器。
  4. 当前页面属于某分组时,该分组保持展开并显示父级激活语义。
  5. 用户可以独立展开或收起其他分组。

移动端

  1. 720px 以下侧栏移出视口,而不是使用 display: none 永久移除。
  2. 点击页面标题前的菜单按钮打开抽屉并显示遮罩。
  3. 点击遮罩、按 Escape 或选择任一叶子路由后关闭抽屉。
  4. 抽屉打开时禁止背景滚动,关闭或组件卸载时恢复。
  5. 抽屉沿用完整租户导航,不维护第二份移动菜单。

权限

导航节点继续携带权限编码。当前模拟用户没有可靠的权限集合,因此静态阶段显示全部已实现菜单。真实认证接口返回权限集合后,再增加叶子过滤;父分组没有可访问子项时隐藏。服务端必须继续执行租户与权限校验。

视觉与可访问性

  • 保留现有 --primary、--navy、--border 和 --muted 设计令牌。
  • 一级分组与二级叶子通过缩进、字号、颜色和激活背景形成清晰层级。
  • 分组触发器与移动菜单按钮使用真实可聚焦控件。
  • 保持可见 :focus-visible 状态,不只依赖 hover 或颜色表达当前状态。
  • 菜单触控目标不低于约 44px。
  • 二级菜单文本允许单行省略,避免长租户业务名称破坏侧栏宽度。
  • 侧栏导航区域允许纵向滚动;底部帮助区保持可达。
  • 320px、720px 与桌面宽度均不能依赖水平滚动完成导航。

错误与边界处理

  • 用户取消退出确认时保持当前页面,不产生控制台未处理异常。
  • 移动抽屉在快速路由切换、浏览器后退和组件卸载后不得残留锁定滚动状态。
  • 不存在或被移除的路由继续走现有兜底重定向。
  • 当前不虚构客户经理电话、微信或帮助中心链接;帮助区保留为信息提示,后续有真实渠道后再做可执行入口。
  • 权限为空或数据尚未加载时,本轮不提前隐藏菜单,避免模拟阶段出现无法恢复的空侧栏。

实施范围

预计修改或新增:

  • apps/client-web/src/navigation/clientNavigation.ts
  • apps/client-web/src/components/ClientSidebarMenu.vue
  • apps/client-web/src/layouts/AppLayout.vue
  • apps/client-web/src/router/index.ts
  • apps/client-web/src/styles/index.css

仅当实现需要声明自动导入组件时,依赖现有插件自动生成声明,不手工编辑 auto-imports.d.ts 或 components.d.ts。

验收与验证

自动验证

在 apps/client-web 执行:

pnpm typecheck
pnpm build

项目当前没有 test 或 lint 脚本,因此不声称相应检查通过,也不为本次导航改动引入整套测试设施。

Impeccable 验收

  • 运行一次机械检测,目标覆盖本次变更的 Vue 文件。
  • 桌面检查:一级分组、二级缩进、当前路由高亮、可见焦点和侧栏滚动。
  • 720px 检查:断点切换时导航仍可达。
  • 320px 检查:抽屉、遮罩、触控目标与页面内容不发生水平溢出。
  • 浏览器可用时检查菜单打开、选择路由、遮罩关闭和 Escape 关闭;若浏览器不可用,明确记录回退证据,不伪装成已完成视觉实测。

完成标准

  • 侧栏不再把全部功能放在一级菜单。
  • 当前六个页面入口全部可达,路由地址不变。
  • 桌面端当前页面与父分组状态清晰。
  • 720px 以下菜单按钮能够打开和关闭真实导航抽屉。
  • 导航图标不会因菜单调序或新增分组而错位。
  • 未实现的原型功能不会作为无效入口出现。
  • 类型检查、生产构建与 Impeccable 检测无未解释失败。