Skip to Content

关联字段

本文用于说明以关联为中心的字段 API:

  • RelationTable
  • SelectTree
  • ManyToOne / OneToOne
  • OneToMany
  • ManyToMany
  • 关联查询 / 分页 / patch 行为

相关文档:

  • Fields:核心 Field props、条件、filtersField.onChange、值契约
  • Widget 矩阵:widget 兼容性与 widget 专属示例
  • ModelForm:页面壳层与关联表单示例
  • ModelTable:只读单元格与内联编辑行为

导入

import { Field, RelationTable } from "@/components/fields";

RelationTable 仅在关联字段声明中使用,例如 Field.tableView

RelationTable

RelationTableOneToManyManyToMany 的关联表格视图定义。 请声明一个零 propstableView 组件,并在其中返回 <RelationTable />

示例:

function OptionItemsTableView() { return ( <RelationTable orders={["sequence", "ASC"]} pageSize={10}> <Field fieldName="sequence" /> <Field fieldName="itemCode" /> <Field fieldName="itemName" /> <Field fieldName="active" /> </RelationTable> ); }

Props

Prop类型必填说明
childrenReactNode按顺序声明的 <Field /> 列,以及可选的 <Action /> 行级操作(见 行内操作)。
ordersOrderCondition关联表格默认排序。支持单个元组或多个元组。
pageSizenumber关联表格页大小。仅对分页关联表格(isPaged={true})生效。

排序示例:

<RelationTable orders={["sequence", "ASC"]}> <Field fieldName="sequence" /> <Field fieldName="itemCode" /> </RelationTable>
<RelationTable orders={[ ["sequence", "ASC"], ["itemCode", "DESC"], ]} > <Field fieldName="sequence" /> <Field fieldName="itemCode" /> </RelationTable>

行为说明:

  • RelationTable.pageSize 仅影响分页关联表格(isPaged
  • RelationTable.orders 同时支持单个元组与多个元组
  • 列声明仍由子级 <Field /> 的顺序决定

行内操作 {#row-actions}

RelationTable 可与 <Field /> 并列接受 <Action /> 子节点。它们渲染在末尾的 Actions 列中,作为每行操作,并面向关联模型派发——动作的 operation / href / onClick 会以该行的 id 作为记录 id。

import { Action } from "@/components/actions/Action"; function OptionItemsTableView() { return ( <RelationTable orders={["sequence", "ASC"]}> <Field fieldName="itemCode" /> <Field fieldName="itemName" /> <Action type="link" labelName="打开" placement="inline" href="/config/option-item/{id}" /> <Action labelName="归档" operation="archive" placement="more" /> </RelationTable> ); }

放置规则与 ModelTable 行级操作一致:

  • placement="inline"(行上默认)→ 显示在 Actions 列中的图标/按钮
  • placement="more" → 显示在 Actions 列的溢出下拉中
  • placement="toolbar" / "header" 会被忽略(关联表格没有工具栏)

行为说明:

  • 仅当行存在 id 时才渲染操作;草稿/未保存行对应单元格为空
  • 动作派发使用关联模型名(而非父表单的模型),因此 operation 针对关联实体执行,查询失效会刷新该模型
  • disabled / hidden 条件基于已保存的行数据求值——不会跟踪未保存的内联编辑值(与 ModelTable 不同)
  • 此上下文中 ActionExecutionContext.scope 报告为 "model-table"(关联行复用同一套派发器)

ManyToOne / OneToOne

默认行为为可搜索的关联选择:

<Field fieldName="departmentId" />

依赖型关联过滤示例:

<Field fieldName="companyId" /> <Field fieldName="departmentId" filters={[ ["companyId", "=", "{{ companyId }}"], "AND", ["active", "=", true], ]} />

说明:

  • filters 作用于默认的可搜索关联查询
  • 当依赖的 {{ fieldName }} 在当前作用域没有值时,选择器保持查询禁用,而不会加载全部选项

filterBySource —— 由后端驱动的上下文过滤

当业务规则无法仅通过 filters / {{ fieldName }} 声明表达时(例如「男性员工不能选产假类型」「剩余年假须大于 0」),可使用 filterBySource,让后端根据调用方记录上下文自行过滤:

<Field fieldName="leaveTypeId" filterBySource />

filterBySource 为 true 时,searchName 请求会携带 SourceRecord

interface SourceRecord { model: string; // metaField.modelName —— 拥有该字段的模型 recordId?: string | null; // 解析后的 recordId;创建模式下为 null values?: Record<string, unknown>; // 该记录当前的内存表单值 }

语义:

  • modelrecordId 描述直接拥有该字段的记录——根表单上的 Field 为根记录;OneToMany / ManyToMany 行内的 Field 则为该行记录(使用行自身模型,而非父级)
  • values 为查询时刻的表单快照;任意表单值变更都会触发重新查询,使下拉随表单联动过滤
  • 默认为 false;按字段开启,因为「该查找是否应尊重宿主表单」由调用方决定,而非目标模型
  • 可与声明式 filters 同时使用;二者一并提交,由后端合并应用

filtersfilterBySource 之间选择:

  • filters + {{ fieldName }} —— 简单跨字段引用,在前端解析(genderstatusdepartmentId 等)。声明式、代码可读,无需改后端。
  • filterBySource —— 需要计算、策略查询或后端跨模型关联的规则。对前端不透明,但业务规则可与在 save 上强制执行的后端服务放在一起。

示例 —— 请假类型下拉通过服务端规则按员工性别过滤:

<Field fieldName="employeeId" /> <Field fieldName="leaveTypeId" filterBySource />

安全提示:filterBySource 会发送当前完整表单值映射。若表单含有目标后端不应看到的字段,请勿启用。

SelectTree

关联模型为层级结构时使用 SelectTree

<Field fieldName="departmentId" widgetType="SelectTree" />

带依赖过滤的常见写法:

<Field fieldName="companyId" /> <Field fieldName="departmentId" widgetType="SelectTree" filters={[ ["companyId", "=", "{{ companyId }}"], "AND", ["active", "=", true], ]} />

行为:

  • SelectTree 是表单与内联编辑器中面向开发者的推荐树形入口
  • 仍通过 Field 声明,而非直接渲染 SelectTreePanel
  • 与可搜索关联字段使用相同的 Field.filters 规则
  • 依赖的 {{ fieldName }} 缺失时,树选择器保持查询禁用,不会加载未过滤的整棵树
  • 底层 Tree / SelectTreePanel 属于内部基础设施

OneToMany

渲染为关联表格,支持行内编辑或对话框编辑。对外仍统一使用 Field

示例:

function OptionItemsTableView() { return ( <RelationTable orders={["sequence", "ASC"]} pageSize={10}> <Field fieldName="sequence" /> <Field fieldName="itemCode" /> <Field fieldName="itemName" /> <Field fieldName="active" /> </RelationTable> ); } <Field fieldName="optionItems" tableView={OptionItemsTableView} />;

常用 props:

  • tableView:零 props 的关联表格视图组件,内部渲染 <RelationTable><Field /></RelationTable>
  • formView:行创建/编辑用的对话框表单
  • isPaged:启用分页 / 远程关联模式

默认提交行为为增量 patch map:

{ "Create": [{ "name": "new row" }], "Update": [{ "id": "101", "name": "changed" }], "Delete": ["102", "103"] }

行为:

  • false(默认):在 getById 中包含关联 subQuery;关联表格 UI 不分页,渲染本地行
  • true:关联表格启用分页 UI;当 recordId + relatedModel + 作用域内关联过滤 就绪时,由 relatedModel.searchPage 加载数据(远程模式),否则在本地做分页
  • 静态 Field.filters(不含 {{ fieldName }} 引用)会并入 getById 的 subQuery,首屏加载即带上过滤;含 {{ fieldName }} 的动态过滤仅在远程模式查询、且引用字段值可用时生效
  • 可编辑单元格限制为已声明的 RelationTable 列与关联模型可编辑字段的交集
  • 未解析的 {{ expr }} 依赖会暂停远程关联查询,直到父表单依赖值存在

ManyToMany

默认渲染为关联表格 + 选择器对话框。

示例:

function UserTableView() { return ( <RelationTable orders={["username", "ASC"]} pageSize={10}> <Field fieldName="username" /> <Field fieldName="nickname" /> <Field fieldName="email" /> <Field fieldName="status" /> </RelationTable> ); } <Field fieldName="userIds" tableView={UserTableView} />;

默认提交行为为增量 patch map:

{ "Add": ["1", "2"], "Remove": ["3"] }

TagList

widgetType="TagList"ManyToMany 切换为可搜索的多选下拉,并在触发器下方以标签展示已选项。

<Field fieldName="userIds" widgetType="TagList" tableView={UserTableView} />

行为:

  • 可搜索下拉,支持多选交互
  • 已选值在触发器下方渲染为标签
  • 触发器文案保持紧凑,仅显示选中数量
  • 字段布局默认遵循周围 FormSection 列宽;需要独占整行时请显式传入 fullWidth
  • 顶层 ModelFormgetById 仅把字段名加入 fields,不会追加关联 subQuery
  • 字段 UI 值为 ModelReference[],顶层提交仍使用常规增量 patch map

查询说明

  • ManyToMany 选择器对话框以 AND 合并有效字段过滤、内部关联作用域过滤、搜索过滤与列过滤
  • 未解析的 {{ expr }} 依赖会暂停远程选择器与关联表格查询,直到源值存在
  • formView 可选;在 ManyToMany 中,点击行以只读模式打开 ModelDialog,新增/移除仍走选择器

共享的只读 / 内联行为

关联字段的共通行为:

  • ModelTable / RelationTable 只读单元格将 OneToManyManyToMany 都渲染为紧凑标签列表,优先 displayName,回退 id,而非 JSON 字符串
  • widgetProps 不会传入 RelationTable 只读单元格渲染器
  • 关联表格复用与 ModelTable 相同的紧凑文件/图片只读渲染
  • RelationTable.pageSize 仅影响分页关联表格(isPaged=true
  • 远程关联表格与选择器查询使用有效字段过滤(Field.filters ?? metaField.filters)、关联作用域过滤以及运行时搜索/列过滤

表单视图示例

formView 通常与 ModelDialog 搭配:

function UserRoleUserIdsFormView() { return ( <ModelDialog title="User Detail"> <FormSection labelName="General" hideHeader> <Field fieldName="username" /> <Field fieldName="nickname" /> <Field fieldName="email" /> <Field fieldName="mobile" /> <Field fieldName="status" /> </FormSection> </ModelDialog> ); }
最后更新于