跳到主要内容

UI 组件

UI 组件

@norbital-ai/ui 包含构建页面所需的视图、输入控件、基础组件与编辑器。每个视图都从一个裸属性 of 推断其集合,并由名称索引提供类型,因此列、卡片、筛选、排序与行操作都会像服务器代码一样针对集合进行检查。

API 参考:ui

组件一览

  • 视图——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 }。