跳到主要内容

使用表格 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

动态修改表格列

表格 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,
});
});

复杂表格列定义

通过上述所说的 updateComponentProps 方法,可以动态修改表格列的定义,实现复杂的表格列定义。 complex-table-column-js complex-table-form-render

复杂的表格列定义要在"JS Code"中通过写js的代码实现。ag-Grid官方文档可以参考:https://www.ag-grid.com/javascript-data-grid/component-cell-renderer/

此示例的 JS 代码如下:


formApi.on('ready', function () {
const tableApi = formApi.getFieldApi('test-table');
const { columnDefs } = tableApi.getComponentProps();
const additionalColumnDefs = [
{
headerName: '普通字符串',
field: 'code',
colId: 'code',
},
{
headerName: '两行显示',
field: 'name',
cellRenderer: BasicInfoRender,
},
{
headerName: '渲染颜色',
field: 'colorSize',
cellRenderer: ColorSizeRender,
},
{
headerName: '文字加跳转连接',
field: 'order',
cellRenderer: OrderRender,
},
{
headerName: '表格',
field: 'countDetail',
cellRenderer: CountDetailRender,
},
{
headerName: '打开弹框',
field: 'openModal',
cellRenderer: OpenModalRender,
},
{
headerName: '表头跨列',
colId: 'arrange',
field: 'arrange',
children: [
{
colId: 'color',
field: 'color',
headerName: '列一',
},
{
colId: 'number',
field: 'number',
headerName: '列二',
},
],
},
];
const newColumnDefs = [...columnDefs, ...additionalColumnDefs];
tableApi.updateComponentProps({
rowHeight: 220,
columnDefs: newColumnDefs,
});
});

class BasicInfoRender {
eGui;

// Optional: Params for rendering. The same params that are passed to the cellRenderer function.
init(params) {
const { code, name } = params.data;
this.eGui = document.createElement('div');

this.eGui.innerHTML = `<div >
<span>款号: </span>
<span>${code}</span>
</div>
<div >
<span>款名: </span>
<span>${name}</span>
</div>
</div>`;
}

// Required: Return the DOM element of the component, this is what the grid puts into the cell
getGui() {
return this.eGui;
}

// Required: Get the cell to refresh.
refresh(params) {
return false;
}
}

class ColorSizeRender {
eGui;

generateTag(name) {
return `<span class="table-cell-tag">${name}</span>`;
}
// Optional: Params for rendering. The same params that are passed to the cellRenderer function.
init(params) {
const { color, size } = params.data;

this.eGui = document.createElement('div');
this.eGui.innerHTML = `<div ><div >
<span>颜色: </span>
<span>${color
.map((item) => {
return this.generateTag(item);
})
.join(' ')}</span>
</div>
</div>`;
}

// Required: Return the DOM element of the component, this is what the grid puts into the cell
getGui() {
return this.eGui;
}

// Required: Get the cell to refresh.
refresh(params) {
return false;
}
}

class OrderRender {
eGui;

generateTag(name) {
return `<span class="table-cell-tag">${name}</span>`;
}
// Optional: Params for rendering. The same params that are passed to the cellRenderer function.
init(params) {
const { count, orderId } = params.data;

this.eGui = document.createElement('div');
this.eGui.innerHTML = `<div ><div >
<span>颜色: </span>
<span>${count}</span>
</div>
<a href="/order${orderId}/view" class="table-cell-mock-button">查看</a>
</div>`;
}

// Required: Return the DOM element of the component, this is what the grid puts into the cell
getGui() {
return this.eGui;
}

// Required: Get the cell to refresh.
refresh(params) {
return false;
}
}

class CountDetailRender {
eGui;

// Optional: Params for rendering. The same params that are passed to the cellRenderer function.
init(params) {
const { count, orderId } = params.data;

this.eGui = document.createElement('div');
this.eGui.innerHTML = `<table class="table-cell-table">
<tr>
<td>颜色\尺码</td>
<td>S</td>
<td>M</td>
<td>合计</td>
</tr>
<tr>
<td>黑色</td>
<td>100</td>
<td>200</td>
<td>300</td>
</tr>
<tr>
<td>白色</td>
<td>300</td>
<td>400</td>
<td>700</td>
</tr>
<tr>
<td>合计</td>
<td>400</td>
<td>600</td>
<td>1000</td>
</tr>
</table>`;
}

// Required: Return the DOM element of the component, this is what the grid puts into the cell
getGui() {
return this.eGui;
}

// Required: Get the cell to refresh.
refresh(params) {
return false;
}
}

class OpenModalRender {
eGui;
eButton;
eventListener;

generateTag(name) {
return `<span class="table-cell-tag">${name}</span>`;
}
// Optional: Params for rendering. The same params that are passed to the cellRenderer function.
init(params) {
const { count, orderId } = params.data;
this.eGui = document.createElement('div');
this.eButton = document.createElement('button');
this.eButton.className = 'btn-simple';
this.eButton.textContent = '编辑';
this.eventListener = () => createModal();
this.eButton.addEventListener('click', this.eventListener);
this.eGui.appendChild(this.eButton);
}

// Required: Return the DOM element of the component, this is what the grid puts into the cell
getGui() {
return this.eGui;
}

// Required: Get the cell to refresh.
refresh() {
return true;
}

destroy() {
if (this.eButton) {
this.eButton.removeEventListener('click', this.eventListener);
}
}
}

function createModal() {
// 检查是否已经存在模态框,避免重复创建
const existingModal = document.getElementById('modal');
if (existingModal) {
existingModal.style.display = 'block'; // 如果已存在,直接显示
return;
}

// 创建模态框的 HTML 字符串
const modalHTML = `
<div id="modal" class="modal">
<div class="modal-content">
<span class="close-btn">×</span>
<h2>模态框标题</h2>
<p>这是一个嵌套了 <iframe> 的模态框。</p>
<iframe width="100%" height="300px" frameborder="0"></iframe>
</div>
</div>
`;

// 将模态框添加到 body
document.body.insertAdjacentHTML('beforeend', modalHTML);

// 获取新创建的元素
const modal = document.getElementById('modal');
const closeBtn = modal.querySelector('.close-btn');
const iframe = modal.querySelector('iframe');

// 显示模态框
modal.style.display = 'block';

// 设置 iframe 的 src(延迟加载)
iframe.src = 'https://baidu.com'; // 可以修改为其他链接

// 关闭模态框
closeBtn.addEventListener('click', () => {
modal.remove(); // 移除模态框
});

// 点击模态框外部时关闭模态框
modal.addEventListener('click', (event) => {
if (event.target === modal) {
modal.remove(); // 移除模态框
}
});
}

此示例的“前端配置”中css代码如下:

.table-cell-tag {
display: inline-block;
border: 1px solid grey;
padding: 4px;
line-height: 1.32;
background: #E6F4FF;
color: #1677FF
}

.table-cell-mock-button {
display: inline-block;
padding: 5px;
line-height: 1.32;
background-color: #1677FF;
color: white;
text-decoration: none;
border-radius: 5px;
}

.table-cell-table {
border-collapse: collapse;
}

.table-cell-table td {
border: 1px solid;
width: 100px
}

/* 模态框容器 */
.modal {
display: none; /* 默认隐藏 */
position: fixed;
z-index: 1000; /* 确保在最上层 */
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4); /* 半透明背景 */
}

/* 模态框内容 */
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 600px; /* 调整宽度以适应 iframe */
position: relative;
}

/* 关闭按钮 */
.close-btn {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}

.close-btn:hover {
color: #000;
}

/* iframe 样式 */
iframe {
border: none; /* 去掉 iframe 边框 */
margin-top: 10px; /* 添加一些间距 */
width: 100%;
height: 300px;
}
提示

以上操作对可编辑表格 EditableTable 也适用。

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

该示例针对场景:不想使用 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