在后台管理系统与低代码平台中,经常遇到这样的需求:同一个表单里既有只需要传一个文件的单端字段,也有必须同时传前端包和后端包的双端字段。如果直接在页面写死 HTML,后期加字段就要改模板、加逻辑,维护成本很高。更合理的办法是把字段变成数据,用一段渲染程序把数据变成真正的表单控件。

配置结构设计
动态生成的第一步是定义清晰的字段配置。每一个上传字段都应该描述自己的名称、标签、类型以及是否必填。对于双端字段,还要标记它包含的两个子端,比如 client 和 server,这样渲染时才能知道要画几个文件选择框。配置用普通数组存放,组件初始化时读取,完全不依赖页面写死的节点。
下面是一段配置示例,其中 type 为 single 表示单端,double 表示双端。双端里面用 ends 列出两端标识,后续收集值时会按这些标识分别存文件。这样的结构让后端也能对照同样的 key 来接收,避免前后端字段名对不上。
const fieldConfig = [
{
name: 'license',
label: '授权文件',
type: 'single',
required: true
},
{
name: 'deployPack',
label: '部署包',
type: 'double',
required: true,
ends: [
{ key: 'client', text: '前端包' },
{ key: 'server', text: '后端包' }
]
}
];
渲染函数的实现
有了配置就可以写渲染函数。函数遍历配置数组,根据 type 创建不同的 DOM 结构。单端字段直接放一个 <input> 元素,注意在正文讨论标签时要转义写成 <input>。双端字段则循环 ends,为每个端生成独立的文件框和文字说明,并用相同的字段名加端 key 做标识,方便后面取值。
渲染过程要把事件也绑好。每个文件框监听 change 事件,把拿到的文件对象写进一个全局的 formData 映射里。单端就存成 formData[field.name],双端存成 formData[field.name][end.key]。这样用户选完文件,数据自然归集,不用额外去 DOM 里捞值。
function renderForm(config, mountNode) {
const formData = {};
config.forEach(function (field) {
const group = document.createElement('div');
const label = document.createElement('label');
label.textContent = field.label;
group.appendChild(label);
if (field.type === 'single') {
const input = document.createElement('input');
input.type = 'file';
input.name = field.name;
input.addEventListener('change', function (e) {
formData[field.name] = e.target.files[0];
});
group.appendChild(input);
} else if (field.type === 'double') {
formData[field.name] = {};
field.ends.forEach(function (end) {
const subLabel = document.createElement('span');
subLabel.textContent = end.text;
const input = document.createElement('input');
input.type = 'file';
input.name = field.name + '_' + end.key;
input.addEventListener('change', function (e) {
formData[field.name][end.key] = e.target.files[0];
});
group.appendChild(subLabel);
group.appendChild(input);
});
}
mountNode.appendChild(group);
});
return formData;
}
校验与提交处理
动态表单也要做提交前校验。单端字段检查 formData 里对应 name 是否有文件,双端则要遍历 ends 看每个子端是否都选了。可以把校验逻辑写成独立函数,返回错误信息数组,有错就提示,没错再发请求。这样校验规则和渲染解耦,配置变了校验也不用大改。
提交时用 FormData 对象装载。单端直接 append(field.name, file),双端用循环 append(field.name + '[client]', file) 这种形式,多数后端框架都能解析成嵌套结构。如果走 JSON 接口,也可以先把文件转 base64 再塞进普通对象,不过大文件不推荐,容易超量。下面给出用 FormData 提交的简版代码。
function submitForm(formData) {
const fd = new FormData();
Object.keys(formData).forEach(function (name) {
const val = formData[name];
if (val instanceof File) {
fd.append(name, val);
} else {
Object.keys(val).forEach(function (endKey) {
fd.append(name + '[' + endKey + ']', val[endKey]);
});
}
});
fetch('https://ipipp.com/api/upload', {
method: 'POST',
body: fd
});
}
整体看,把单端与双端上传抽象成配置加渲染,不仅让表单代码变短,也让产品运营自己改字段成为可能。后续如果要支持多端或者图片预览,只需在配置加类型、在渲染加分支,核心收集与提交流程都不用动。
form_componentfile_uploaddynamic_render修改时间:2026-08-14 21:09:26