前端fetch自定义批量发送new formdata()数据(对象加文件)

826 阅读3分钟

最近写图片批量上传同时携带对象数据功能,遇到了一些问题,前端使用element-plus的el-upload组件,但是因为是自定义fetch传送,所以传输还是等同于原生, 后端使用express的multer中间件接收. 首先表单数据一般须声明multipart/form-data以及input的type

<form  action="#" method="post" enctype="multipart/form-data" >
    <input type="text" />
</form>

采用Formdata传送数据,数据会被转换为特殊的包含键值的字符串,其中append的第一个参数只是告知后端,后端解析数据时的键,固定语法,

const fd = new FormData()
fd.append("file", rawfile)
fd.append("text", "你好")
fetch(url, {method: "post", body: fd}) // 后面省略 用fetch传递时不要指定content-type, 因为涉及boundary分隔,默认就好

当我们传普通数据时,直接append就行,每次append类似于在传输数据数组里push了一个值,批量的普通数据,直接放数组里for循环append就行,但是都要指定append第一个形参,形参相同的话,批量数据就会放在一个数组里;
而复杂数据就要讨论格式转换问题了,之前以为json转换及解析下就行了,然后就会收到TypeError: Cannot convert object to primitive value 的报错. 因为复杂数据后端打印貌似是json格式,但实际嵌套的value都变成了字符串

// 此数据参考  详细内容可以查看  https://www.cnblogs.com/wonyun/p/7966967.html
var obj = 
{ a: '2', 
b: {c: 'test'}, 
c: [ {id: 1, name: 'xx'}] 
}

//假如item是append的第一个参数,最终转换的formData数据是这样的 
item[a]: 2 
item[b][c]: test 
item[c][][id]: 1 
item[c][][name]: xx

//  生成的各种类型数据都会以这样独立的字符串 类似数组的形式 传输

所以对象要么只能以键值对的形式单独append,且value还必须是普通数据
要么要自己构造一个转换函数,此处参考这位大佬的文章

function objectToFormData (obj, form, namespace) {
    const fd = form || new FormData();
    let formKey;
    for(var property in obj) {
        if(obj.hasOwnProperty(property)) {
          let key = Array.isArray(obj) ? '[]' : `[${property}]`;
          if(namespace) {
            formKey = namespace + key;
          } else {
            formKey = property;
          }
          // if the property is an object, but not a File, use recursivity.
          if(typeof obj[property] === 'object' && !(obj[property] instanceof File)) {
            objectToFormData(obj[property], fd, formKey);
          } else {
            // if it's a string or a File object
            fd.append(formKey, obj[property]);
          }
          
        }
      }
    return fd;
  }
  
//膜拜大神的递归,不过此函数有个小问题,当对象为空时数据不会被append执行
  {"_id":34564645,"name":"436546","num":"546546", urls: []}
  //最终只会得到  
obj[_id] 34564645
obj[name] 436546
obj[num] 546546
//并没有期待的     // 也许可以改进,目前不确定是函数问题还是formdata本身解析问题
obj[urls][]
 //formData查看方法 let fd = new formData()
 for (var [a, b] of fd.entries()) {
  console.log(a, b);
} 

下面是大致实现的代码


let res = await api.batchUploadImg(fileArr , toRaw(formData.self)) //批量上传

//  function objectToFormData  此处省略上面大神写的封装函数...

// 数据上传函数
async batchUploadImg(files, item) {
        try {  //此处为了后端获取数据分开 files 是原始图片文件的数组, item为对象数据
            const body1 = new FormData()
            for (let i = 0; i < files.length; i++) {
                body1.append("images", files[i]);
            }
            let body = objectToFormData(item, body1, "data")
            let rawres =  await fetch('http://url', 
            { method: 'post', body, headers: 
            { 'Authorization': window.localStorage.getItem('accessToken')}})
            return rawres.json()
        } catch (error) {
            return {status: 999, message:'网络错误或者服务器未开启'}
        }
    }
    
//  后端multer配置
const upload = multer({ storage })  //multer会在storage此处直接存储不带后缀名的文件,所以需要中间函数进行处理
const uploadRouter = require('./router/upload')
app.use('/upload', upload.any(), uploadRouter)  

// 后端获取数据
let item = req.body.data   // multer会将数据放到req.body里, 文件放入req.files
let files = req.files