UI 组件
UI 组件
@norbital-ai/ui 包含构建页面所需的视图、输入控件、基础组件与编辑器。每个视图都从一个裸属性 of 推断其集合,并由名称索引提供类型,因此列、卡片、筛选、排序与行操作都会像服务器代码一样针对集合进行检查。
组件一览
- 视图——
Table、Board、Form、Field、Rows、RecordShell、RunStatus、RunsFor、Matrix、Chart、Pivot、Map、Show、SchemaEditor与CameraCapture - 输入控件——
Picker、TimeInput、MoneyInput、FileInput、PointInput、NumberTuple与TimeRangeInput - 基础组件——
Button、Input、Textarea、Label、Badge、Alert、Dialog、Sheet、Popover、Tooltip、Tabs、Accordion、Checkbox、Spinner、Progress与Icon,以及编辑器MarkdownEditor与CodeEditor - 布局——
AppShell与布局基元,来自@norbital-ai/ui/layout;带活体检测的相机拍摄来自@norbital-ai/ui/capture
一个属性指定数据源
of="quotes" 读取一个集合;of={{ query: '<c>.<q>', input }} 读取返回行的集合查询;of={q} 接收一个 bolt 查询或实时值(Table、Chart 与 Pivot);of={rows} 接收本地数组(仅限 Table,提供 onChange 时可编辑)。
Table 与 Board
<!-- src/app/scheduler/schedule/+board.page.svelte (templates/on-demand-services, excerpt) -->
<Board
of="visits"
key="by-helper"
by="helper"
{lanes}
{where}
{card}
orderBy={{ number: 'asc' }}
toolbar={{ title: t('app.schedule.by_helper'), controls: dayPicker, new: false }}
/>
<Table
of="visits"
key="list"
{where}
orderBy={{ number: 'asc' }}
toolbar={{ title: t('app.schedule.list'), controls: dayPicker, new: false }}
columns={['number', 'slot', 'helper', 'address', 'status', 'shift_check', 'eta_minutes']}
actions={[
{ action: 'visits.recommend', label: t('app.schedule.auto_reassign') },
{
action: 'visits.cancel',
label: t('app.schedule.cancel_visit'),
confirm: t('app.schedule.cancel_visit_confirm')
}
]}
/> columns 是视图行的字段,或 { field, label?, cell? },其中 cell 片段接收该列带类型的值。where 是作者的固定范围:始终与查看者的每个筛选以 AND 组合,只读显示且无法清除。initialFilter 预置查看者可以编辑或清除的筛选行,orderBy 是初始排序。行 actions 是 { action: '<c>.<a>', input?, confirm? } 字面量。容器宽度小于 48rem 时,Table 以卡片形式渲染行。
Board 按 by 指定的字段把行分到各个泳道。card 最多显示四个字段;当泳道字段是关系时,必须提供 lanes。把卡片拖到另一个泳道是一次乐观更新。
工具栏
<!-- src/app/field_ops_controller/+sites.page.svelte (templates/field-operations, excerpt) -->
<Table
of="sites"
toolbar={{
title: t('app.field_ops_controller.tab_sites'),
new: true,
select: true,
actions: [
{
start: 'site_handover',
input: (ids) => ({ ids }),
label: t('app.field_ops_controller.site_handover'),
requiresSelection: true
}
]
}}
orderBy={{ name: 'asc' }}
columns={['name', { field: 'client_name', label: t('component.client_tenant') }, 'address']}
/> 视图的外围界面由一个 toolbar 属性描述,在 Table、Board、Map 与 Pivot 上形状相同:{ title?, description?, search?, filter?, actions?, export?, delete?, select?, new? }。每个键都由声明与查看者的授权决定默认值,因此一个不加配置的视图已经显示:
- 以集合名称作为标题,描述放在信息图标后面
- 模型声明了
search.text时显示搜索框 - 视图弹出层,用于筛选与排序(
filter: false可隐藏) - 操作菜单:查看者持有的集合操作、集成的“立即同步”、“导出 CSV”(集合声明了导出流水线时为“导出”),然后是作者的
actions项 - 查看者可以创建时显示“新建”;
new: false可隐藏它,传入函数则用于替代记录面板进行预填创建
作者项为 { action: '<c>.<a>', input?, confirm?, label } 或 { start: '<automation>', input: (selected) => …, label, requiresSelection? };工具栏会显示已发起运行的进度与文件。delete: true 添加“删除所选”,select: true 打开行复选框,toolbar={false} 则完全不渲染外围界面。
表单与记录
Form of="<c>" mode="create" | "update" 编辑集合白名单中的字段,并只发送改动过的字段;Form of={{ action: '<c>.<a>' }} 渲染一个操作的输入并调用它。RecordShell 是记录外框——名称、标签页、历史记录,以及挂起行的审批视图——当集合没有表示时,它就是完整的记录视图。
表示
<!-- src/data/collection/sites/+representation.svelte (templates/field-operations, excerpt) -->
<script lang="ts">
import { bolt } from '$bolt';
import type { RecordView } from '@norbital-ai/ui';
import { RecordShell, Table } from '@norbital-ai/ui';
let { view }: { view: RecordView<'sites'> } = $props();
const t = bolt.t;
const record = $derived(view.mode === 'update' ? view.record : null);
</script>
{#snippet history()}
{#if record}
<Table
of="job_assignments"
key="site-history"
where={{ site_id: { eq: record.id }, status: { in: ['assigned', 'completed'] } }}
orderBy={{ dispatched_at: 'desc' }}
columns={['title', 'status', 'summary']}
/>
{/if}
{/snippet}
{#if record == null}
<RecordShell of="sites" mode="create" values={view.mode === 'create' ? view.values : {}} />
{:else}
<RecordShell
of="sites"
id={record.id}
subtitle={record.client_name ?? '—'}
tabs={[{ name: 'activity', title: t('component.activity_history'), body: history }]}
/>
{/if} src/data/collection/<c>/+representation.svelte 取代生成的记录视图。它接收 { view: RecordView<'<c>'> }:view.mode 为 create 或 update,分别带有 view.values 或 view.record。它渲染在 RecordShell 内,可以设置其 subtitle、badge、actions 与 tabs。自定义字段的 +renderer.svelte 接收 { view: CustomFieldView }。