Files
shanxia2.0/docs/superpowers/specs/2026-10-04-web-admin-group-leaf-tabs-design.md
T
2026-10-05 12:57:51 +08:00

9.2 KiB
Raw Blame History

web-admin 侧栏非叶子目录 + 右侧叶子组 Tab 设计

日期:2026-10-04
状态:待审阅(实现前须产品确认本文)
交互 Demo:docs/demos/sidebar-group-leaf-tabs-demo.html
范围:web-admin 全站业务菜单导航。默认壳层为单列树形侧栏(MenuTypeEnum.LEFT),只展示非叶子;无独立一级竖向 Tab。不含小程序、不含后端菜单 API 变更。

背景与目标

当前侧栏同时展示目录和叶子,内容区一次只挂一个页面(默认 showWorkTab: false)。用户希望:

  • 左侧树只展示非叶子;
  • 点击最后一层非叶子后,右侧用 Tab 展示该层全部可见叶子;
  • 换另一个最后一层非叶子时,整组 Tab 替换;
  • 默认只加载第一个(或 URL 已指向的)叶子,点哪个 Tab 再加载哪个界面。

报销管理是典型三层(报销管理 → 基础配置 → 费用项目等)。CRM 为三层:CRM管理 → 产品/报价单/合同/客户分组 → 组内叶子;点击分组后右侧 Tab 只含该组可见叶子。

已确认决策

项 决策
覆盖范围 整站业务菜单,不是只做报销
Tab 形态 整组固定 Tab:不能单个关闭;换组整组替换
刷新 以 URL 为准,停在当前叶子或隐藏页
加载 默认只挂载当前路由页;点过的同组叶子 keep-alive;换组清空缓存
一级下全是叶子 一级当作最后一层非叶子;Tab 只含有权限且未隐藏的叶子
隐藏页(isHide) 不进 Tab 条;内容区切到该页;父叶子 Tab 保持选中
未保存拦截 不做;与现有点侧栏换页一致
实现路径 方案 1:布局层组 Tab 条 + 现有单 router-view + keep-alive。不打开、不改造 WorkTab 打开记录
单叶子组 不显示组 Tab 条,直接展示该叶子页
中间目录 只展开/收起,不改右侧组

信息架构

侧栏(非叶子)                         内容区
┌─────────────────┐                 ┌──────────────────────────────────┐
│ 报销管理        │ 展开            │ Tab: 费用项目 | 工作流(备用金) | … │
│   基础配置    ● │ ──点击──►       │ (仅当前/已访问叶子真正挂载)      │
│   备用金        │                 │ 页面:费用项目管理                 │
│   报销          │                 └──────────────────────────────────┘
│ CRM管理         │ 展开            │                                     │
│   产品管理    ● │ ──点击──►       (无 Tab,直接产品管理页)             │
│   报价单管理  ● │ ──点击──►       Tab: 报价单管理 | 报价单审批           │
│   合同管理    ● │ ──点击──►       Tab: 合同管理 | 合同审批 | 调价函管理   │
│   客户管理    ● │ ──点击──►       (无 Tab,直接客户管理页)             │
└─────────────────┘

可见菜单

参与侧栏和组 Tab 计算的节点必须同时满足:

  • 当前用户有权限;
  • meta.isHide 不为 true;
  • 非登录/403/404/门户首页等无业务组页面。

非叶子 / 最后一层非叶子

在过滤后的树上:

  • 非叶子:至少有一个可见子节点。
  • 最后一层非叶子:可见子节点里没有目录(子节点都是叶子),或为「混合节点」(见下)。
  • 中间非叶子:可见子节点里至少还有一个非叶子。点击只展开树,不 push 叶子、不换组。

混合节点

可见子节点里既有目录又有叶子时:该节点标题点击打开其直接叶子组成的 Tab 组;展开箭头只负责展开/收起子目录。子目录仍留在侧栏,可再点进各自的组。否则这些叶子无法从侧栏进入。

当前组

由当前路由向上找最近的「会打开 Tab 的节点」(最后一层非叶子或混合节点)。该节点的直接可见叶子= Tab 全集,顺序与菜单 order 一致。

隐藏页不进入全集。隐藏页通过 meta.activePath(没有则用 path 最长前缀匹配组内叶子)归属到某一个叶子,该叶子高亮。

首页、登录、错误页、无匹配组:不渲染组 Tab。

交互

  1. 点击最后一层非叶子 / 混合节点:若当前路由已属于该组(含其隐藏页),不打断 URL;否则 router.push 该组第一个可见叶子。
  2. 点击组内 Tab:router.push 该叶子 path。已是当前叶子则不重复导航。
  3. 换组:Tab 条换成新组;上一组 keep-alive 清空。
  4. 进入隐藏页:Tab 条不变;父叶子高亮;内容为隐藏页。返回/提交成功仍由页面自己 push 回列表。
  5. 从隐藏页点另一个叶子 Tab:直接切走,无确认框。
  6. 浏览器刷新 / 书签:按 URL 恢复页面;侧栏展开到对应组并选中;画出整组 Tab;只挂载 URL 这一页(不必先经过组内第一个叶子)。
  7. 直达无权限 URL:现有 403/跳转;不生成假 Tab。

加载与缓存

当前 ArtPageContent 仅在 meta.keepAlive === true 时缓存,且 showWorkTab === false 时 keepAliveMax 为 1。组 Tab 需要覆盖这一策略:

场景 行为
首次进入组 只创建当前路由组件
同组点过的可见叶子 纳入 keep-alive include(不要求页面 meta.keepAlive)
换组 清空本组 include,卸载上一组叶子
隐藏页 默认不纳入组 include;若该页自身 keepAlive: true(如用户详情)则仍按其 meta
上限 keepAliveMax ≥ 当前组可见叶子数(本组同时缓存的叶子不超过组大小)

组 Tab 的缓存与 WorkTab 的 opened 列表无关。showWorkTab 保持默认 false。

视觉

  • 组 Tab 在内容区顶部:顶栏/面包屑之下、业务页之上。
  • Fresh Mint:对齐案例仓储「库存信息表」.subnav——白底描边圆角轨道(10px)+ 内嵌 8px 圆角项;选中为 --primary-bg 填充与 --primary-dark 字色;无关闭图标。不得使用案例 #167c4a。
  • 侧栏宽度、一级竖 Tab、树区规则仍遵循 docs/web-admin-ui-spec-fresh-mint.md §3;本能力只改树节点过滤与内容区 Tab。
  • 规范文档与 OpenSpec ui-conventions 实现时同步补「侧栏非叶子 + 组 Tab」,避免与「默认不展示 WorkTab」条文冲突:组 Tab 不是 WorkTab。

架构(方案 1)

菜单树 + 当前 route
        │
        ▼
resolveLeafGroup(route, menu)  →  { groupId, groupNode, leaves[], activeLeafId }
        │
        ├─► Sidebar:只渲染非叶子;高亮 groupNode
        └─► GroupLeafTabs:渲染 leaves[];点击 push
                │
                ▼
        ArtPageContent
          KeepAlive include = 当前组已访问叶子组件名
          RouterView 仍只挂当前匹配页

建议落点(实现时可微调路径,职责不变)

单元 职责
utils/router/leaf-group.ts(名称可调整) 纯函数:判非叶子、解析当前组、隐藏页归属、第一片叶子 path
侧栏(art-sidebar-menu / 双列子树 / PORTAL 子导航) 过滤叶子;最后一层/混合节点点击走「进组」而不是进单个叶子
GroupLeafTabs 组件 固定 Tab 条,无关闭
ArtPageContent 挂 Tab 条;按组维护 keep-alive include / max
隐藏路由 meta.activePath 补齐常用表单/详情,避免高亮丢失
UI 规范 + OpenSpec 记录交互,明确组 Tab ≠ WorkTab

不改业务列表内部 CRUD;不改后端 sys_menu 结构(除非实现时发现个别 activePath 必须写在路由 meta,前端即可)。

错误与边界

  • 组内可见叶子数为 0:不画 Tab,走现有空菜单/无权限。
  • 门户首页、全屏页(isFullPage):无组 Tab。
  • 混合节点:Tab 仅含该节点直接叶子,不含孙节点。
  • 角标:若叶子在侧栏被拿掉,原叶子角标改挂到其所属最后一层非叶子(或 Tab 项上)。实现时与现有 menu-badge 对齐,避免角标消失。

验收

  1. 报销 → 基础配置:三个 Tab,只加载第一个;点第二个才出页面;再点备用金,上一组卸载,换成申请/审批/发放。
  2. 刷新停在当前叶子或隐藏表单 URL;Tab 条仍是该组;父叶子高亮。
  3. CRM:点一级只出现有权限且未隐藏的叶子;进报价表单时「报价单管理」仍高亮;返回列表不丢组。
  4. 无权限叶子不出现;直达无权限 URL 仍 403。
  5. 同组来回切换已访问叶子不重复打首屏列表请求(网络面板可核对);换组后旧页请求不再因缓存而显示。

非目标

  • 不恢复可关闭、可积压的 WorkTab 历史页签。
  • 不做全局表单脏检查。
  • 不把隐藏页(表单/详情)做成弹窗。
  • 不强制给 CRM/系统管理补二级目录(一级可当最后一层)。
  • 不改小程序导航。