必须统一使用name="items[]"而非带索引的name,dom顺序决定数组顺序;删除需移除整个.form-item容器;新增用clonenode克隆隐藏模板项;后端接收前确保无隐藏空项。

name属性必须用items[],别手动拼索引
浏览器原生靠name的方括号语法聚合成数组,不是靠JS算出来的数字。写name="items[0]"看似直观,但删掉第1项后,DOM里还留着items[2],后端收到的就是稀疏结构——Express默认跳过缺失索引,PHP可能转成对象,Django解析失败。
✅ 统一用name="items[]",顺序由DOM节点位置决定,增删互不干扰
✅ 多字段嵌套也用空键:name="files[][name]、name="files[][size]
❌ 避免混用:name="meta[key1]"和name="items[]"一起提交,后端解析类型会打架
❌ 别信querySelectorAll('input[name="items[]"]').length来生成新索引——它只数当前DOM,不管用户删没删
删除必须移除整个.form-item容器节点
只清空input.value或调input.remove()是错的:表单仍会提交空值,FormData里残留字段,JS逻辑误判“该项还在”。
✅ 每个可删项用<div class="form-item">包裹,按钮在内部<br>
✅ 删除函数里取<code>button.parentNode(即.form-item),再调removeChild()或直接item.remove()
✅ 加confirm("确定删除?")防手滑,尤其涉及文件或关联数据时
⚠️ 如果用了FormData.append()手动收集,删DOM后必须同步清理FormData实例
新增表单项别拼HTML字符串,用cloneNode(true)更稳
用innerHTML +=注入字符串容易XSS,还会丢事件监听器;每次手写document.createElement又太啰嗦。
✅ 页面加载时藏一个“模板项”:<div class="form-item template" style="display:none">...</div>
✅ 新增时克隆它:const newItem = document.querySelector('.template').cloneNode(true)
✅ 清空值、移除template类、设style.display = "block",再append()到容器
✅ 模板里所有name都用[]语法,不带数字,不依赖JS索引
后端接收不到数组?检查有没有隐藏空项兜底
某些老版本PHP(如5.6)在只有一个items[]时会当字符串处理,导致$_POST['items']不是数组。
✅ 在表单底部加一行隐藏输入:<input name="items[]" value="" style="display:none">
✅ Express + body-parser默认没问题,但若用urlencoded({ extended: true }),确保没关extended
✅ Django用request.POST.getlist('items'),别用request.POST['items']——后者只取第一个











