关于 objectstack-ui
objectstack-ui 是一项使用 ObjectStack 规范设计界面的技能,涵盖视图、导航、仪表板、报表、页面和操作。
主要功能
- 01列表与表单视图设计指南
- 02主从表单模式
- 03应用导航结构
- 04仪表板与报表配置
- 05页面与操作设计
适用场景
- 01设计元数据驱动的 CRM 界面
- 02配置包含子记录的父表单
- 03组织应用导航与自定义页面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ objectstack-ai/ objectstack --skill objectstack-ui安装后在 Claude Code 中输入/objectstack-ui确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/README.mdanalytics-inline-vs-dataset.jsonviews-apps-actions-pages.jsonreferences/_index.mdreact-blocks.mdrules/actions.mdlist-views.mddashboards.mdnavigation.mdpages.md详细介绍
UI Design — ObjectStack UI Protocol
使用 ObjectStack 规范设计用户界面的专家指南。本技能涵盖视图(列表、表单、看板、日历等)、 应用导航、仪表板、报表和操作。
视图类型
列表视图
| 类型 | 适用场景 |
|---|---|
grid | 标准数据表——适用于大多数对象的默认视图 |
kanban | 带列的可视化看板(由状态驱动的工作流) |
gallery | 基于卡片的瀑布流布局(可视化目录、联系人) |
calendar | 基于日期的日程安排(事件、任务、预订) |
timeline | 按时间顺序排列的活动流 |
gantt | 带依赖关系跟踪的项目管理 |
map | 含有 location 字段的地理空间记录 |
chart | 对象数据的聚合可视化(迷你图表视图) |
tree | 自引用层级结构(树形网格) |
表单视图
| 类型 | 适用场景 |
|---|---|
simple | 单页表单——适用于字段不超过 15 个的对象 |
tabbed | 分标签页的区块——适用于包含多个字段组的复杂对象 |
wizard | 分步流程——引导式数据录入(入职、申请) |
主从表单(父记录 + 子明细行)
要让用户连同子明细行一起录入记录(发票 + 明细、项目 + 任务),并原子性地保存,几乎从不需要自定义页面或表单配置。请按以下优先顺序选择:
-
关系
inlineEdit(默认,无需 UI 配置)。 在 DATA MODEL 中声明:在引用父记录的子对象master_detail字段上设置inlineEdit。之后,父记录的每个标准新建/编辑表单(模态框、抽屉、整页表单)都会自动渲染子记录,并通过一次原子性/api/v1/batch保存父记录 + 子记录。无需视图元数据。 各值true/'grid'/'form'的选择规则:objectstack-data → Relationships → Inline Editing。 -
表单视图
subforms(覆盖 / 调整)。 仅当需要覆盖派生出的列或顺序,或显示关系未标记为内联编辑的子对象时,才添加到表单视图:form: { type: 'simple', sections: [{ group: 'invoice_header' }], // a declared fieldGroup subforms: [ { childObject: 'invoice_line', // relationshipField + columns are title: 'Line Items', // derived from the child object; addLabel: 'Add line' }, // set `columns` here only to override. ], }, -
object-master-detail-form页面块(定制布局)。 只有自由布局时才使用页面。可使用相同的details: [{ childObject }]简写。
所有情况下,关系外键和网格列都根据子对象的元数据派生;选择项和查找字段也会一并沿用。父对象的 summary 字段会在服务器端汇总子对象的值(参见 objectstack-data)。
明细行网格行为(grid 模式)。 可编辑网格是真正的电子表格样式明细编辑器(QuickBooks / Stripe / NetSuite 模式),所有行为都由 DATA MODEL 派生——计算列、目录类型提示自动填充、持久化拖动排序,以及实时的小计 → 税额 → 总计:objectstack-data → Relationships → Inline Editing。这一侧只需编写三项配置:始终存在一个末尾的幽灵行,因此切勿添加“添加明细行”按钮(在幽灵行中输入内容会将其实体化为一行;未触碰的幽灵行永远不会持久化);autofill: false 可让某一列不参与类型提示复制;表单的 taxRateField 可覆盖用于计算总额的父对象字段。
读取侧——详情页关联列表。 inlineEdit 在父记录详情页上的对应功能是关联列表。无需手动编写:默认情况下,每个子对象关系都会显示为关联列表(所属的 master_detail 子对象优先)。可通过关系进行调整:
relatedList: 'primary'— 将关系标记为核心关系;详情页会将其提升为独立标签页(见下方布局)。这表示重要性意图,而非布局切换(ADR-0085)。relatedList: false— 从详情页中隐藏不重要的子对象。relatedListTitle/relatedListColumns— 覆盖派生出的标题 / 列(两者均为可选;否则列会根据子对象的highlightFields自动派生)。参见 objectstack-data → Relationships → Detail-page related lists。
关联列表布局。 在合成的记录详情页上,每个 relatedList: 'primary' 子对象都会获得独立标签页;所有其他关联列表则汇总到一个共享的关联标签页中。因此,只需在关系上更改一个词,就能将子表提升为一级标签页——无需自定义页面。对象仍不声明各表面专用的布局提示:旧的 detail.relatedLayout 开关和对象级 detail: {...} 块均已移除(ADR-0085);relatedLayout: 'tabs' | 'stack' 仅保留为应用级默认覆盖项,不能作为对象键使用。对于关系层无法表达的布局方式——筛选拆分(例如“打开”和“已关闭”标签页)、图表/报告标签页、精确的标签页顺序——请为对象分配一个自定义记录页面,并使用 record:related_list(或可内联编辑的 line_items)块显式排版。
表单中的字段条件规则
属于字段生命周期的条件规则 — visibleWhen、readonlyWhen、
requiredWhen — 应声明在数据模型字段上,而不是表单视图中;
ObjectUI 表单会使用这些规则。其语义和服务端行为详见:
objectstack-data → Conditional Field Rules。
UI 侧:内联主从网格会针对每个子行逐行评估这些规则。使用
requiredWhen — conditionalRequired 别名已在协议 17 中移除,现在会导致解析错误。编写复杂的 CEL 时,请加载 objectstack-formula。
详细规则
- 列表、看板与甘特视图 —
defineView、data、列、筛选、userFilters、工具栏搜索、排序、看板、甘特图。 - 导航与运行模式 —
App.create、三种运行模式、记录呈现。 - 仪表板、报表与多维数据集 — 小组件、数据集绑定、筛选、
compareTo、分桶、options、下钻、报表配置、多维数据集。 - 页面与文档 — 页面类型、区域、组件、打印页面、html/react 源代码层级、样式、软件包文档。
- 操作 —
locations、可见性、示例、ctx、新标签页、参数。
应用导航
应用将对象、仪表板、报表和自定义页面整合为结构化的导航树。使用
@objectstack/spec/ui 中的 App.create({...}) 构建,并在
defineStack({ apps: [...] }) 中注册。
⛔ 每个 type: 'app' 软件包中只能有一个 App — 根据 ADR-0019(app-as-consumer-unit)D3,defineStack 在加载时会拒绝第二个 App。更多受众应通过这一个应用内部受 requiredPermissions 控制的分组来实现,绝不能创建第二个应用。
导航项类型
| 类型 | 属性 | 用途 |
|---|---|---|
group | label、icon、expanded、children[] | 可折叠的导航项分组 |
object | objectName、viewName?、recordId?、filters?、label、icon | 链接到对象列表、命名视图、记录深层链接,或基础数据界面上的 filters 切片 |
dashboard | dashboardName、label、icon | 链接到仪表板 |
report | reportName、label、icon | 链接到报表 |
page | pageName、label、icon | 链接到自定义页面 |
url | url、label、icon | 外部或自定义 URL |
action | actionDef({ actionName, params? })、label、icon | 执行操作,而非导航 |
component | componentRef、params?、label、icon | 内置平台组件;componentRef 是以冒号连接的注册表键(metadata:resource) |
doc | book? / doc?(必填其一)、label、icon | 软件包文档;继承文档受众 |
separator | — | 视觉分隔符 |
requiresObject/requiresService: 对任何依赖可选系统对象或内核服务的导航项使用这些属性,这样对应项在依赖缺失时会自动隐藏 — 切勿硬编码条件 UI。
报表类型
| 类型 | 适用场景 |
|---|---|
tabular | 包含列和筛选条件的扁平数据表 |
summary | 带小计的分组数据(例如按地区统计收入) |
matrix | 交叉表 / 数据透视表(rows 向下 × columns 横向展开) |
joined | 多区块分析界面(组合多个子报表) |
没有 chart 报表类型 — 报表通过嵌入式
chart: 配置进行可视化。
CRM UI Blueprint(元数据优先)
将此 CRM 风格的结构用作规范的 UI 组装参考:
| UI 界面 | 典型位置 | 遵循的模式 |
|---|---|---|
| 多视图对象 UI | src/views/*.view.ts | 定义默认的 list + form,然后为不同场景定义命名的 listViews / formViews |
| 公开表单 | src/views/*.view.ts(包含 sharing: { enabled: true, allowAnonymous: true, publicLink: 'slug' } 的 formView) | Web-to-Lead,位于 GET/POST /api/v1/forms/:slug |
| 应用导航 | src/apps/*.app.ts | 使用分组导航树、viewName 快捷方式和 requiresObject 实现感知能力的可见性 |
| 分析数据集 | src/datasets/*.dataset.ts | 每个需要用于报告的对象定义一个数据集——仪表板和报告会按名称绑定已声明的数据集,因此没有数据集的对象无法用于分析 |
| 仪表板 | src/dashboards/*.dashboard.ts | 组合 KPI、图表和表格组件,并共享 dateRange 与 globalFilters |
| 报告 | src/reports/*.report.ts | 从该数据集中选择 rows(维度)+ values(度量);支持表格、摘要、矩阵和联接 |
| 记录页面 | src/pages/*.page.ts | 组合 regions + 组件(page:header、record:highlights、相关列表、标签页) |
| 用户操作 | src/actions/*.actions.ts | 使用 flow 进行编排,使用 script 执行参数化批量变更 |
在“构建完整的元数据应用 UI”任务中,默认采用此蓝图。
日期宏 — 筛选占位符
列表视图、仪表板、报告和页面中的筛选值支持相对日期标记({today}、{current_month_start}、{30_days_ago})以及两个会话标记 {current_user_id} / {current_org_id}。**词汇表、两种可接受的拼写、两个解析器和易混淆项列表由 objectstack-query → rules/filters.md 维护——编写筛选标记前请先加载该文件。**无法识别的标记会导致构建错误和运行时抛出异常,绝不会被静默视为字面值。
本包负责以下两条规则,因为它们涉及的是 UI 界面,而非标记词汇表:
- 筛选标记属于展示范围,而非安全机制。它决定界面显示什么;RLS 决定调用方可以读取什么。移除
{current_user_id}筛选条件会扩大视图范围——绝不能让它成为阻止用户访问他人数据的唯一屏障。 AppContextSelectorID(例如{active_package})仅在导航的recordId/params中解析。筛选条件不会使用侧边栏选择器的状态进行求值。
操作
操作是附加到对象或视图、由用户触发的操作。
在 defineStack({ actions: [...] }) 下注册。
操作类型
type | 用途 | 必填字段 |
|---|---|---|
script | 在服务器上运行内联 L2 钩子代码体(沙盒化 JS) | body(或将 target 设为已注册函数名称) |
url | 导航至内部路由或外部 URL | target |
modal | 打开由 target 命名的页面;仅 script 会运行 body | target |
flow | 按名称启动屏幕流或自动启动流 | target |
api | 调用已注册的 API 端点 | target |
form | 按名称打开 FormView(路由至 /_console/forms/:name) | target |
常见陷阱
-
在网格视图中放入过多列。 用户很少需要默认显示超过 6–8 列。次要列请使用
hidden。 -
忘记在主列上设置
link: true。 第一个有实际意义的列(通常是名称/主题)应作为指向记录详情的导航链接。 -
将组件的网格布局放在
position中。 网格布局字段是layout: { x, y, w, h }——position不是组件键,封闭式架构会按名称拒绝它(在 protocol 17 之前,它会被静默丢弃)。layout是可选的:省略时,组件会自动流式排列(Studio 设计器依赖此行为);只有在需要明确指定网格位置时才设置它。
验证你的工作
编写任何 *.view.ts / *.action.ts / *.dashboard.ts 后,在报告完成前运行创作时检查:
os validate # CEL predicates (record.<field>) + widget bindings + schema
# or: os build # the same gates, plus emits dist/
它会捕获两个特定于 UI 的陷阱,否则这两种情况在运行时都不会发出提示:
- 操作/字段谓词 — 在操作的
visible/disabled或字段的visibleWhen中使用裸字段引用(done而不是record.done),结果会变为null,并在每条记录上隐藏控件(即“按钮从不显示”陷阱)。 - 仪表板组件绑定 — 组件的
dataset/dimensions/values无法解析到已声明的数据集/字段时,会渲染出空图表(ADR-0021)。
在 os validate 通过之前,不要报告视图/操作/仪表板已完成。在脚手架项目中,检查命令是 npm run validate。
参考资料
完整的 Zod 架构列表(附单行说明)请参阅 references/_index.md——其中包含指向 node_modules/@objectstack/spec/src/ 的引用。请始终Read源文件以确认准确的字段结构;不要依赖对属性名称的记忆。