カスタムページテーブルは、データ管理ページ機能を実現します
1. 使用シーン
ユーザー側でユーザー情報表を収集し、フォームに記入した後、直感的にデータを管理したいと考えており、従来はデータ管理ページを作成することで実現できたしかし、データ管理ページには少し制限があり、データを直接修正して追加するなどの操作ができない場合は、カスタムページの表コンポーネントを使用して実現できますデータを展示した後、その操作列を編集して、添削調査の効果を実現することができる。
2. 機能を実現する
より複雑なビジネスシーンを実現するには、必要に応じて (高度な) 機能を構成できます。
この機能リストの例:
- データをテーブルに表示するように設定します
- 画像アップロードカスタム表示
- テーブルのページング
- テーブルのソート
- 検索 (詳細版)
- イベントの作成 (詳細版)
- 更新
- 詳細を見る
- 修正活動 (上級版)
- 削除活動 (詳細版)
- 一括削除活動 (上級版)
- 画像ダウンロード (プレミアム)
2.1. 【イベント情報テーブル】フォームの設定

2.2. データを取得してテーブルに表示します
2.2.1. リモートデータソースの追加
参考ドキュメント:条件に基づいてフォームインスタンスの詳細リストを検索します
インタフェース構成図:


`/${window.pageConfig.appType}/v1/form/searchFormDatas.json`
2.2.2. 変数を追加
2.2.2.1. 1ページあたりのインスタンス数

2.2.2.2. 現在のページ番号

2.2.2.3. 照会条件 (上級版)

2.2.2.4. ソート条件

2.2.2.5. テーブルロードステータス

2.2.2.6. テーブルデータ

2.2.2.7. 現在選択されているテーブルデータインスタンスid (詳細バージョン)
いくつかの一括操作に使用します。

2.2.3. 次のjsコードをページjsにコピーし、didmountで呼び出します
2.2.3.1. プレミアム版

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. 基本版

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. テーブル設定
2.2.4.1. データ列
データフィールドは「活動情報テーブル」のコンポーネントidに直接入力すればよい。

アクティブな画像のカスタムレンダリング:

// 活动图片表格列自定义渲染
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. データソース

2.2.4.3. データ主キー
Forminstid
2.2.4.4. ロードステータス

2.2.4.5. ページング設定


// 分页
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. 行セレクタ (詳細版)



// 表格数据选择变动回调
export function onTableDataSelectChange(selectedRowKeys, records) {
this.setState({
selectedTableDataRowKeys: selectedRowKeys,
});
}
2.3. ページjsにツール関数を追加する (詳細版)
何も修正する必要はありません。
// 判断是否是钉钉容器
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. 構成の検索 (詳細版)
より多くのオペレータを使用した高度な検索が必要な場合は、次のケースを参考にして検索活動機能をアップグレードできます。
FaaS 连接器 - 钉钉开放平台 - 通过高级查询条件获取表单实例数据(包括子表单组件数据)
2.4.1. クエリコンポーネントの追加

2.4.2. 検索イベントのバインド
状況に応じてフィールドマッピングを修正します。

// 搜索
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. バインディングリセットイベント

// 重置
export function onReset(values) {
this.setState({
currentPage: 1,
searchFieldData: {},
tableIsLoading: true,
});
this.getData(); // 获取数据
}
2.5. イベントの作成 (詳細版)
イベントを一括作成する必要がある場合は、次のケースを参考にして、イベント作成機能をアップグレードできます。
2.5.1. リモートデータソースの追加
参考ドキュメント:フォームインスタンスの新規追加
インタフェース構成図:


`/${window.pageConfig.appType}/v1/form/saveFormData.json`
2.5.2. アクティビティダイアログの作成を追加します
構成図:

2.5.3. トップアクションを設定し、次の関数をバインドします

// 创建活动弹窗
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. アクティビティダイアログの作成次の関数をバインドします

// 确认创建活动
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. 更新
2.6.1. トップアクションを設定し、次の関数をバインドします

// 刷新
export function onRefresh() {
this.setState({
tableIsLoading: true,
});
this.getData(); // 获取数据
}
2.7. 詳細を見る
2.7.1. 操作列を設定し、次の関数をバインドします

// 详情
export function onDetailClick(rowData) {
this.utils.router.push(
`/${pageConfig.appType}/formDetail/${rowData.formUuid}`,
{
formInstId: rowData.formInstId,
},
true,
true,
);
}
2.8. 修正活動 (上級版)
2.8.1. リモートデータソースの追加
参考ドキュメント:フォームで指定したコンポーネント値を更新します
インタフェース構成図:


`/${window.pageConfig.appType}/v1/form/updateFormData.json`
2.8.2. アクティビティダイアログの変更を追加します
構成図:

2.8.3. 操作列を設定し、次の関数をバインドします

// 修改活动弹窗
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. アクティビティダイアログを修正して次の関数をバインドします

// 确认修改活动
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. 削除活動 (詳細版)
2.9.1. リモートデータソースの追加
参考ドキュメント:フォームインスタンスの削除
インタフェース構成図:


`/${window.pageConfig.appType}/v1/form/deleteFormData.json`
2.9.2. アクティビティダイアログの追加削除
構成図:

2.9.3. 操作列を設定し、次の関数をバインドします

// 删除活动弹窗
export function onDeleteActivityDialog(rowData) {
this.$('dialog_l9njsj8t').show(() => {
this.$('textField_los2eejb').setValue(rowData.formInstId);
});
}
2.9.4. アクティビティダイアログを削除して次の関数をバインドします

// 确认删除活动
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. 一括削除活動 (上級版)
2.10.1. Faasコネクタの設定
次のドキュメントを参照して、コネクタを構成できます
FaaS 连接器 - 批量删除表单实例 | 钉钉宜搭·帮助中心
2.10.2. 変数を追加し、行セレクタを設定します
上記の2.2.2.7と2.2.4.6を参考にしてください。
2.10.3. リモートデータソースの追加

2.10.4. 一括削除アクティビティダイアログの追加
構成図:

2.10.5. トップアクションを設定し、次の関数をバインドします

// 批量删除活动弹窗
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. アクティブなダイアログを一括削除して、次の関数をバインドします

// 确认批量删除活动
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. 画像ダウンロード (プレミアム)
ホッチキス作業台やモバイル端末での使用はサポートしていません。
2.11.1. 画像ダウンロードに必要な三者jsリソースを導入する

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. 画像の追加ダウンロードダイアログ
次のように構成します:

2.11.3. 変数を追加
2.11.3.1. 現在選択されている画像id

2.11.3.2. 現在選択されている画像の詳細

2.11.4. 画像ダウンロードダイアログのテーブルの設定
2.11.4.1. データ列

2.11.4.2. データ主キー
Previewurl
2.11.4.3. 行セレクタ

// 图片选择变动回调
export function onImageSelect(selectedRowKeys, records) {
this.setState({
selectedImageRowKeys: selectedRowKeys,
selectedImageDetail: records,
});
}
2.11.5. 操作列を設定し、次の関数をバインドします

// 图片下载弹窗
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. ページjsにツール関数を追加する
何も修正する必要はありません。
// 单张图片下载
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. 画像ダウンロードダイアログには以下の関数がバインドされています

// 确定下载图片
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();
};
}
3. 効果を実現する
3.1. プレミアム版

3.2. 基本版

