Customize page tables to implement data management page functions
1. Usage scenarios
After the user collects a batch of user information tables and fills in the form, we hope to manage the data intuitively. In the past, we can use the create data management page to achieve, however, the data management page has some limitations and cannot directly modify and add data. In this case, we can use the table component in the custom page to implement it, after the data is displayed, you can edit its operation column to achieve the effect of adding, deleting, modifying and checking.
2. Implement functions
If you implement more complex business scenarios, you can configure (Advanced Edition) features as needed.
This example features:
- The configuration data is displayed in a table.
- Image upload custom display
- Table pagination
- Table sorting
- Search (Advanced Edition)
- Create an activity (Advanced Edition)
- Refresh
- View details
- Modify activities (Advanced Edition)
- Delete activity (Advanced Edition)
- Batch delete activities (Advanced Edition)
- Image Download (Advanced Edition)
2.1. Configure the activity information table form
2.2. Get data to display in a table
2.2.1. Add a remote data source
Reference documents:Search form instance details by criteria
The interface configuration is as follows:
`/${window.pageConfig.appType}/v1/form/searchFormDatas.json`
2.2.2. Add variables
2.2.2.1. Number of instances per page
2.2.2.2. Current page number
2.2.2.3. Query criteria (Advanced Edition)
2.2.2.4. Sort condition
2.2.2.5. Table loading status
2.2.2.6. Table data
2.2.2.7. The id of the currently selected table data instance (Advanced Edition)
Used for batch operations.
2.2.3. Copy the following JS code to the page JS and call it in didMount
2.2.3.1. Advanced Edition
export function didMount() {
this.getData(); // 获取数据
}
// 获取数据 - 高级版
export function getData() {
const { pageSize, currentPage, searchFieldData = {}, dynamicOrderData = {} } = this.state;
this.dataSourceMap.getData.load({
formUuid: 'FORM-KW766OD1AGUFQQHJ80EG66IGJ6S13GIUKYROL2',
pageSize,
currentPage,
searchFieldJson: JSON.stringify(searchFieldData),
dynamicOrder: JSON.stringify(dynamicOrderData),
}).then((res) => {
const { totalCount, data = [] } = res;
const result = data.map((item) => {
const { formInstId, formUuid, formData = {} } = item;
return {
formInstId,
formUuid,
...formData,
};
});
this.setState({
tableData: {
currentPage,
data: result,
totalCount,
},
tableIsLoading: false,
});
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.setState({
tableIsLoading: false,
});
});
}
2.2.3.2. Basic edition
export function didMount() {
this.getData(); // 获取数据
}
// 获取数据 - 基础版
export function getData() {
const { pageSize, currentPage, dynamicOrderData = {} } = this.state;
this.dataSourceMap.getData.load({
formUuid: 'FORM-KW766OD1AGUFQQHJ80EG66IGJ6S13GIUKYROL2',
pageSize,
currentPage,
dynamicOrder: JSON.stringify(dynamicOrderData),
}).then((res) => {
const { totalCount, data = [] } = res;
const result = data.map((item) => {
const { formInstId, formUuid, formData = {} } = item;
return {
formInstId,
formUuid,
...formData,
};
});
this.setState({
tableData: {
currentPage,
data: result,
totalCount,
},
tableIsLoading: false,
});
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.setState({
tableIsLoading: false,
});
});
}
2.2.4. Table configuration
2.2.4.1. Data column
Enter the component ID in the activity information table directly.
Custom rendering of active images:
// 活动图片表格列自定义渲染
export function renderImageCell(value, index, rowData) {
const imgList = value ? JSON.parse(value) : [];
const imgUrls = imgList.map((item) => { return item.url; });
return <div class='imgContainer' style={{
display: 'flex',
width: '170px',
paddingBottom: '12px',
overflowX: 'auto',
userSelect: 'none'
}}>{imgList.map((item, index) => {
return (
<img
src={item.previewUrl}
onClick={() => {
this.utils.previewImage({
urls: imgUrls,
current: item.url,
});
}}
style={{
display: 'block',
width: '48px',
height: '48px',
borderRadius: '6px',
cursor: 'pointer',
marginRight: (index + 1) !== imgList.length ? '12px' : '0px'
}}
/>
)
})}</div>;
}
2.2.4.2. Data source
2.2.4.3. Data primary key
formInstId
2.2.4.4. Loading status
2.2.4.5. Pagination settings
// 分页
export function onFetchData(params) {
const { pageSize } = this.state;
if (params.from === 'search' || params.pageSize !== pageSize) {
params.currentPage = 1;
}
const orderTypeText = {
'desc': '-', // 降序
'asc': '+', // 升序
};
let dynamicOrderData = {};
dynamicOrderData[params.orderColumn] = orderTypeText[params.orderType];
this.setState({
currentPage: params.currentPage,
pageSize: params.pageSize,
dynamicOrderData,
tableIsLoading: true,
});
this.getData();
}
2.2.4.6. Row selector (Advanced Edition)
// 表格数据选择变动回调
export function onTableDataSelectChange(selectedRowKeys, records) {
this.setState({
selectedTableDataRowKeys: selectedRowKeys,
});
}
2.3. Add tool functions to Page JS (Advanced Edition)
No modification is required.
// 判断是否是钉钉容器
export function isDingTalkEnv(win) {
win = win || window;
return win.navigator && /dingtalk/i.test(win.navigator.userAgent) //true false
}
// 获取成员字段的 value
export function formatEmployeeFieldValue(userData) {
if (Array.isArray(userData)) {
return userData.length ? userData.map((item) => { return item.value; }) : '';
} else {
return userData ? [userData.value] : '';
}
}
// fieldList:Array,需要校验组件的唯一标识集合
export async function fieldsValidate(fieldList = []) {
const result = [];
for (let i = 0; i < fieldList.length; i++) {
await this.$(fieldList[i]).validate((errors, values) => {
if (!errors) { return };
result.push({
fieldId: fieldList[i], // 组件标识
errors: this.utils.isMobile() ? errors.errors[fieldList[i]].errors : errors[fieldList[i]].errors // 校验错误信息
});
});
};
return result;
}
2.4. Search configuration (Advanced Edition)
If you need to use more operators for advanced search, see the following example to upgrade the search activity function.
FaaS 连接器 - 钉钉开放平台 - 通过高级查询条件获取表单实例数据(包括子表单组件数据)
2.4.1. Add a query component
2.4.2. Bind search events
Modify the field mapping according to the actual situation.
// 搜索
export function onSearch(values) {
this.setState({
currentPage: 1,
searchFieldData: {
textField_l9m5jvma: values.textField_lorywk26, // 活动名称
radioField_l9m5jvmc: values.selectField_los2eeiz, // 活动类型
radioField_l9m5jvmg: values.selectField_l9xx3dnz, // 所属组织
radioField_l9m5jvmn: values.selectField_lorywk27, // 活动进度
dateField_l9m5jvmi: values.cascadeDateField_l9njsj95 ? [values.cascadeDateField_l9njsj95.start, new Date(values.cascadeDateField_l9njsj95.end).setHours(23, 59, 59, 999)] : '', // 开始时间
employeeField_l9m5jvmp: this.formatEmployeeFieldValue(values.employeeField_l9njsj96), // 发起人
},
tableIsLoading: true,
});
this.getData(); // 获取数据
}
2.4.3. Bind reset event
// 重置
export function onReset(values) {
this.setState({
currentPage: 1,
searchFieldData: {},
tableIsLoading: true,
});
this.getData(); // 获取数据
}
2.5. Create an activity (Advanced Edition)
If you need to create activities in batches, see the following example to upgrade the create activity function.
2.5.1. Add a remote data source
Reference documents:Add a form instance
The interface configuration is as follows:
`/${window.pageConfig.appType}/v1/form/saveFormData.json`
2.5.2. Add create activity dialog box
As shown in the following figure:
2.5.3. Configure the top operation and bind the following functions
// 创建活动弹窗
export function onCreateActivityDialog() {
this.$('dialog_lowmwcke').show(() => {
this.$('textField_l9njsj8u').reset();
this.$('selectField_l9njsj8w').reset();
this.$('selectField_l9njsj8v').reset();
this.$('dateField_l9njsj8y').reset();
this.$('dateField_l9njsj8z').reset();
this.$('selectField_los2eej0').reset();
this.$('employeeField_l9njsj90').reset();
this.$('imageField_l9njsj94').reset();
});
}
2.5.4. Bind the following function to the create activity dialog box
// 确认创建活动
export function onCreateActivityOk() {
// 需要校验组件的唯一标识集合
const createFieldList = [
'textField_l9njsj8u',
'selectField_l9njsj8w',
'selectField_l9njsj8v',
'dateField_l9njsj8y',
'dateField_l9njsj8z',
'selectField_los2eej0',
'employeeField_l9njsj90',
];
// 调用表单校验函数
this.fieldsValidate(createFieldList).then((errorList) => {
setTimeout(() => {
if (errorList.length > 0) {
// 表单校验未通过
return;
};
this.$('dialog_lowmwcke').set('confirmState', 'LOADING'); // 开启对话框加载状态
// 新增表单数据
this.dataSourceMap.saveData.load({
formUuid: 'FORM-KW766OD1AGUFQQHJ80EG66IGJ6S13GIUKYROL2',
appType: pageConfig.appType,
formDataJson: JSON.stringify({
textField_l9m5jvma: this.$('textField_l9njsj8u').getValue(), // 活动名称
radioField_l9m5jvmc: this.$('selectField_l9njsj8w').getValue(), // 活动类型
radioField_l9m5jvmg: this.$('selectField_l9njsj8v').getValue(), // 所属组织
dateField_l9m5jvmi: this.$('dateField_l9njsj8y').getValue(), // 开始时间
dateField_l9m5jvmj: this.$('dateField_l9njsj8z').getValue(), // 结束时间
radioField_l9m5jvmn: this.$('selectField_los2eej0').getValue(), // 活动进度
employeeField_l9m5jvmp: this.formatEmployeeFieldValue(this.$('employeeField_l9njsj90').getValue()), // 发起人
imageField_l9nvelqm: (this.$('imageField_l9njsj94').getValue() || []).map((item) => {
return {
downloadUrl: item.downloadURL || item.downloadUrl,
name: item.name,
previewUrl: item.imgURL || item.imgUrl,
url: item.url,
};
}), // 将上传图片的参数修改为符合接口传参的格式,否则数据无法写入
}),
}).then(() => {
this.$('dialog_lowmwcke').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
this.$('dialog_lowmwcke').hide();
this.utils.toast({
title: '创建成功',
type: 'success',
});
setTimeout(() => {
this.setState({
tableIsLoading: true,
});
this.getData(); // 获取数据
}, 1000);
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.$('dialog_lowmwcke').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
});
});
}, 0);
}
2.6. Refresh
2.6.1. Configure the top operation and bind the following functions
// 刷新
export function onRefresh() {
this.setState({
tableIsLoading: true,
});
this.getData(); // 获取数据
}
2.7. View details
2.7.1. Configure the action column and bind the following functions
// 详情
export function onDetailClick(rowData) {
this.utils.router.push(
`/${pageConfig.appType}/formDetail/${rowData.formUuid}`,
{
formInstId: rowData.formInstId,
},
true,
true,
);
}
2.8. Modify activities (Advanced Edition)
2.8.1. Add a remote data source
Reference documents:Update the specified component value in the form
The interface configuration is as follows:
`/${window.pageConfig.appType}/v1/form/updateFormData.json`
2.8.2. Add modify activity dialog box
As shown in the following figure:
2.8.3. Configure the action column and bind the following functions
// 修改活动弹窗
export function onEditActivityDialog(rowData) {
this.$('dialog_lowmwckf').show(() => {
this.$('textField_los2eeja').setValue(rowData.formInstId);
this.$('textField_los2eej1').setValue(rowData.textField_l9m5jvma);
this.$('selectField_los2eej2').setValue(rowData.radioField_l9m5jvmc_id);
this.$('selectField_los2eej3').setValue(rowData.radioField_l9m5jvmg_id);
this.$('dateField_los2eej4').setValue(rowData.dateField_l9m5jvmi);
this.$('dateField_los2eej5').setValue(rowData.dateField_l9m5jvmj);
this.$('selectField_los2eej6').setValue(rowData.radioField_l9m5jvmn_id);
this.$('employeeField_los2eej7').setValue((rowData.employeeField_l9m5jvmp || []).map((item, index) => {
return {
label: item,
value: rowData.employeeField_l9m5jvmp_id[index],
};
}));
this.$('imageField_los2eej8').setValue(rowData.imageField_l9nvelqm ? JSON.parse(rowData.imageField_l9nvelqm) : '');
});
}
2.8.4. Bind the following function to the modify activity dialog box
// 确认修改活动
export function onEditActivityOk() {
// 需要校验组件的唯一标识集合
const editFieldList = [
'textField_los2eej1',
'selectField_los2eej2',
'selectField_los2eej3',
'dateField_los2eej4',
'dateField_los2eej5',
'selectField_los2eej6',
'employeeField_los2eej7',
];
// 调用表单校验函数
this.fieldsValidate(editFieldList).then((errorList) => {
setTimeout(() => {
if (errorList.length > 0) {
// 表单校验未通过
return;
};
this.$('dialog_lowmwckf').set('confirmState', 'LOADING'); // 开启对话框加载状态
// 修改表单数据
this.dataSourceMap.editData.load({
formInstId: this.$('textField_los2eeja').getValue(), // 实例 id
updateFormDataJson: JSON.stringify({
textField_l9m5jvma: this.$('textField_los2eej1').getValue(), // 活动名称
radioField_l9m5jvmc: this.$('selectField_los2eej2').getValue(), // 活动类型
radioField_l9m5jvmg: this.$('selectField_los2eej3').getValue(), // 所属组织
dateField_l9m5jvmi: this.$('dateField_los2eej4').getValue(), // 开始时间
dateField_l9m5jvmj: this.$('dateField_los2eej5').getValue(), // 结束时间
radioField_l9m5jvmn: this.$('selectField_los2eej6').getValue(), // 活动进度
employeeField_l9m5jvmp: this.formatEmployeeFieldValue(this.$('employeeField_los2eej7').getValue()), // 发起人
imageField_l9nvelqm: (this.$('imageField_los2eej8').getValue() || []).map((item) => {
return {
downloadUrl: item.downloadURL || item.downloadUrl,
name: item.name,
previewUrl: item.imgURL || item.imgUrl,
url: item.url,
};
}), // 将上传图片的参数修改为符合接口传参的格式,否则数据无法修改
}),
}).then(() => {
this.$('dialog_lowmwckf').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
this.$('dialog_lowmwckf').hide();
this.utils.toast({
title: '修改成功',
type: 'success',
});
setTimeout(() => {
this.setState({
tableIsLoading: true,
});
this.getData(); // 获取数据
}, 1000);
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.$('dialog_lowmwckf').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
});
});
}, 0);
}
2.9. Delete activity (Advanced Edition)
2.9.1. Add a remote data source
Reference documents:Delete a form instance
The interface configuration is as follows:
`/${window.pageConfig.appType}/v1/form/deleteFormData.json`
2.9.2. Add Delete activity dialog box
As shown in the following figure:
2.9.3. Configure the action column and bind the following functions
// 删除活动弹窗
export function onDeleteActivityDialog(rowData) {
this.$('dialog_l9njsj8t').show(() => {
this.$('textField_los2eejb').setValue(rowData.formInstId);
});
}
2.9.4. The delete activity dialog box binds the following functions
// 确认删除活动
export function onDeleteActivityOk() {
this.$('dialog_l9njsj8t').set('confirmState', 'LOADING'); // 开启对话框加载状态
// 删除表单数据
this.dataSourceMap.deleteData.load({
formInstId: this.$('textField_los2eejb').getValue(), // 实例 id
}).then(() => {
this.$('dialog_l9njsj8t').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
this.$('dialog_l9njsj8t').hide();
this.utils.toast({
title: '删除成功',
type: 'success',
});
setTimeout(() => {
this.setState({
tableIsLoading: true,
});
this.getData(); // 获取数据
}, 1000);
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.$('dialog_l9njsj8t').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
});
}
2.10. Batch delete activities (Advanced Edition)
2.10.1. Configure FaaS connector
For more information about how to configure a connector, see the following documentation:
FaaS 连接器 - 批量删除表单实例 | 钉钉宜搭·帮助中心
2.10.2. Add variables and configure row selectors
Refer to 2.2.2.7 and 2.2.4.6 above.
2.10.3. Add a remote data source
2.10.4. Add batch delete activity dialog box
As shown in the following figure:
2.10.5. Configure the top operation and bind the following functions
// 批量删除活动弹窗
export function onBatchDeleteActivityDialog(rowData) {
const { selectedTableDataRowKey = [] } = this.state;
if (!selectedTableDataRowKey.length) {
this.utils.toast({
title: '当前未选中任何活动',
type: 'warning',
});
return;
}
this.$('dialog_lpjg5aha').show(() => {
this.$('numberField_lpjg8ky1').setValue(selectedTableDataRowKey.length);
});
}
2.10.6. Bind the following function to the batch delete activity dialog box
// 确认批量删除活动
export function onBatchDeleteActivityOk() {
const { selectedTableDataRowKeys = [] } = this.state;
this.$('dialog_lpjg5aha').set('confirmState', 'LOADING'); // 开启对话框加载状态
// 批量删除表单数据
this.dataSourceMap.batchDeleteData.load({
inputs: JSON.stringify({
body: {
formUuid: 'FORM-4C833A663C8F4FC7A265CB075A82ED962L0T', // 活动信息表formUuid
appType: pageConfig.appType, // 应用 appType
formInstanceIdList: selectedTableDataRowKeys, // 需要删除的数据实例id列表
asynchronousExecution: true, // 是否需要异步执行该任务
executeExpression: false, // 是否需要触发表单绑定的校验规则、关联业务规则和第三方服务回调
},
}),
}).then((res) => {
this.$('dialog_lpjg5aha').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
const { success, error } = res;
if (success) {
this.$('dialog_lpjg5aha').hide();
this.utils.toast({
title: '批量删除成功',
type: 'success',
});
setTimeout(() => {
this.setState({
tableIsLoading: true,
selectedTableDataRowKeys: [],
});
this.getData(); // 获取数据
}, 1000);
} else {
this.utils.toast({
title: error,
type: 'success',
});
}
}).catch(({ message }) => {
this.utils.toast({
title: message,
type: 'error',
});
this.$('dialog_lpjg5aha').set('confirmState', 'NORMAL'); // 关闭对话框加载状态
});
}
2.11. Image Download (Advanced Edition)
DingTalk workbench or mobile terminal is not supported.
2.11.1. Introduce three-party JS resources required for image download
export async function didMount() {
if (!this.utils.isMobile() || !this.isDingTalkEnv()) {
// 不支持在钉钉工作台或移动端中下载
try {
await this.utils.loadScript('https://g.alicdn.com/code/lib/jszip/3.9.1/jszip.min.js');
await this.utils.loadScript('https://g.alicdn.com/code/lib/FileSaver.js/2.0.5/FileSaver.min.js');
console.log('DownloadImage Load Success');
} catch (e) {
this.utils.toast({
title: '图片下载加载失败',
type: 'error'
});
};
};
this.getData(); // 获取数据
}
2.11.2. Add image download dialog box
The configuration is as follows:
2.11.3. Add variables
2.11.3.1. The id of the currently selected image.
2.11.3.2. Details of the currently selected image
2.11.4. Configure the table in the image download dialog box
2.11.4.1. Data column
2.11.4.2. Data primary key
previewUrl
2.11.4.3. Row selector
// 图片选择变动回调
export function onImageSelect(selectedRowKeys, records) {
this.setState({
selectedImageRowKeys: selectedRowKeys,
selectedImageDetail: records,
});
}
2.11.5. Configure the action column and bind the following functions
// 图片下载弹窗
export function onDownloadImageDialog(rowData) {
const { imageField_l9nvelqm } = rowData;
if (this.utils.isMobile() || this.isDingTalkEnv()) {
this.utils.toast({
title: '不支持在钉钉工作台或移动端中下载',
type: 'warning',
});
return;
};
if (!imageField_l9nvelqm) {
this.utils.toast({
title: '暂无图片需要下载',
type: 'warning',
});
return;
}
this.$('dialog_legisiyq').show(() => {
this.$('textField_legtgpx9').setValue(`批量下载文件_${this.utils.formatter('date', Date.now(), 'YYYYMMDDhhmmss')}`);
this.$('tablePc_legpq38q').set('data', JSON.parse(imageField_l9nvelqm));
this.setState({
selectedImageRowKeys: [],
selectedImageDetail: [],
});
});
}
2.11.6. Add tool functions to Page JS
No modification is required.
// 单张图片下载
export function downloadFile(downloadUrl) {
const link = document.createElement('a');
link.href = downloadUrl;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 多张图片打包下载
export function downloadFiles(downloadfiles, fileName) {
const zip = new JSZip();
const result = downloadfiles.map(item => {
let promise = getFileBlob(item.downloadUrl).then((res) => {
zip.file(item.name, res, { binary: true });
});
return promise;
});
Promise.all(result).then(() => {
zip.generateAsync({ type: "blob" }).then((res) => {
saveAs(res, `${fileName}.zip`);
})
})
}
// 通过请求获取文件blob格式
function getFileBlob(url) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest()
request.open("GET", url, true)
request.responseType = "blob"
request.onload = (res) => {
if (res.target.status == 200) {
resolve(res.target.response)
} else {
reject(res)
}
}
request.send()
})
}
2.11.7. Bind the following functions to the image download dialog box
// 确定下载图片
export function onDownloadImageOk() {
const { selectedImageDetail = [] } = this.state;
if (!selectedImageDetail.length) {
this.utils.toast({
title: '尚未选择任何图片',
type: 'warning'
});
return;
};
if (selectedImageDetail.length > 1) {
// 批量下载
this.$('textField_legtgpx9').validate();
const fileName = this.$('textField_legtgpx9').getValue();
if (!fileName) { return; };
this.downloadFiles(selectedImageDetail, fileName);
this.$('dialog_legisiyq').hide();
} else {
// 单张下载
this.downloadFile(selectedImageDetail[0].downloadUrl);
this.$('dialog_legisiyq').hide();
};
}