跳到主要内容

UI 组件

UI 组件

Bolt 随附 集合表面 —— CollectionTableCollectionKanbanCollectionForm ——它们以零接线读写一个集合:由模式派生、按策略过滤,并且 默认为实时 。本页涵盖这些表面、 +representation.svelte 覆盖,以及自定义数据类型如何获得自己的渲染。

CollectionTable

表格是集合的默认视图。它要求一个显式的 columns 片段——表格 UI 不会从模型自动推导列——并从生成的表面上下文取得 client

<CollectionTable collection="tasks">
	{#snippet columns({ Column })}
		<Column name="title" />
		<Column name="status" />
		<Column name="assignee" />
	{/snippet}
</CollectionTable>

值得注意的属性:

  • query ——一个响应式查询:过滤、排序、搜索、分页在集合之上生效
  • view ——为该表面命名。同一集合在一个应用中的两个表格需要不同的 view
  • features —— { search, filter, create } 切换工具栏能力
  • exportPipelines / importPipelines ——从工具栏运行集合的 流水线 ,带选择感知的禁用原因
  • integrations ——集合的 集成
  • rowActions ——逐行动作片段
  • ListCard ——移动端卡片覆盖;省略时卡片从列角色派生
  • selectabledisabledtitledescriptionemptyPlaceholder

CollectionKanban

看板按一个字段对集合分组,并通过一次乐观写入在泳道之间移动记录:

<CollectionKanban collection="tasks" groupBy="status" />

值得注意的属性:

  • groupBy ——泳道据此构建的字段(必填)
  • lanes ——泳道子集、顺序、标签与颜色;省略时泳道从字段派生
  • rows ——可视泳道行,用于多行看板
  • onCardMove ——移动处理器;默认把目标泳道乐观写入 groupBy 字段,失败时回滚
  • Card ——卡片片段覆盖
  • queryviewselectable ,以及与表格相同的流水线属性

CollectionForm

表单通过与任何其他客户端相同的按策略过滤的变更路径创建或编辑一条记录。它拥有校验、可编辑字段选择与提交生命周期:

<CollectionForm collection="tasks" defaultValues={selectedRow} />

值得注意的属性:

  • defaultValues ——正在编辑的行,或新记录的局部种子:携带行键的值是更新,否则是草稿。特意没有 recordId 属性。
  • validation —— { schema, semantic } :用于字段校验的标准模式(含 refinement)加一个用于跨字段规则的异步语义检查
  • fields ——自动生成表单的字段有序挑选;省略时每个可写字段按声明顺序生成
  • children ——自动布局不够时,带表单控制器的完整字段组合片段
  • onSubmitonAfterSubmitdeleteActiondisabledloading

字段级的 renderer 属性让一个字段在不出表单的情况下使用自定义控件。提交经由 client.db 落地,因此受影响的实时查询在本地重新评估——这条路径上没有任何查询失效或重新获取。

覆盖:+representation.svelte

由模式派生的创建、展示与编辑是默认行为。当它们不够用时,一个集合拥有的文件同时覆盖全部三种模式:

src/collections/<collection>/+representation.svelte
<script lang="ts">
	import type { RepresentationProps } from './$types.js';

	let { record, close }: RepresentationProps = $props();
</script>

{#if record === null}
	{!-- create mode --}
<!-- display/edit mode: record is the row -->
	{record.title}
{/if}
  • 生成的 RepresentationProps 从相邻的 ./$types.js 到达: { record, close, refresh } ,其中 record: Row | null —— null 是创建,一行是展示/编辑。
  • 没有独立的创建角色,也没有调用点注册:表格、看板与详情视图从生成的静态映射解析同一个文件。
  • 把可编辑控件留在表单内。不要在只读摘要中重复同一个可编辑事实。共享展示属于普通的相邻 Svelte 组件,而不是 representation。
只在需要时覆盖
由模式派生的默认是表单与详情行为的唯一事实来源。只在真正需要覆盖时才用 +representation.svelte

自定义数据类型

当领域值比标量列更有形状时——带币种的货币、带规则的日期范围、还款计划——它就成为一个 自定义类型 :一个模式权威加一个渲染器,声明为 src/custom-types/ 下的一个目录。

src/datatypes/
└── site_coordinates/
    ├── +definition.ts      # schema — the only source of truth for the value
    └── +renderer.svelte    # required — how the value renders and edits

定义默认导出带模式的 defineCustomType ——或一个选项流向模型的结构工厂:

// src/datatypes/site_coordinates/+definition.ts
import { defineCustomType } from '@norbital-ai/bolt/authoring';
import { Schema } from 'effect';

export default defineCustomType({
	name: 'site_coordinates',
	description:
		'An x, y, and z point in the site model, with any axis that was never surveyed left empty.',
	schema: Schema.Struct({
		x: Schema.NullOr(Schema.Number),
		y: Schema.NullOr(Schema.Number),
		z: Schema.NullOr(Schema.Number)
	})
});

模型用 custom('<name>') 使用该类型;结构工厂推断它可选的选项参数:

// src/collections/projects/+model.ts
import { defineModel, custom, text } from '@norbital-ai/bolt/authoring';

export default defineModel(
	{
		name: text().notNull(),
		coordinates: custom('site_coordinates')
	},
	{ description: 'Construction project', recordLabel: 'name' }
);
  • 自定义值以 JSONB 存储并在边界处校验。
  • 平台已经拥有 moneyinstant_range —— 两者都通过 custom('<name>') 访问,不要重新声明;遮蔽平台名的数据类型是编译错误。 custom('money') 调用使用相同的校验和渲染路径,也可以收窄货币列表。
  • 定义是唯一被推断的值类型。绝不要强转它——模式才是校验数据的东西。

渲染器

+renderer.svelte 是该值的唯一 UI。它从自己生成的 RendererProps 接收一个可辨识联合的 ./$types.js 属性:

  • display —— { mode: 'display', field, value } ,用于表格、看板卡片与详情
  • edit —— { mode: 'edit', field, value, disabled, onValueChange } ,用于表单; onValueChange 上报编辑
<!-- src/datatypes/site_coordinates/+renderer.svelte -->
<script lang="ts">
	import type { RendererProps } from './$types.js';

	let props: RendererProps = $props();
</script>

{#if props.mode === 'display'}
	{props.value ? `(${props.value.x ?? '—'}, ${props.value.y ?? '—'}, ${props.value.z ?? '—'})` : '—'}
{:else}
	<input
		type="number"
		value={props.value?.x ?? ''}
		disabled={props.disabled}
		onchange={(e) => props.onValueChange({ ...props.value, x: Number(e.currentTarget.value) })}
	/>
{/if}

渲染器由编译器静态发现——没有注册步骤,缺少渲染器是编译错误。渲染器覆盖也可以按字段应用在 CollectionForm 组合内部,用于不值得完整自定义类型的一次性字段。

组合规则

  • 表面渲染在应用主体区域内——参见 布局 .
  • 记录导航——点击一行打开详情栈——由外壳处理;参见 导航状态
  • 所有读写都经由 实时数据层 .
  • 布局 ——表面渲染于其中的应用主体约定
  • 应用 ——在租户应用入口组件内组合表面
  • 集合 ——定义表面读写的模型
  • 实时数据 ——每个表面背后的实时查询与乐观写入