日期:2026-10-04
状态:待审阅(实现前须产品确认本文)
交互 Demo:docs/demos/sidebar-group-leaf-tabs-demo.html
范围:web-admin 全站业务菜单导航。默认壳层为单列树形侧栏(MenuTypeEnum.LEFT),只展示非叶子;无独立一级竖向 Tab。不含小程序、不含后端菜单 API 变更。
当前侧栏同时展示目录和叶子,内容区一次只挂一个页面(默认 showWorkTab: false)。用户希望:
报销管理是典型三层(报销管理 → 基础配置 → 费用项目等)。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;在过滤后的树上:
push 叶子、不换组。可见子节点里既有目录又有叶子时:该节点标题点击打开其直接叶子组成的 Tab 组;展开箭头只负责展开/收起子目录。子目录仍留在侧栏,可再点进各自的组。否则这些叶子无法从侧栏进入。
由当前路由向上找最近的「会打开 Tab 的节点」(最后一层非叶子或混合节点)。该节点的直接可见叶子= Tab 全集,顺序与菜单 order 一致。
隐藏页不进入全集。隐藏页通过 meta.activePath(没有则用 path 最长前缀匹配组内叶子)归属到某一个叶子,该叶子高亮。
首页、登录、错误页、无匹配组:不渲染组 Tab。
router.push 该组第一个可见叶子。router.push 该叶子 path。已是当前叶子则不重复导航。keep-alive 清空。push 回列表。当前 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。
.subnav——白底描边圆角轨道(10px)+ 内嵌 8px 圆角项;选中为 --primary-bg 填充与 --primary-dark 字色;无关闭图标。不得使用案例 #167c4a。docs/web-admin-ui-spec-fresh-mint.md §3;本能力只改树节点过滤与内容区 Tab。ui-conventions 实现时同步补「侧栏非叶子 + 组 Tab」,避免与「默认不展示 WorkTab」条文冲突:组 Tab 不是 WorkTab。菜单树 + 当前 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,前端即可)。
isFullPage):无组 Tab。menu-badge 对齐,避免角标消失。