You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Fetch提交multipart/form-data表单上传文件为空问题修复

问题描述

我有一个如下所示的简单表单:
表单示例

<form id="my-form-1" class="ajax-form my-form p-4 p-md-5 bg-light text-dark rounded shadow-sm fs-6" action="https://www.gmondini.local/contact-form/send/1" method="post" enctype="multipart/form-data" _lpchecked="1">
    ...
    <button type="submit" class="mt-4 p-3 btn btn-primary text-uppercase text-white">Send Message</button>

    <div class="mt-4 d-none alert alert-success" role="alert">
        Thank you for your message. It has been sent.
    </div>
    <div class="mt-4 d-none alert alert-danger" role="alert">
        There was an error trying to send your message. Please try again later.
    </div>
</form>

我编写了如下JavaScript函数,将原生表单提交转换为Ajax异步调用:

document.querySelector('.ajax-form').addEventListener('submit', function(e) {
    e.preventDefault();
    let form = this;
    let formData = new FormData(form);
    let parsedData = {};
    for (let name of formData) {
        if (typeof(parsedData[name[0]]) == "undefined") {
            let tempdata = formData.getAll(name[0]);
            if (tempdata.length > 1) {
                parsedData[name[0]] = tempdata;
            } else {
                parsedData[name[0]] = tempdata[0];
            }
        }
    }

    let options = {};
    switch (this.method.toLowerCase()) {
        case 'post':
            options.body = JSON.stringify(parsedData);
        case 'get':
            options.method = this.method;
            options.headers = { 'Content-Type': 'application/json' };
            break;
    }

    var error = form.querySelector(".alert-danger");
    var success = form.querySelector(".alert-success");
    var submits = form.querySelectorAll('[type="submit"]');
    fetch(this.action, options).then(r => r.json()).then(data => {
        if (typeof data.success == 'undefined' || !data.success) {
            success.classList.add("d-none");
            error.classList.remove("d-none");
        } else {
            success.classList.remove("d-none");
            error.classList.add("d-none");
            submits.forEach(toremove => {
                toremove.classList.add("d-none");
            });
        }
    });
});

点击提交后查看请求payload,上传的文件字段始终为空:
请求payload异常截图
后端基于Laravel 8.0框架,编写如下代码获取上传文件:

$files = $request->file('filebig');
var_dump($files);die;

执行后返回NULL。我已经在表单上正确设置了multipart/form-data属性,该如何修复这个问题?

问题原因

代码存在3个核心问题直接/间接导致文件上传失败:

  • 错误地把FormData转成了JSON对象:JSON.stringify无法序列化File/Blob类型的文件对象,序列化后文件字段会变成空对象或者null,后端自然拿不到文件内容。
  • 手动设置了错误的Content-Type请求头:用multipart/form-data上传文件时,请求头里的Content-Type必须携带浏览器自动生成的boundary分隔符,手动设置为application/json或者不带boundary的multipart/form-data,都会导致后端无法正确解析上传的文件。
  • switch语句漏写break:POST请求逻辑会穿透到GET分支,不管什么请求方法都会强制设置JSON请求头,属于明显的逻辑bug。
修复方案

直接将原生FormData对象作为fetch的body传入,不要手动转JSON,也不要手动设置Content-Type请求头,浏览器会自动生成符合规范的请求头(包含正确的boundary值)。
修复后的完整JS代码如下:

document.querySelector('.ajax-form').addEventListener('submit', function(e) {
    e.preventDefault();
    const form = this;
    // 直接使用FormData实例即可,不需要手动转成普通对象/JSON
    const formData = new FormData(form);

    const options = {
        method: form.method
    };
    // 仅POST/PUT/PATCH等需要传请求体的方法设置body
    if (['post', 'put', 'patch'].includes(form.method.toLowerCase())) {
        options.body = formData;
        // 禁止手动设置Content-Type,浏览器会自动配置multipart/form-data和对应boundary
    }

    const errorEl = form.querySelector(".alert-danger");
    const successEl = form.querySelector(".alert-success");
    const submitBtns = form.querySelectorAll('[type="submit"]');
    
    fetch(form.action, options)
        .then(r => r.json())
        .then(data => {
            if (typeof data.success == 'undefined' || !data.success) {
                successEl.classList.add("d-none");
                errorEl.classList.remove("d-none");
            } else {
                successEl.classList.remove("d-none");
                errorEl.classList.add("d-none");
                submitBtns.forEach(btn => btn.classList.add("d-none"));
                // 可选:提交成功后重置表单
                form.reset();
            }
        })
        .catch(() => {
            // 补充网络异常场景的提示
            successEl.classList.add("d-none");
            errorEl.classList.remove("d-none");
        });
});

额外说明

如果需要在提交前修改/追加表单字段,直接操作FormData实例即可,不要转成普通对象,示例:

// 追加额外字段
formData.append('extra_field', 'extra_value');
// 格式化已有字段
formData.set('username', formData.get('username').trim());

Laravel端不需要做特殊修改,保持原有的$request->file('filebig')写法即可正常获取上传的文件。


内容的提问来源于stack exchange,提问作者Kormi Web Solutions

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 12:51:24