應用與導航
把物件、檢視、頁面和儀表盤打包成一個帶品牌、可導航的外殼 —— 並精確控制誰能看到什麼。
應用是一個邏輯容器,把物件、檢視、頁面和儀表盤打包成一體化的體驗。它定義導航樹、品牌,以及 —— 最關鍵的 —— 誰能進來。
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'],
})應用屬性
| 屬性 | 型別 | 必填 | 說明 |
|---|---|---|---|
name | string | 是 | 機器名(snake_case) |
label | string | 是 | 顯示名 |
icon | string | — | 應用圖示(Lucide) |
description / version | string | — | 用於列表展示的後設資料 |
active | boolean | — | 應用是否啟用(預設 true) |
isDefault | boolean | — | 是否為預設應用 |
navigation | NavigationItem[] | — | 導航樹 |
branding | AppBranding | — | primaryColor、logo、favicon |
requiredPermissions | string[] | — | 誰可以開啟這個應用 |
homePageId | string | — | 用作著陸頁的導航項 id |
mobileNavigation | object | — | 移動端專屬導航 |
導航項
導航樹支援八種專案型別。常用的五種是 object、dashboard、page、url 和 group;規範還定義了 report、action 和 component 三種。
type | 落到哪裡 | 關鍵配置 |
|---|---|---|
object | 物件的列表檢視 | objectName,外加可選的定位配置(見下) |
dashboard | 一個儀表盤 | dashboardName |
page | 一個自定義頁面 | pageName、可選 params |
url | 一個外部 URL | url、target: '_blank' |
group | 可摺疊分組 | children、expanded |
每個導航項必須宣告唯一的 id(小寫 snake_case)—— homePageId 和 mobileNavigation.bottomNavItems 引用的就是它。公共屬性:label、icon、order、badge,再加下面的門控三件套。
定位 object 入口
三個可選欄位可以細化 object 入口落到哪裡,優先順序為 recordId → filters → viewName:
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.access、studio.access、manage_metadata |
內建許可權集已經編碼了這種分割:member_default 和 viewer_readonly 不攜帶 studio.access / manage_metadata,因此構建者介面對他們不可見;admin_full_access 和 organization_admin 則攜帶。
每個導航項支援三種相互獨立的門控:
| 門控 | 型別 | 隱藏該項,除非…… |
|---|---|---|
requiredPermissions | string[] | 使用者持有該 RBAC 能力(如 manage_metadata) |
visible | CEL 表示式 | 謂詞求值為 true(如 'org_admin' in current_user.positions) |
requiresObject / requiresService | string | 具名物件 / 核心服務已安裝 |
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 能力後面。如果你想要"應用即後設資料"可以規模化的證據 —— 你已經在用了。
反模式
- 把每個終端使用者都當成構建者級協作者,再一個個隱藏選項卡。應該讓消費者 / 構建者分割成為預設。
- **停用而不是隱藏構建者入口。**可見但失效的擺設照樣讓人困惑。
- 放開原始物件讀取,而其實一個精心組織的頁面就能恰好暴露終端使用者需要的內容。