PaaS平台低代码表单常用逻辑封装与实战示例
在PaaS低代码平台中,表单交互逻辑高度依赖运行时上下文(ctx)、字段元数据(fieldQueue)、表单实例(form)及平台提供的工具方法(如request、getTranslateText)。以下为高频业务场景的标准化实现方式,涵盖校验、联动、国际化、弹窗集成、附件处理等核心能力。
1. 时间区间双向校验
确保开始时间不晚于结束时间,且任一字段变更时触发对方校验:
const [value, rule, ctx] = arguments;
const fields = Object.values(ctx.form.fields).filter(f => f.data);
const findFieldByApiName = (name) => fields.find(f => f?.data?.source?.field?.apiName === name);
const startField = findFieldByApiName('market_time_start');
const endField = findFieldByApiName('market_time_end');
if (startField && endField) {
const startMs = startField.value ? new Date(startField.value).getTime() : null;
const endMs = endField.value ? new Date(endField.value).getTime() : null;
if (value && startField.data.source.field.apiName === 'market_time_start' && endMs && startMs > endMs) {
return '投放起始时间不可晚于截止时间';
}
if (value && endField.data.source.field.apiName === 'market_time_end' && startMs && endMs < startMs) {
return '投放截止时间不可早于起始时间';
}
// 主动触发关联字段校验
if (startField.errors?.length && startField.validate) startField.validate();
if (endField.errors?.length && endField.validate) endField.validate();
}
2. 多字段互斥必填校验
手机号与邮箱至少填写一项,支持动态翻译:
const [value, rule, ctx] = arguments;
const getTrans = ctx?.getTranslateText || ((k, d) => d);
const { form, extendsParams: { fieldQueue } } = ctx;
const emailField = fieldQueue?.find(f => f.field?.apiName === 'c_email') &&
form?.query(fieldQueue.find(f => f.field?.apiName === 'c_email')?.field?.instanceCode)?.take();
const mobileField = fieldQueue?.find(f => f.field?.apiName === 'c_mobile') &&
form?.query(fieldQueue.find(f => f.field?.apiName === 'c_mobile')?.field?.instanceCode)?.take();
if (!emailField?.value && !mobileField?.value) {
emailField?.validate();
mobileField?.validate();
return getTrans('messageTip.phoneOrEmail', '手机号与邮箱需至少填写一项');
}
3. 表单初始化联动控制
根据主对象类型动态显示/隐藏关联字段,并设置日期控件最小可选值:
const [form, deferredObj, bindObj, fieldQueue, basicInfo] = arguments;
const { primaryVal, pageParamType } = basicInfo;
const getField = (apiName) => {
const field = fieldQueue?.find(f => f.field?.apiName === apiName);
return field && form?.query(field.field.instanceCode)?.take();
};
// 关联对象类型切换时控制子字段显隐
const typeField = getField('c_custome_object');
deferredObj[typeField?.data?.source?.field?.instanceCode]?.promise?.then(() => {
const selectedLabel = typeField?.dataSource?.find(i => i.value === typeField?.value)?.label;
['c_leads_id', 'c_business_id', 'c_customer_id'].forEach(api => {
const f = getField(api);
f?.setDisplay(selectedLabel === api.replace('c_', '').replace('_id', ''));
});
});
// 设置跟进时间最小日期为今日
const timeField = getField('next_followup_time');
timeField?.setComponentProps({
'disabled-date': (date) => date.getTime() < new Date().setHours(0, 0, 0, 0)
});
4. 自定义按钮驱动表单操作
封装通用弹窗打开逻辑,兼容PC与移动端路由跳转:
<template>
<el-dialog v-model="dialogVisible" title="新建任务" width="900">
<FormResult ref="formRef" />
</el-dialog>
</template>
<script setup>
import { ref } from 'vue';
import FormResult from 'FormResult';
const dialogVisible = ref(false);
const formRef = ref(null);
const init = (info, callback) => {
const params = {
objectCode: 'c_task',
layoutCode: 'C_LYT_7x8y9z0a1b',
openType: 'Dialog',
pageParamType: { businessTypeCode: 'C_BTE_abc123', parentId: info.context.id }
};
formRef.value.open({ param: params }, callback);
dialogVisible.value = true;
};
defineExpose({ init });
</script>
5. 明细表格联动计算与校验
实时汇总金额并限制单行输入不超过上限:
// 列值变更事件:自动汇总回款总额
const [listData, index, headerInfo, emits, props, BigNumber, form] = arguments;
const totalField = form?.query('FID_kfmbqd84o9')?.take();
const sum = listData.reduce((acc, row) => {
const amount = Number(row['FID_l4ss56bopt']?.key) || 0;
return new BigNumber(acc).plus(amount).toNumber();
}, 0);
emits('dataValChangeEffectForm', [{ instanceCode: 'FID_kfmbqd84o9', value: sum.toFixed(2) }]);
// 列校验事件:单行金额不得超过最大可回款额
const [value, callback, ctx, row] = arguments;
const maxAllowed = Number(row?.rowData?.['FID_l4ss56bopt']?.key) || 0;
if (Number(value) > maxAllowed) {
return callback(new Error(`单笔回款不可超过${maxAllowed}元`));
}
callback();
6. 安全附件提交封装
统一调用平台request接口上传附件,支持多类型分组:
const submitAttachments = (objectId, fileLists, attachmentTypes) => {
const promises = fileLists.map((files, idx) => {
const authList = files.map(f => ({
fileId: f.id,
settingList: f.settings || []
}));
return props.request({
url: `${props.baseApi.VITE_PAAS_BASE_URL}/runtime-extender/file/saveAttachmentList`,
method: 'post',
data: {
objectCode: basicInfo()?.objectCode,
objectId,
attachmentAuthInfoList: authList,
attachmentType: attachmentTypes[idx]
}
});
});
return Promise.all(promises);
};
7. 跨平台国际化提示
通过注入式翻译函数实现按钮级文案适配:
<script setup>
import { inject } from 'vue';
const getTrans = inject('getTranslateText');
const handleSave = async () => {
const res = await saveData();
if (res.code === '0') {
ElMessage.success(getTrans()('common.saveSuccess', '保存成功'));
} else {
ElMessage.error(getTrans()('common.saveFail', '保存失败'));
}
};
</script>