ObjectOS
构建界面

应用与导航

把对象、视图、页面和仪表盘打包成一个带品牌、可导航的外壳 —— 并精确控制谁能看到什么。

应用与导航

应用是一个逻辑容器,把对象、视图、页面和仪表盘打包成一体化的体验。它定义导航树、品牌,以及 —— 最关键的 —— 谁能进来。

import { App } from '@objectstack/spec/ui'

export const CrmApp = App.create({
  name: 'crm_app',
  label: 'CRM',
  icon: 'briefcase',
  branding: { primaryColor: '#2563EB' },
  navigation: [
    { id: 'group_sales', type: 'group', label: 'Sales', icon: 'briefcase', children: [
      { id: 'nav_leads',    type: 'object', objectName: 'crm_lead',    label: 'Leads',    icon: 'funnel' },
      { id: 'nav_accounts', type: 'object', objectName: 'crm_account', label: 'Accounts', icon: 'building' },
    ]},
  ],
  requiredPermissions: ['crm_access'],
})

应用属性

属性类型必填说明
namestring机器名(snake_case
labelstring显示名
iconstring应用图标(Lucide)
description / versionstring用于列表展示的元数据
activeboolean应用是否激活(默认 true
isDefaultboolean是否为默认应用
navigationNavigationItem[]导航树
brandingAppBrandingprimaryColorlogofavicon
requiredPermissionsstring[]谁可以打开这个应用
homePageIdstring用作着陆页的导航项 id
mobileNavigationobject移动端专属导航

导航项

导航树支持八种项目类型。常用的五种是 objectdashboardpageurlgroup;规范还定义了 reportactioncomponent 三种。

type落到哪里关键配置
object对象的列表视图objectName,外加可选的定位配置(见下)
dashboard一个仪表盘dashboardName
page一个自定义页面pageName、可选 params
url一个外部 URLurltarget: '_blank'
group可折叠分组childrenexpanded

每个导航项必须声明唯一的 id(小写 snake_case)—— homePageIdmobileNavigation.bottomNavItems 引用的就是它。公共属性:labeliconorderbadge,再加下面的门控三件套。

定位 object 入口

三个可选字段可以细化 object 入口落到哪里,优先级为 recordIdfiltersviewName

  • viewName —— 把入口锚定到某个具名列表视图。
  • recordId —— 深链到单条记录("我的资料");支持 {current_user_id} / {current_org_id} 模板变量。
  • filters —— 一次性的参数化切片:入口落到裸数据界面上,每个条件是一个可移除的 URL 筛选标签。适合"分派给我"这类链接,不必专门写视图。这不是安全特性 —— 显示什么仍由行级权限决定。
{ id: 'nav_my_open', type: 'object', label: 'My Open Deals', objectName: 'opportunity',
  filters: { owner_id: '{current_user_id}', status: 'open' }, icon: 'user-check' }

移动端导航

mobileNavigation: {
  mode: 'bottom_nav',                    // 'drawer'(默认)| 'bottom_nav' | 'hamburger'
  bottomNavItems: ['nav_home', 'nav_accounts', 'nav_contacts'],  // 导航项 id,最多 5 个
}

按受众门控应用

同一份数据服务于两类截然不同的受众:设计 Schema 的构建者,和只录入、查看数据的最终用户。默认就把这两类界面分开 —— 别指望每位管理员手动把东西藏起来。

受众界面门控方式
最终用户(消费者)精心组织的应用 → page / view 入口App.requiredPermissions、导航项门控
构建者 / 管理员Setup / Studio、原始对象表格能力:setup.accessstudio.accessmanage_metadata

内置权限集已经编码了这种分割:member_defaultviewer_readonly 携带 studio.access / manage_metadata,因此构建者界面对他们不可见;admin_full_accessorganization_admin 则携带。

每个导航项支持三种相互独立的门控:

门控类型隐藏该项,除非……
requiredPermissionsstring[]用户持有该 RBAC 能力(如 manage_metadata
visibleCEL 表达式谓词求值为 true(如 'org_admin' in current_user.positions
requiresObject / requiresServicestring具名对象 / 内核服务已安装
navigation: [
  { id: 'nav_contacts', type: 'object', label: 'Contacts', objectName: 'showcase_contact' },
  // 仅构建者可见的入口 —— 消费者永远不会渲染它:
  { id: 'nav_designer', type: 'component', label: 'Object Designer',
    componentRef: 'metadata:resource', params: { type: 'object' },
    requiredPermissions: ['manage_metadata'] },
]

**隐藏,而不是禁用。**禁用但可见的构建者入口仍是噪音。被门控的导航项对缺少该能力的用户根本不渲染 —— 不留下让最终用户困惑的灰色摆设。

还有两个习惯能让消费者界面保持干净:

  • 给最终用户一个页面,而不是原始表格。page 入口让你精确策划暴露的内容 —— 精选的列、固定的可视化、只有你启用的筛选和操作。object 入口给出的则是宽松的表格:可切换视图、个人视图、完整工具栏。
  • **也要策划数据界面本身。**优先用精心组织的视图,而不是放开原始对象读取,把用户扔到一个 40 列的表格上。

操作门控是双面的:UI 隐藏或禁用按钮,同时服务端拒绝调用 —— 不存在"只在 UI 门控、服务端敞开"的坑。见操作

Setup 应用 —— 一个内置示例

平台自身的管理 UI —— Setup(管理后台)应用 —— 本身就是用同一套应用元数据协议渲染的:导航、页面与门控都声明为数据,由渲染你的应用的同一个渲染器绘制。它的门控方式正是你的构建者界面应有的方式:藏在消费者权限集不携带的 setup.access 能力后面。如果你想要"应用即元数据"可以规模化的证据 —— 你已经在用了。

反模式

  • 把每个最终用户都当成构建者级协作者,再一个个隐藏选项卡。应该让消费者 / 构建者分割成为默认。
  • **禁用而不是隐藏构建者入口。**可见但失效的摆设照样让人困惑。
  • 放开原始对象读取,而其实一个精心组织的页面就能恰好暴露最终用户需要的内容。

下一步

页面原因
视图object 导航入口落到的地方
页面面向最终用户的精心组织的 page 入口
仪表盘dashboard 入口渲染的内容
操作按钮及其双面门控
权限requiredPermissions 背后的权限集
界面总览各部分如何组合

On this page