跳到主要内容

使用表格 Table

配置列的样式

配置 Avatar 列

简单列单独显示头像

如果 Table 的数据源表单有个上传字段,那么直接讲 Table 列的类型选择为 “头像” 即可。配置和效果如图:

表单上传头像字段, ID 随意,不一定要叫 avatar,按需设置即可。

avatar-form-upload

Table 列设置

avatar-upload-column

运行效果

avatar-upload-column-view

头像和名称合并为一列

大多数情况 avatar 并不会单独显示为一列,而是如下图效果:

avatar-label-column-view

要实现这种配置需要进行数据转换。Avatar 列要求的数据为:

{ 
"avatar": "图片地址",
"label": "显示 label"
}

例如,上图实例效果的数据转换配置为:

avatar-label-column

avatar-label-column-js

其中 JS 数据转换代码如下:

(function transform(data) {
return data.map((item) => {
return {
...item,
nickName: {
avatar: item.avatar?.resources?.[0]?.url,
label: item.nickName,
},
};
});
})(this);

多选头像 Group

当有多个 avatar 头像的时候,可以设置 group 组的展示形式,此时 Avatar 列要求的数据格式为数组形式:

[
{
"avatar": "图片地址",
"label": "显示 label"
},
{
"avatar": "图片地址",
"label": "显示 label"
}
]

例如,这里有一个多选的上传组件,希望把多个图片展示为多个 avatar 的形式。配置和运行效果如下:

Table 列设置

avatar-group-column

数据转换

avatar-group-js

其中 JS 数据转换代码如下:

(function transform(data) {
return data.map((item) => {
const c817f839 = item.c817f839?.resources?.map((x) => {
return { avatar: x.url, label: item.name };
});
return { ...item, c817f839 };
});
})(this);

运行效果

avatar-group-view

合并多行选择列(GROUP_SELECT_COLUMN)

注意

GROUP_SELECT_COLUMN 与 AG Grid 原生的 Row Grouping(行分组)功能类似但并非同一功能,两者不可混用。启用合并多行选择列后,AG Grid 的行分组功能会被自动禁用。

GROUP_SELECT_COLUMN 是表格的一种特殊列类型,用于将值相同且相邻的多行合并为一个单元格,并在合并后的单元格中显示一个 checkbox,实现"整组勾选"的交互效果。

典型使用场景:数据中某个字段(如订单号、批次号)会对应多行明细,用户需要按组批量选中/取消选中这些行。

运行效果如下图所示,多行被合并并共用一个 checkbox:

group-select-column-1

快速开始

在表单设计器中,选中表格的某一列,将列的类型切换为 "合并多行选择",设计器会自动完成以下配置:

  1. 将表格切换为 clientSide(一次性加载)模式
  2. 开启单元格合并
  3. 关闭分页
  4. 隐藏分组面板
  5. 配置多选行为(隐藏 ag-grid 自带的 checkbox 列,由本列接管选择逻辑)

用户只需配置分组字段即可使用。

设计器中新增一列并选中“合并多行选择”然后配置数据字段:

group-select-column-2

group-select-column-3

group-select-column-4

配置项说明

配置项说明默认值
分组字段 (field)值相同的相邻行会被合并成一组,共用一个勾选框。支持从当前数据源的字段列表中选择,也可手动输入任意字段路径。
列的 ID (colId)列的唯一标识。'group-selection'
排序 (sort)排序方向,确保同组数据相邻排列,合并才能正确生效。升序
不合并空值数据 (notMergeEmptyValue)默认勾选:空值各自独立,不会与其它空值行合并。取消勾选时空值之间也会合并成一组。勾选(空值不合并)

运行时行为

分组与合并逻辑

  1. 表格加载数据后,按照配置的排序方向对分组字段进行排序,确保同组数据相邻。
  2. 相邻行的分组字段值相同时会被合并成一个单元格;值为 null / undefined / ''(空值)的行默认各自独立,除非取消勾选"不合并空值数据"。

Checkbox 选择逻辑

  • 单元格 checkbox:勾选时会同时选中/取消选中该组内所有可选行。

JSON 配置示例

以下为一个完整的列配置示例(columnDefs 部分):

{
"columnDefs": [
{
"type": "GROUP_SELECT_COLUMN",
"colId": "group-selection",
"field": "orderNo",
"sort": "asc",
"width": 60,
"minWidth": 60,
"flex": null
},
{
"field": "orderNo",
"headerName": "订单号"
},
{
"field": "itemName",
"headerName": "物料名称"
},
{
"field": "quantity",
"headerName": "数量"
}
]
}
提示

通过设计器切换列类型时,表格级配置(clientSide 模式、关闭分页等)会自动设置,无需手动编写。如果是手动编写 JSON,还需要在表格的 componentProps 中配置 rowModelType: "clientSide"enableCellSpan: truepagination: falserowGroupPanelShow: "never" 以及 rowSelection

获取选中行数据

配合按钮使用,可以获取用户勾选的行数据并进行后续操作(如批量审批等)。

实现步骤:

  1. 为当前表格设置一个 ID(如 my-table),后续在 JS 中通过该 ID 获取表格 API。
  2. 在表格旁添加一个按钮,设置按钮的 action 为调用自定义方法(如 approve)。
  3. 在 JS 中通过 formApi.registerMethod 注册该方法,方法内通过 formApi.getFieldApi 获取表格的 AG Grid API,再调用 getSelectedRows() 获取选中行。

设计器中按钮配置调用自定义方法的界面:

group-select-column-5

formApi.registerMethod('batchApprove', (params) => {
const selectedRows = formApi.getFieldApi('my-table').gridApi.getSelectedRows();
// selectedRows 即为当前用户勾选的所有行数据数组
// 在此处进行下一步操作,如调用接口批量审批
console.log('选中的行:', selectedRows);
});

运行时点击按钮后获取选中行数据的效果:

group-select-column-6

CSS 样式自定义

组件在运行时会为合并的单元格动态添加 CSS class,方便自定义样式:

可用的 class

Class 名称添加时机说明
icp-group-select-cell-selected组内有任意行被选中时控制合并单元格的选中背景色
icp-group-select-even分组序号为偶数(0, 2, 4...)用于区分相邻组
icp-group-select-odd分组序号为奇数(1, 3, 5...)用于区分相邻组

自定义奇偶组样式示例

利用 icp-group-select-even / icp-group-select-odd 可以为相邻组添加不同的视觉区分,例如不同颜色的右边框:

/* 偶数组:蓝色右边框 */
.icp-group-select-even {
border-right: 3px solid #1890ff !important;
}

/* 奇数组:绿色右边框 */
.icp-group-select-odd {
border-right: 3px solid #52c41a !important;
}

通过 columnDef 的 cellClass 自定义

如果只想为某张特定表格的合并选择列定制样式,可以在列配置 JSON 中使用 cellClass

{
"type": "GROUP_SELECT_COLUMN",
"colId": "group-selection",
"field": "orderNo",
"cellClass": "my-custom-group-cell"
}

结合奇偶组 class 编写对应的 CSS 样式:

/* 偶数组:蓝色右边框 */
.my-custom-group-cell.icp-group-select-even {
border-right: 3px solid #1890ff !important;
}

/* 奇数组:绿色右边框 */
.my-custom-group-cell.icp-group-select-odd {
border-right: 3px solid #52c41a !important;
}

在哪里配置 CSS

以上自定义 CSS 可以在前端配置中进行设置,详细操作请参考:外观配置 - CSS 自定义

注意事项与限制

  1. 仅支持一次性加载(clientSide)模式
    合并多行选择依赖客户端 API 来实现表头全选和整组连续判断,不支持 serverSide(服务端分页/排序)模式。切换为该列类型时,表格会自动从 serverSide 切换到 clientSide。

  2. 必须关闭分页
    分页会将数据拆分到不同页面,导致同一组的行可能被分到不同页中,无法跨页合并。

  3. 其它列的排序与分组被禁用
    用户手动排序或分组其它列会打破分组字段的排序连续性,导致合并失效。因此启用该列后,其它列的排序和分组功能会被自动禁用。

  4. 性能考虑
    由于必须使用 clientSide 模式且关闭分页,所有数据会一次性加载到前端。数据量较大时(如数万行以上)可能会影响性能,请根据实际业务量评估。

  5. 空值处理
    默认情况下,分组字段值为空(null / undefined / '')的行各自独立,不会合并。如需空值行也参与合并,取消勾选"不合并空值数据"。

  6. 排序方向的作用
    排序配置(升序/降序)的主要作用是确保同组数据相邻排列。选择升序或降序取决于业务上希望哪些组排在前面。

复杂表格列定义(CUSTOM_COLUMN)

注意

请勿滥用 CUSTOM_COLUMN。由于每个自定义列都需要递归渲染低代码组件树,过多使用可能会带来性能问题。仅在已有的其他所有列类型(如 TEXT_COLUMNSELECT_COLUMNAVATAR_COLUMN 等)均无法满足需求时,才使用 CUSTOM_COLUMN 来实现自定义列内容。

当内置的列类型无法满足需求时,可以使用 CUSTOM_COLUMN 类型来自定义表格列的渲染内容。CUSTOM_COLUMN 允许你在列定义中直接使用低代码 JSON 配置来描述单元格的渲染结构,无需编写 JS 代码。

例如下图用红框圈起来的 2 个 Text 拼接 + 1 个 Button 动作。

custom-column-3

配置示例

以下示例展示了一个自定义列,包含文本显示和根据行数据条件显示不同图标按钮的效果:

custom-column-1

custom-column-2

{
"type": "CUSTOM_COLUMN",
"colId": "customCol",
"headerName": "Custom Cell",
"width": 250,
"flex": null,
"cellRendererParams": {
"fields": [
{
"component": "Stack",
"componentProps": {
"flexDirection": "row",
"alignItems": "center"
},
"fields": [
{
"component": "Text",
"componentProps": {
"content": ":textCol :statusCol4"
}
},
{
"component": "Button",
"componentProps": {
"icon": "material:star-border-rounded",
"action": {
"type": "confirm"
},
"type": "text",
"size": "small"
},
"hidden": {
"dataPredicate": {
"operator": "AND",
"conditions": [
{
"condition": "equals",
"value": "done",
"dataField": "statusCol4"
}
]
}
}
},
{
"component": "Button",
"componentProps": {
"icon": "material:star-rounded",
"action": {
"type": "confirm"
},
"type": "text",
"size": "small"
},
"hidden": {
"dataPredicate": {
"operator": "AND",
"conditions": [
{
"condition": "equals",
"value": "done",
"dataField": "statusCol4"
}
]
},
"valueIfPositive": false,
"valueIfNegative": true
}
}
]
}
]
}
}

上面的示例效果为:

  • 显示当前行 textColstatusCol4 字段的拼接文本
  • statusCol4 不等于 "done" 时,显示空心星标按钮(star-border)
  • statusCol4 等于 "done" 时,显示实心星标按钮(star)

基本原理

CUSTOM_COLUMN 列通过 cellRendererParams.fields 接收一组低代码组件 JSON 配置,使用 RecursionRenderer 递归渲染。渲染时会将当前行数据注入上下文,子组件中可以通过 :fieldName 变量语法引用当前行的字段值。

JSON 配置方式

在表格列定义中,将 type 设置为 CUSTOM_COLUMN,并在 cellRendererParams.fields 中定义要渲染的组件结构:

{
"type": "CUSTOM_COLUMN",
"colId": "customCol",
"headerName": "自定义列",
"width": 250,
"cellRendererParams": {
"fields": [
// 低代码组件 JSON 配置
]
}
}

关键属性说明:

属性说明
type固定值 "CUSTOM_COLUMN"
colId列的唯一标识
headerName列标题
cellRendererParams.fields低代码组件 JSON 数组,定义单元格的渲染内容
信息

CUSTOM_COLUMN 默认 sortable: falsefilter: false,因为自定义渲染列通常不具备排序和过滤能力。

引用当前行数据

fields 配置中,可以通过 :fieldName 语法引用当前行的字段值。例如当前行数据为 { "textCol": "Hello", "statusCol4": "done" },则:

  • :textCol 会解析为 "Hello"
  • :statusCol4 会解析为 "done"
  • 也支持拼接使用,如 ":textCol :statusCol4" 会解析为 "Hello done"

使用场景

CUSTOM_COLUMN 适用于以下场景:

  • 需要在单元格中组合多个组件(如文本 + 按钮 + 图标)
  • 需要根据行数据条件渲染不同内容(通过 hidden + dataPredicate
  • 需要在单元格中放置可交互组件(如按钮触发 action)
  • 需要自定义单元格布局(通过 Stack 等布局组件)
提示

CUSTOM_COLUMN 对可编辑表格 EditableTable 同样适用。

动态修改表格列

表格 API 接口有 updateComponentProps 方法,通过此方法可以修改任意的表格属性,包括表格列的定义 columnDefs

如下示例所示,在表格渲染过后,点击 “添加列” 的按钮过后,动态往表格后面添加了一列 12/2/2024

update-column-defs-run

设计器中的配置如下:

update-column-defs-button update-column-defs-js

此示例的 JS 代码如下:

formApi.registerMethod('changeColumnDefs', () => {
const tableApi = formApi.getFieldApi('45cfdeaa-table');
const { columnDefs } = tableApi.getComponentProps();

const newColDef = {
colId: new Date().toLocaleDateString(),
field: new Date().toLocaleDateString(),
headerName: new Date().toLocaleDateString(),
};
const newColumnDefs = [...columnDefs, newColDef];

tableApi.updateComponentProps({
columnDefs: newColumnDefs,
});
});

将外部过滤条件保存到表格视图

该示例针对场景:不想使用 table toolbar 自带的过滤功能,需要自己将过滤条件配置在表格外部,但是同时又希望跟 table toolbar 自带的 filter 一样将过滤条件存储到 table 的状态里,方便下次刷新页面它依然生效。同时想将外部过滤条件保存到表格的视图里,可以切换视图使用。

如下图所示。左上角是自定义的一个 feature 过滤条件,修改 feature,表格内容会刷新过滤请求。同时将改条件保存在 table toolbar 右上角 的视图里命名为 view 2,下次切换到 view 2 会自动带出 feature 设置的过滤条件。

custom-out-filter-1

实现步骤如下:

先实现表格联动外部过滤条件

此例中,外部配置了一个过滤条件 acl,ID 是 feature。同时,表格的 ID 是 user-story-list-table

custom-out-filter-2

custom-out-filter-3

然后,配置 table 数据源的数据过滤,关联到一个动态变化的表单数据。此例中改表单数据叫 :formData.customUserStoryFilters, 其中 customUserStoryFilters 可以随意修改为自己喜欢的名字,只要和下面 JS 代码中的 key 匹配得上就行。

custom-out-filter-4

使用方法 formApi.on('fieldValueChange', (id, newValue) => {}) 监听外部自定义字段值发生改变。

当外部自定义字段值发生改变的时候调用方法 formApi.setValue('customUserStoryFilters', dataFilters); 设置给表格绑定的数据过滤 customUserStoryFilters

通过 JS 实现将外部过滤条件存储到 table 的状态里

通过表格接口 tableApi.setTableState() 将外部过滤条件保存到 table 的状态里。

然后可以通过表格事件 onSwitchFavoriteView 监听 table toolbar 右上角切换视图操作,获取上一步保存下来的 customUserStoryFilters

两个步骤完整的 JS 代码如下。

function customFilterModelToApiDataFilters(customFilterModel) {
if (!customFilterModel) return null;
return customFilterModel.map((item) => {
if (item.filterType === 'set') {
return {
...item,
// 后端 api 接口是按照 label 进行过滤的,所以发送出去 label
values: item.values.map((v) => v.label),
};
}
return item;
});
}

function setCustomFilterToTable(customFilterModel) {
// 转换成 api 要求的 filter 格式
const dataFilters = customFilterModelToApiDataFilters(customFilterModel);
// 设置给表格的 componentProps.dataFilters 依赖的 userStoryFilters,表格就会自动刷新数据请求。
formApi.setValue('customUserStoryFilters', dataFilters);
}

function restoreCustomFilter(customFilterModel) {
// 按需设置,有多少字段设多少,字段太多可以使用 formApi.setData() 批量设置
const feature = (customFilterModel || []).find((item) => item.colId === 'feature');
if (feature) {
formApi.setValue('feature', feature.values);
} else {
formApi.setValue('feature', null);
}

setCustomFilterToTable(customFilterModel);
}

function initialCustomFilter() {
const tableApi = formApi.getFieldApi('user-story-list-table');

// 如果你的 table 在页面一加载的时候就有,那直接通过 tableApi 获取上一次离开页面存下来的 customFilterModel 就行。
const customFilterModel = tableApi.getTableState().customFilterModel;
// 如果你的 table 在页面一加载的时候是隐藏的,那就自己去自己存的 localStorage 里的
// 代码类似: const customFilterModel = JSON.parse(localStorage.getItem(customFilterKey));

restoreCustomFilter(customFilterModel);
}

formApi.setFieldComponentProps('user-story-list-table', {
// 监听表格的切换视图功能,去除自定义的 customFilterModel 使用
onSwitchFavoriteView: (view) => {
restoreCustomFilter(view.customFilterModel);
},
});

formApi.on('ready', function () {
// 上一次离开页面有存 customFilterModel,取出来重新设置给自定义 filter 条件字段以及表格的 dataFilters 属性
initialCustomFilter();
});

formApi.registerMethod('doFilter', () => {
const formData = formApi.getData();
const { feature } = formData;

const customFilterModel = [];
if (feature?.length) {
customFilterModel.push({
colId: 'feature',
filterType: 'set',
// 需要保存完整的 value label 数组,以用于刷新页面的时候重新赋值给 acl 显示
values: feature,
});
}

setCustomFilterToTable(customFilterModel);

// 将自定义 filter 存到 tableState 里去,可以在 table 保存视图的时候一起保存,
// 下次刷新页面的时候可以通过 tableApi.getTableState() 获取回来,切换视图的时候通过 onFavoriveViewChange 接口取出来。
const tableApi = formApi.getFieldApi('user-story-list-table');
tableApi.setTableState({
// 名字随意起就行,我这里叫 customFilterModel
customFilterModel: customFilterModel,
});

// 对应上面的 table 一开始不存在的情况,如果有需求,自己在 localStore 里存一份自己用。
// 注意!! key 一定要 by user 存区分用户,否则每个用户看到都是一样的。
// 可以仿照 table 默认的 localStorage key,可以通过如下代码获取 username 和 pbc token
// const { pbcToken, userProfile } = formApi.getContext();
// const customFilterKey = ['table', pbcToken, '如果要区分表单和布局,这里还可以填 formEntityToken layoutToken,不需要则删除这句话', '改成你的 table 的 id', userProfile.username].join('.');
// localStorage.setItem(customFilterKey, JSON.stringify(customFilterModel));
});

API

完整的 Table API 参考在此处 Table