跳到主要内容

表单中关联表单设置默认值(默认选中第一项)

本案例来自三方开发者「Jing」

1. 使用场景

宜搭目前关联表单组件无法设置默认值,借助此例我们可以探讨一下如何去实现相应的功能。

目前仅支持关联表单组件在主表中使用的场景,暂不支持在子表单中使用的场景。

若包含数据填充,关联表单需使用新版数据填充,旧版数据填充不支持触发,具体可见下述更新公告。

升级新版数据填充可参考目录【5.升级新版数据填充】(2023.10.30 后配置的关联表单无需操作)。

2. 实现功能

2.1. 配置关联表单数据表

用于后续关联表单关联数据。

2.2. 不包含数据填充

2.2.1. 配置页面

2.2.2. 配置关联表单

关联 2.1 中配置的表单并设置显示字段。

2.2.3. 将下述代码拷贝至页面 JS 中

无需做任何修改。

/**
* 获取关联表单设置默认值(默认第一个选项)
* @param associationFormField 需要设置默认值的关联表单组件唯一标识
*/
export function getAssociationFormFirstData(associationFormField = '') {
if (this.utils.isSubmissionPage() && associationFormField) {
const {
formUuid, // 关联表单
appType, // 应用 appType
formType, // 表单类型
mainFieldId, // 主要信息字段唯一标识
subFieldId, // 次要信息字段唯一标识
} = this.$(associationFormField).get('associationForm'); // 获取关联表单配置
const {
condition, // 筛选条件关系
rules = [], // 筛选条件
} = this.$(associationFormField).get('dataFilterRules'); // 获取关联表单筛选条件
const searchField = rules.map((item) => {
return {
key: item.id,
value: item.extValue === 'value' ? item.value : this.$(item.value).getValue(),
type: this.getType(item.componentType),
componentName: item.componentType,
operator: item.op,
};
});
const orderConfig = this.$(associationFormField).get('orderConfig') || []; // 获取关联表单排序条件
const dynamicOrder = {}; // 排序条件
orderConfig.forEach((item) => {
dynamicOrder[item.fieldId] = item.order === 'asc' ? '+' : '-';
});
const body = new URLSearchParams();
body.append('_csrf_token', this.getCsrfToken());
body.append('formUuid', formUuid);
body.append('manageUuid', formUuid);
body.append('appType', appType);
body.append('currentPage', 1);
body.append('page', 1);
body.append('pageSize', 20);
body.append('limit', 20);
body.append('logicOperator', condition);
body.append('searchField', JSON.stringify(searchField));
body.append('dynamicOrder', JSON.stringify(dynamicOrder));
fetch(`/${appType}/query/formProcInstData/getInstanceDatas.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
}).then((data) => {
return data.json();
}).then(({ content }) => {
const { totalCount, values = [] } = content;
if (totalCount) {
const { dataMap = {}, formInstanceId = '', processInstanceId = '' } = values[0] || {};
this.$(associationFormField).setValue([{
appType,
formType,
formUuid,
instanceId: formInstanceId || processInstanceId,
subTitle: dataMap[subFieldId],
title: dataMap[mainFieldId],
}]);
}
}).catch(() => {
this.utils.toast({
title: '数据获取失败',
type: 'error',
});
});
}
}

/**
* 获取数据筛选的类型
* @param componentName 字段类型
*/
export function getType(componentName = '') {
if (componentName) {
switch (componentName) {
case 'RadioField': // 单选
case 'CheckboxField': // 复选
case 'SelectField': // 下拉单选
case 'MultiSelectField': // 下拉复选
case 'DepartmentSelectField': // 部门
case 'CountrySelectField': // 国家地区
return 'ARRAY';
case 'NumberField': // 数值
case 'RateField': // 评分
case 'DateField': // 日期
case 'CascadeDateField': // 日期区间
return 'DOUBLE';
default:
return 'TEXT';
}
}
}

/**
* 获取 csrfToken
*/
export function getCsrfToken() {
const tokenDom = document.getElementsByName('_csrf_token');
const csrfToken = tokenDom && tokenDom.length ? (tokenDom[0] || {}).value : '';
return csrfToken;
}

2.2.4. didMount 中调用函数

注意修改括号内的唯一标识(需要设置默认值的关联表单组件唯一标识)。

// 当页面渲染完毕后马上调用下面的函数,这个函数是在当前页面 - 设置 - 生命周期 - 页面加载完成时中被关联的。
export function didMount() {
this.getAssociationFormFirstData('associationFormField_lrq26sam');
}

2.3. 包含数据填充

2.3.1. 配置页面

2.3.2. 配置关联表单

关联 2.1 中配置的表单并设置显示字段。

2.3.3. 配置数据填充

按需配置数据填充。

2.3.4. 将下述代码拷贝至页面 JS 中

无需做任何修改。

/**
* 获取关联表单设置默认值(默认第一个选项)
* @param associationFormField 需要设置默认值的关联表单组件唯一标识
*/
export function getAssociationFormFirstData(associationFormField = '') {
if (this.utils.isSubmissionPage() && associationFormField) {
const {
formUuid, // 关联表单
appType, // 应用 appType
formType, // 表单类型
mainFieldId, // 主要信息字段唯一标识
subFieldId, // 次要信息字段唯一标识
} = this.$(associationFormField).get('associationForm'); // 获取关联表单配置
const {
condition, // 筛选条件关系
rules = [], // 筛选条件
} = this.$(associationFormField).get('dataFilterRules'); // 获取关联表单筛选条件
const searchField = rules.map((item) => {
return {
key: item.id,
value: item.extValue === 'value' ? item.value : this.$(item.value).getValue(),
type: this.getType(item.componentType),
componentName: item.componentType,
operator: item.op,
};
});
const orderConfig = this.$(associationFormField).get('orderConfig') || []; // 获取关联表单排序条件
const dynamicOrder = {}; // 排序条件
orderConfig.forEach((item) => {
dynamicOrder[item.fieldId] = item.order === 'asc' ? '+' : '-';
});
const body = new URLSearchParams();
body.append('_csrf_token', this.getCsrfToken());
body.append('formUuid', formUuid);
body.append('manageUuid', formUuid);
body.append('appType', appType);
body.append('currentPage', 1);
body.append('page', 1);
body.append('pageSize', 20);
body.append('limit', 20);
body.append('logicOperator', condition);
body.append('searchField', JSON.stringify(searchField));
body.append('dynamicOrder', JSON.stringify(dynamicOrder));
fetch(`/${appType}/query/formProcInstData/getInstanceDatas.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
}).then((data) => {
return data.json();
}).then(({ content }) => {
const { totalCount, values = [] } = content;
if (totalCount) {
const { dataMap = {}, formInstanceId = '', processInstanceId = '' } = values[0] || {};
this.$(associationFormField).setValue([{
appType,
formType,
formUuid,
instanceId: formInstanceId || processInstanceId,
subTitle: dataMap[subFieldId],
title: dataMap[mainFieldId],
}]);
this.associationFormDataFilling(associationFormField); // 触发关联表单数据填充
}
}).catch(() => {
this.utils.toast({
title: '数据获取失败',
type: 'error',
});
});
}
}

/**
* 触发关联表单数据填充
* @param associationFormFieldId 需要数据填充的关联表单组件唯一标识
*/
export function associationFormDataFilling(associationFormFieldId = '') {
if (associationFormFieldId) {
const associationFormValue = this.$(associationFormFieldId).getValue();
if (associationFormValue && associationFormValue.length) {
const { appType = '', formType = '', formUuid = '', instanceId = '' } = associationFormValue[0] || {};
if (appType && formType && formUuid && instanceId) {
const dataFillingRules = this.$(associationFormFieldId).get('dataFillingRules') || {}; // 关联表单填充条件
const { mainRules = [], tableRules = [] } = dataFillingRules;
if (mainRules.length) {
// 主表字段数据填充
if (formType === 'receipt') {
// 表单
fetch(`/${appType}/v1/form/getFormDataById.json?_csrf_token=${this.getCsrfToken()}&formInstId=${instanceId}`, {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
})
.then((data) => {
return data.json();
}).then(({ content }) => {
const { formData = {} } = (content || {});
this.mainDataFilling(formData, mainRules);
}).catch(() => {
this.utils.toast({
title: '数据获取失败',
type: 'error',
});
});
}
if (formType === 'process') {
// 流程
fetch(`/${appType}/v1/process/getInstanceById.json?_csrf_token=${this.getCsrfToken()}&processInstanceId=${instanceId}`, {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
})
.then((data) => {
return data.json();
}).then(({ content }) => {
const { data = {} } = (content || {});
this.mainDataFilling(data, mainRules);
}).catch(() => {
this.utils.toast({
title: '数据获取失败',
type: 'error',
});
});
}
}
if (tableRules.length) {
// 子表字段数据填充
const body = new URLSearchParams();
const searchBody = tableRules.map((item) => {
return {
page: 1,
limit: 500,
fieldId: item.tableId,
resultGroupKey: item.tableId,
searchField: (((item.filters || {}).rules) || []).map((i) => {
return {
key: i.id,
value: i.extValue === 'value' ? i.value : this.$(i.value).getValue(),
type: this.getType(i.componentType),
componentName: i.componentType,
operator: i.op,
};
}),
logicOperator: (item.filters || {}).condition,
}
});
body.append('_csrf_token', this.getCsrfToken());
body.append('formUuid', formUuid);
body.append('formInstId', instanceId);
body.append('instSubTableDataParamListStr', JSON.stringify(searchBody));
fetch(`/${appType}/query/formProcInstData/getInstMultiSubTableDatas.json`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body,
})
.then((data) => {
return data.json();
}).then(({ content }) => {
// this.utils.__getFlowMaps().tableFieldIdMap 为非官方提供标准 API,有失效风险,谨慎使用
const targetTableFieldIdMap = this.utils.__getFlowMaps().tableFieldIdMap; // 获取当前页面子表单字段映射
const childDataFillingRules = {}; // 子表字段填充规则
tableRules.forEach((item) => {
if (!childDataFillingRules[item.tableId]) {
childDataFillingRules[item.tableId] = [];
}
const childRules = (item.rules || []).map((i) => {
return {
...i,
sourceTableField: item.tableId, // 目标子表单
targetTableField: targetTableFieldIdMap[i.target], // 当前页面子表单
};
});
childDataFillingRules[item.tableId] = childRules;
});
this.childDataFilling(content, childDataFillingRules);
}).catch(() => {
this.utils.toast({
title: '数据获取失败',
type: 'error',
});
});
}
}
}
}
}

/**
* 主表字段数据填充处理函数
* @param dataMap 源数据
* @param mainRules 主表字段填充规则
*/
export function mainDataFilling(dataMap = {}, mainRules = []) {
try {
mainRules.forEach((item) => {
this.$(item.target).setValue(this.mainDataSolve(dataMap, item.source, item.sourceType));
});
} catch (error) {
console.error(error);
this.utils.toast({
title: '主表字段数据填充错误',
type: 'error',
});
}
}

/**
* 主表数据处理函数
* @param dataMap 源数据
* @param source 源字段唯一标识
* @param sourceType 源字段类型
*/
export function mainDataSolve(dataMap = {}, source = '', sourceType = '') {
try {
if (dataMap[source]) {
switch (sourceType) {
case 'AttachmentField': // 附件
case 'ImageField': // 图片
case 'AddressField': // 地址
return dataMap[source] ? JSON.parse(dataMap[source]) : [];
case 'RadioField': // 单选
case 'CheckboxField': // 复选
case 'SelectField': // 下拉单选
case 'MultiSelectField': // 下拉复选
return dataMap[`${source}_id`];
case 'CascadeSelectField': // 级联选择
return (dataMap[`${source}_id`] || []).at(-1);
case 'EmployeeField': // 成员
return (dataMap[source] || []).map((item, index) => {
return {
label: item,
value: dataMap[`${source}_id`][index],
};
});
case 'DepartmentSelectField': // 部门
case 'CountrySelectField': // 国家 / 地区
return (dataMap[source] || []).map((item, index) => {
return {
text: item,
value: dataMap[`${source}_id`][index],
};
});
case 'DateField': // 日期
return Number(dataMap[source]);
case 'CascadeDateField': // 日期区间
return [Number(dataMap[source][0]), Number(dataMap[source][1])];
default:
return dataMap[source];
}
}
} catch (error) {
console.error(error);
this.utils.toast({
title: '数据填充处理错误',
type: 'error',
});
}
}

/**
* 子表字段数据填充处理函数
* @param dataMap 源数据
* @param childDataFillingRules 子表字段填充规则
*/
export function childDataFilling(dataMap = {}, childDataFillingRules = {}) {
try {
const tableDataResult = {};
Object.keys(dataMap).forEach((tableKey) => {
const items = [];
if (!tableDataResult[tableKey]) {
tableDataResult[tableKey] = [];
}
const tableDataItem = (dataMap[tableKey].values || []).map((item) => {
return item.instValue ? JSON.parse(JSON.parse(item.instValue)) : [];
});
tableDataItem.forEach((i) => {
const objItem = {};
i.forEach((j) => {
objItem[j.fieldId] = this.childDataSolve(j.fieldData, j.componentName);
});
items.push(objItem);
});
tableDataResult[tableKey] = items;
});
Object.keys(childDataFillingRules).forEach((key) => {
if (key && childDataFillingRules[key] && childDataFillingRules[key].length) {
const result = []; // 数据处理结果
tableDataResult[key].forEach((i) => {
const objItem = {};
childDataFillingRules[key].forEach((j) => {
objItem[j.target] = i[j.source];
});
result.push(objItem);
});
this.$(childDataFillingRules[key][0].targetTableField).setValue(result);
}
});
} catch (error) {
console.error(error);
this.utils.toast({
title: '子表单数据填充错误',
type: 'error',
});
}
}

/**
* 子表数据处理函数
* @param fieldData 源数据
* @param componentName 源字段类型
*/
export function childDataSolve(fieldData = {}, componentName = '') {
try {
if (componentName) {
switch (componentName) {
case 'CascadeSelectField': // 级联选择
return ((fieldData.value || []).at(-1) || {}).value;
default:
return fieldData.value;
}
}
} catch (error) {
console.error(error);
this.utils.toast({
title: '数据填充处理错误',
type: 'error',
});
}
}

/**
* 获取数据筛选的类型
* @param componentName 字段类型
*/
export function getType(componentName = '') {
if (componentName) {
switch (componentName) {
case 'RadioField': // 单选
case 'CheckboxField': // 复选
case 'SelectField': // 下拉单选
case 'MultiSelectField': // 下拉复选
case 'DepartmentSelectField': // 部门
case 'CountrySelectField': // 国家地区
return 'ARRAY';
case 'NumberField': // 数值
case 'RateField': // 评分
case 'DateField': // 日期
case 'CascadeDateField': // 日期区间
return 'DOUBLE';
default:
return 'TEXT';
}
}
}

/**
* 获取 csrfToken
*/
export function getCsrfToken() {
const tokenDom = document.getElementsByName('_csrf_token');
const csrfToken = tokenDom && tokenDom.length ? (tokenDom[0] || {}).value : '';
return csrfToken;
}

2.3.5. didMount 中调用函数

注意修改括号内的唯一标识(需要设置默认值且触发数据填充的关联表单组件唯一标识)。

// 当页面渲染完毕后马上调用下面的函数,这个函数是在当前页面 - 设置 - 生命周期 - 页面加载完成时中被关联的。
export function didMount() {
this.getAssociationFormFirstData('associationFormField_lrq3dp10');
}

3. 实现效果

3.1. 不包含数据填充

3.2. 包含数据填充

4. 在线试玩

5. 升级新版数据填充

将关联表单的填充条件重新保存即可升级到新版数据填充(下述操作后页面也需重新保存下)。

Copyright © 2024钉钉(中国)信息技术有限公司和/或其关联公司浙ICP备18037475号-4