必须在choose回调最开头禁用上传按钮,使用prop('disabled',true)并同步修改文字和样式;禁用仅限按钮本身,避免影响校验;需在done、error、complete三处统一恢复,且须排查layui.js重复引入导致的多实例绑定问题。

upload.render 里 submit 按钮没禁用,点击就发多请求
用户点一次上传按钮,Layui 默认会立刻触发 choose 回调,但此时按钮仍是可点击状态。如果用户手快连点两下,就会触发两次 choose,进而调用两次 obj.upload()——哪怕后端做了幂等,前端也已把多个文件塞进队列、重复预览、重复生成 DOM 节点。
必须在 choose 回调最开头就禁用按钮,且仅禁用按钮本身:
$('#upload-btn').prop('disabled', true).addClass('layui-btn-disabled').text('上传中…')- 别用
attr('disabled', 'disabled'),部分浏览器不触发语义变更 - 禁用动作不能写在
done或error里——那时请求早发出去了 - 如果页面用过
layui.form.render()重绘表单,禁用状态会被清空,需在 render 后手动补设
multiple: true 时,choose 中的 this.files 是引用,不是拷贝
当启用多文件上传(multiple: true),choose 回调里的 this.files 是一个实时更新的 FileList 对象。如果你在回调里直接操作 this.files(比如 delete this.files[0]),后续再点按钮,obj.files 可能为空或错位——但这不是重复提交的主因,而是导致「你以为点了却没反应」的隐藏坑。
真正引发重复的关键是:每次点击都新建一个 obj 实例,而每个实例都拥有独立的 upload() 方法。所以防重复必须绑定到按钮上,而不是靠清理 this.files。
- 不要在
choose里做delete this.files[i],改用闭包变量缓存原始File数组(如pendingFiles = Array.from(obj.files)) - 按钮禁用后,即使用户再点,
choose也不会执行——前提是禁用逻辑写在事件源头(即按钮 click 绑定前) - 若上传区域是动态插入的 DOM,确保
upload.render()在元素存在后再调用,否则elem找不到,绑定失败,按钮始终可点
移动端 touchstart 阶段比 click 更早拦截双击
安卓 WebView 和旧版 iOS 存在 300ms click 延迟,用户快速连点可能触发两个 click 事件,即使你用了 prop('disabled', true),也可能因时机差几毫秒而失效。
解决方案是提前在 touchstart 阶段锁死:
- 给上传按钮同时绑定
touchstart和click,并在touchstart里立即禁用:$('#upload-btn').on('touchstart', function() { $(this).prop('disabled', true) }) - 避免同时监听
touchend和click,容易重复触发;统一用click+preventDefault()+ 禁用更稳妥 - 禁用后记得在
done/error/always三处都恢复,不能只靠 success
重复引入 layui.js 导致 upload 绑定多次
这是最隐蔽也最常被忽略的根源:HTML 中同时存在 <script src="layui.js"></script> 和 <script src="layui.all.js"></script>,或在不同 script 标签里重复加载 layui.js。结果是 upload.render() 被执行多次,同一个按钮触发多个上传实例,点一下等于发 N 次请求。
检查方式很简单:
- 打开 DevTools → Console,输入
layui.upload,看是否报错或返回 undefined - 搜索 HTML 源码,确认
layui.js只出现一次,且没和layui.all.js共存 - 若用构建工具(如 webpack),确保没有重复
import 'layui'或require('layui')
禁用按钮只是表层控制,真正的防线在初始化阶段——绑错一次,后面怎么禁都拦不住并发请求。


















