浅论CabloyJS全栈框架提供的“两级页签”机制

CabloyJS 全栈框架的"两级页签"本质上不是单纯的 UI 花样,而是一种把"业务模块切换"与"模块内多工作项切换"分层管理的工作台机制。

一、先说结论:两级页签分别代表什么

1)一级页签:业务模块 / 业务入口

一级页签对应 RouteTab,核心标识是 tabKey,标题和图标来自菜单信息 getTabInfo(tabKey),也就是它更像一个业务分组功能入口

一级页签的业务意义是:

让用户在"用户管理、订单管理、内容管理、首页"等业务域之间切换。

它代表的是"我现在在哪个业务模块里工作"。


2)二级页签:模块内的具体工作对象 / 工作实例

二级页签对应 RouteTab.items[] 中的每个 IRouteViewRouteItem,核心标识是 componentKey,标题来自运行时 pageMeta.pageTitle,不是菜单标题。

二级页签的业务意义是:

在同一个业务模块下,同时打开多个具体任务、表单、记录或页面实例。

例如在"用户管理"一级页签下,可以同时有:

  • 用户列表
  • 新建用户
  • 编辑用户 A
  • 编辑用户 B

这就是典型的模块内多文档 / 多任务工作模式。


二、代码里是怎么把"两级"区分出来的

核心在路由被转换成两个 key:

  • tabKey:决定归属哪个一级页签
  • componentKey:决定模块内是不是同一个页面实例

1)tabKey:业务归组

ini 复制代码
const tabKey = this._handleRouteProp(route, 'tabKey') || componentKey;

含义是:

  • 如果路由显式声明了 meta.tabKey,那么多个页面可以归到同一个一级页签下
  • 如果没声明,就退化成 tabKey = componentKey,也就是"一页一个一级页签"

这说明两级页签不是硬编码死的,而是一个按业务需要启用的分组机制


2)componentKey:页面实例区分

componentKey 优先取路由 meta 配置,否则按 route name/path 推导。

这里特别重要的是:

  • 默认可能按 route.path 区分
  • 如果 componentKeyMode === 'nameOnly',则按路由名归一

业务含义是:

  • 有些页面希望"同类页面共用一个实例"
  • 有些页面希望"不同参数/路径形成不同实例"

也就是说,二级页签不是简单"多开页面",而是支持按业务语义控制"是否多开"。


三、为什么要做两级,而不是只做一级多标签

这是这套机制最核心的业务价值。

1)避免一级页签爆炸

如果只有一级页签,那么每打开一个"编辑用户""查看订单""新建文章",顶层标签都会越来越多,最后变成:

  • 用户管理
  • 编辑用户A
  • 编辑用户B
  • 订单管理
  • 编辑订单X
  • 创建订单
  • 内容管理
  • 编辑文章1
  • 编辑文章2

这样顶层结构就失去"业务导航"的意义了。

两级设计后:

  • 一级页签保持稳定:用户管理 / 订单管理 / 内容管理
  • 二级页签承载临时工作项:编辑A / 编辑B / 创建 / 查看详情

这就把导航层工作层分开了。


2)让用户保留"模块上下文"

点一级页签时,系统不是简单跳到固定首页,而是优先回到该一级页签关联的首个路由项。

这意味着:

用户切回"用户管理"时,不只是重新打开菜单,而是回到这个业务域当前的工作上下文。

业务上这很重要,因为后台系统用户常常是在多个模块间来回切换处理事务,而不是一次只做一个任务。


3)支持同模块下的多对象并行处理

RouteTab.items 是一个数组,一个一级页签下可以挂多个页面实例。

这意味着同一个模块下可以并行做多件事,比如:

  • 在"商品管理"里同时编辑商品 A、B、C
  • 在"订单管理"里同时查看订单详情、发货页、售后页
  • 在"内容管理"里同时新建文章、修改文章、预览文章

这比传统单页返回列表再点下一条的方式,更接近桌面应用/IDE 的工作体验。


4)二级页签标题是"任务标题",不是"菜单标题"

一级页签标题来自菜单 title/icon,而二级页签标题来自页面运行时 pageMeta.pageTitle

业务意义很明确:

  • 一级页签告诉你"在哪个模块"
  • 二级页签告诉你"当前具体在处理什么对象"

这是两个不同层次的信息。


四、这套机制还有几个很有业务味道的设计

1)一级页签有"固定业务入口"概念

Admin 初始化时默认放入一个 affix 页签 /,在模型里 affix 页签不可随意被裁剪、也不会轻易消失。

业务上就是:

首页、主工作台、固定入口是"常驻工作台"的一部分。


2)二级页签里有一个"隐形首项"

渲染二级页签时会跳过 componentKey === tabKey 的那一项,同时模型里又明确"不删除 first tabItem"。

这说明每个一级页签下其实有一个基础入口页作为锚点,但这个锚点不一定展示成二级标签。

业务意义:

一级页签不是空容器,它总有一个基础着陆页;二级页签显示的是"额外打开的工作项"。

这让结构更稳定。


3)支持未保存、创建态、编辑态提示

二级页签图标会根据 pageMeta 变化:

  • pageDirty:星号
  • formScene === create:新建图标
  • formScene === edit:编辑图标

业务意义是:

二级页签不仅表示"打开了什么",还表示"这个工作项当前处于什么状态"。

非常适合后台表单密集型业务。


4)支持容量控制,防止工作台失控

模型支持:

  • 一级页签最大数量 max
  • 二级页签最大数量 maxItems
  • 超出后按最近最少使用倾向清理旧项

业务意义:

系统允许用户多开,但不会无限膨胀到难以管理。


五、可以把它理解成什么业务模型

我觉得最准确的理解是:

一级页签 = Workspace / Business Domain

代表一个业务工作区,比如:

  • 首页
  • 用户管理
  • 订单管理
  • 内容管理

二级页签 = Task / Document / Instance

代表这个工作区里的具体工作对象,比如:

  • 新建用户
  • 编辑用户张三
  • 编辑用户李四
  • 查看订单 2026001

这其实很像:

  • 浏览器:站点级标签 + 页面内上下文
  • IDE:项目/目录 + 打开的文件
  • ERP/后台:模块入口 + 当前处理单据/表单

所以它的业务意义不是"多一行 tabs",而是:

把后台系统从"菜单跳转式操作"升级成"工作台式并行处理"。


六、结合 Admin 这套实现,可得出的最终判断

在 Admin 布局里,这套两级页签机制的目标非常明确:

  1. 一级页签承载菜单级业务导航
  2. 二级页签承载当前业务模块下的多任务/多对象并行处理
  3. 通过 tabKeycomponentKey 解耦"业务归组"和"页面实例"
  4. 通过 pageMeta 给工作项增加状态语义
  5. 通过 cache / keepAlive / prune 让工作台既连续又可控

七、用一句话概括

一句话总结:

Cabloy 的两级页签机制,本质上是在后台工作台中,把"业务模块切换"与"模块内多文档/多任务处理"分层管理,从而同时兼顾导航稳定性、并行作业能力、页面状态表达和工作现场恢复。

GitHub:github.com/cabloy/cabl...

相关推荐
meilindehuzi_a1 小时前
深入理解 Ajax 异步请求:从 XMLHttpRequest 到 Node.js HTTP 服务实践
http·ajax·node.js
Asize1 小时前
Bun + TypeScript:AI 时代的后端开发入门
人工智能·typescript·bun
SwJieJie2 小时前
Webpack vs Vite 构建工程化实战(Vue 项目深度解析)
前端·vue.js·webpack·node.js
l1o3v1e4ding3 小时前
windows安装Claude Code,并接入Deepseek-v4模型 ,提供离线安装包
git·npm·node.js·claude code·cc-switchcc
退休倒计时15 小时前
【每日一题】LeetCode 53. 最大子数组和 TypeScript
数据结构·算法·leetcode·typescript
Rain50916 小时前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
小林ixn18 小时前
你以为你懂 + 号?看完这篇 Bun + TS 实战,才发现以前全写错了
前端·javascript·typescript
晓杰'18 小时前
从0到1实现Balatro游戏后端(8):Skip Blind与Tag奖励机制设计与实现
后端·websocket·typescript·项目实战·nestjs·状态管理·游戏服务器
YHHLAI19 小时前
从零搭建一个 RESTful Todo 服务 —— Bun + TypeScript 全栈最小闭环
后端·typescript·restful