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

Vue3向Laravel传输含文件的FormData遇422验证错误求助

问题描述

我在Vue3项目中向Laravel后端传输包含文件的数据,已编写FormData构建代码,将表单字段及文件添加至FormData中,并通过封装的基于Vue3 $fetch的fetchData方法发送POST请求,但后端返回422验证错误,提示flows、responsible_name等多个字段必填。使用Postman测试该接口可正常请求,尝试过XHR等多种方式仍无法解决问题。


FormData构建代码

const formData = new FormData();
formData.append('responsible_name', groupForm.value.responsibleName);
formData.append('responsible_phone', groupForm.value.responsiblePhone);
formData.append('responsible_email', groupForm.value.responsibleEmail);
formData.append('appeal_type_id', groupForm.value.appealTypeId);
formData.append('municipality_id', groupForm.value.municipalityId);
formData.append('educational_institution_id', groupForm.value.educationalInstitutionId);
formData.append('users', groupForm.value.users);
formData.append('flows', groupForm.value.flows.filter((flow: EventFlow) => flow.select).map((flow: EventFlow) => flow.id));
formData.append('appeal_doc', fileInput.value.files[0]);

请求发送代码

const headers = {
  'Content-Type': 'multipart/form-data',
  Accept: 'application/json',
  'Access-Control-Allow-Origin': '*',
  'Cache-Control': 'no-cache',
};
const makeAppeal = async <T>(body: BodyFetchData): Promise<T> => {
    try {
      const data = await fetchData('/appeal/create', 'POST', body, headers);
      return data as T;
    } catch (e) {
      return Promise.reject(e);
    }
};

fetchData封装代码

const fetchData = async <T>(url:string, method: MethodFetchData = 'GET', body: BodyFetchData = '', headers: Record<string, string> = {}): Promise<T> => {
  const apiUrl = `${useRuntimeConfig().public.domainApi}${url}`;
  const headersDefault = {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'Access-Control-Allow-Origin': '*',
    ...headers,
  };
  try {
    if (method === 'GET') {
      const data = await $fetch(apiUrl, { headers: headersDefault, method, params: body || {} });
      return data as T;
    }
    if (method === 'POST') {
      const data = await $fetch(apiUrl, { headers: headersDefault, method, body });
      return data as T;
    }
    return Promise.reject();
  } catch (error) {
    return Promise.reject(error);
  }
};

后端错误响应

{"message":"The flows field is required. (and 4 more errors)","errors":{"flows":["The flows field is required."],"responsible_name":["The responsible name field is required."],"responsible_phone":["The responsible phone field is required."],"responsible_email":["The responsible email field is required."],"users":["The users field is required."]}}

解决方案

问题根源出在FormData解析、请求头设置和数组字段传递三个方面,对应修改如下:

1. 移除手动设置的Content-Type头

发送FormData时,浏览器会自动生成带边界标识的Content-Type(例如multipart/form-data; boundary=----WebKitFormBoundaryxxx),手动设置固定值会导致后端无法识别FormData结构。直接删除请求头中的Content-Type配置。

2. 动态调整fetchData的默认请求头

原封装代码中默认添加application/json头,会干扰FormData解析。修改为:当请求体是FormData类型时,自动移除Content-Type;非FormData请求才保留默认头。

3. 正确传递数组类型字段

Laravel接收数组字段需要用字段名[]作为键名,或多次调用append添加同一键。将flows、users数组改为逐个添加。


修改后的代码示例

FormData构建代码

const formData = new FormData();
formData.append('responsible_name', groupForm.value.responsibleName);
formData.append('responsible_phone', groupForm.value.responsiblePhone);
formData.append('responsible_email', groupForm.value.responsibleEmail);
formData.append('appeal_type_id', groupForm.value.appealTypeId);
formData.append('municipality_id', groupForm.value.municipalityId);
formData.append('educational_institution_id', groupForm.value.educationalInstitutionId);

// 处理users数组(如果users是数组类型)
if (Array.isArray(groupForm.value.users)) {
  groupForm.value.users.forEach(user => formData.append('users[]', user));
} else {
  formData.append('users', groupForm.value.users);
}

// 处理flows数组
const selectedFlowIds = groupForm.value.flows.filter((flow: EventFlow) => flow.select).map((flow: EventFlow) => flow.id);
selectedFlowIds.forEach(id => formData.append('flows[]', id));

formData.append('appeal_doc', fileInput.value.files[0]);

请求发送代码

// 移除Content-Type,保留必要头
const headers = {
  Accept: 'application/json',
  'Cache-Control': 'no-cache',
};
const makeAppeal = async <T>(body: BodyFetchData): Promise<T> => {
    try {
      const data = await fetchData('/appeal/create', 'POST', body, headers);
      return data as T;
    } catch (e) {
      return Promise.reject(e);
    }
};

fetchData封装代码

const fetchData = async <T>(url:string, method: MethodFetchData = 'GET', body: BodyFetchData = '', headers: Record<string, string> = {}): Promise<T> => {
  const apiUrl = `${useRuntimeConfig().public.domainApi}${url}`;
  
  // 动态构建请求头
  let headersDefault = {
    Accept: 'application/json',
    'Access-Control-Allow-Origin': '*',
    ...headers,
  };

  // 若请求体是FormData,删除Content-Type让浏览器自动处理
  if (body instanceof FormData) {
    delete headersDefault['Content-Type'];
  } else {
    headersDefault['Content-Type'] = 'application/json';
  }
  
  try {
    if (method === 'GET') {
      const data = await $fetch(apiUrl, { headers: headersDefault, method, params: body || {} });
      return data as T;
    }
    if (method === 'POST') {
      const data = await $fetch(apiUrl, { headers: headersDefault, method, body });
      return data as T;
    }
    return Promise.reject();
  } catch (error) {
    return Promise.reject(error);
  }
};

内容的提问来源于stack exchange,提问作者bobrov_site

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 23:43:10