笔者在项目中有如下需求:使用多个 el-upload
手动上传文件,最后一次性提交。后台要求提交的文件格式是 binary 即二进制形式,实现过程中出现了文件数据以对象方式提交给后端导致报错。
01 Bug 描述
笔者在使用 Vue + Element UI 进行前端开发时遇到多文件上传的需求,我使用 Element UI 的 el-upload
上传器组件实现这一功能,使用如下图所示的官方用例进行开发
基于上述 手动上传 用例实现多个文件上传时,后端响应结果为 上传文件为空
,即文件没有正确发送给后端,如下所示:
02 手动上传多个文件实现过程
首先回顾手动上传多个文件实现过程,主要分为如下三步:
HTML 页面中引入el-upload
组件并设置属性JS 中监听文件上传事件并作出响应操作使用 Axios 将文件发送给后端处理
2.1 引入 el-upload 组件
el-upload
组件实现了用户点击上传按钮从本地上传文件,这些文件构成的上传文件列表 file-list
将被上传到组件的必选参数 action
指定的地址中。
除了这两个基础参数,该组件还可以通过设置 limit
和 on-exceed
来限制上传文件的个数和定义超出限制时的行为。 更多其他属性设置可以参考官方文档 Upload 上传
<template><el-uploadref="upload"action="jsonplaceholder.typicode./posts/":auto-upload="false":on-change="handleChange"multiple:limit="3":on-exceed="handleExceed":file-list="fileList"><el-button size="small" type="primary" @click="createTask()">上传</el-button><template #tip><div class="el-upload__tip">jpg/png files with a size less than 500kb</div></template></el-upload>
</template>
2.2 监听文件上传事件
笔者的项目中,多个文件是作为向后端提交的表单的一部分,所以不直接使用 action
属性指定的上传地址,并将 auto-upload
属性设置为 false
实现手动上传。
除此之外,还需要定义 on-change
属性,该属性是监听文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用。我们使用该属性实现,动态获取文件上传组件的上传文件列表,并将文件作为提交表单的一部分。相关内容的 JS 代码如下:
<script>
import { addTask } from '@/api/task'
export default {data() {return {// 提交的表单taskForm: {content: null,title: null,attachments: [] // 要向后端传输的多个文件}}},methods: {// 监听文件状态handleChange(file, fileList) {// 将上传文件列表中的所有文件拷贝到 taskForm.attachments 中fileList.forEach(file => {this.taskForm.attachments.push(file)})},// 监听文件数目上限handleExceed(files, fileList) {this.$message.warning(`The limit is 3, you selected ${files.length} files this time, add up to ${files.length + fileList.length} totally`)},// 提交表单表单由三个部分构成 content title 和由多个文件构成的 attachmentscreateTask() {console.log('this task', this.taskForm)this.$refs['taskForm'].validate((valid) => {if (valid) {addTask(this.taskForm).then((response) => {this.$refs.upload.clearFiles()})}})}}
}
</script>
2.3 Axios 向后端传送文件
最后使用 @api/task
中定义的 axios 后端接口 addTask
传送数据,需要注意的是 2.2 中的 taskForm
是 JSON 格式的数据,如果后端处理是接收 FormData 格式的数据需要进行转换。笔者的后端要求接收的是 FormData 格式数据,所以使用 transformRequest
方法将其转换。
export function addTask(data) {return request({method: 'post',url: '/task',headers: {'Content-Type': 'multipart/form-data'},// 将 json 格式的 data 转换成 formDatatransformRequest: [function(data) {const formData = new FormData()for (var key in data) {formData.append(String(key), data[key])}}],data})
}
03 追溯 Bug
梳理了实现过程之后,我开始在每个环节中筛查 Bug。
首先检查了 HTML 中 el-upload
属性是否定义正确,主要是将 auto-upload
属性设置为 false
实现手动上传,定义 on-change
属性获取上传文件列表。
然后检查 on-change
属性的 handleChange()
方法是否正确获取了 el-upload
组件的上传文件列表 file-list
, handleChange()
方法添加输出语句如下:
handleChange(file, fileList) {fileList.forEach(file => {this.taskForm.attachments.push(file)})console.log('attachments', this.taskForm.attachments)},
输出如下,这里我们可以发现第一个 Bug:笔者项目的后端要求接收的文件类型是二进制格式的,而 attachments
中存放的是一个文件对象包含name, size, uid
等额外字段,而我们仅需要其中的二进制数据 raw
,所以仍然报错。
最后,检查 @api/task
中定义的 axios 后端接口 addTask
,虽然修改上述 Bug 之后 attachments
中都是二进制文件,但基于 2.3 节代码实现的 addTask
传送给后端的 attachments
是一个列表,而非二进制数据,所以仍然报错,如下图所示:
04 解决 Bug
发现上述 Bug 后,我们逐一进行解决,首先正确获取 el-upload
组件中 filelist
文件列表中二进制文件数据,仅获取 file.raw
数据即可,修改如下:
handleChange(file, fileList) {fileList.forEach(file => {this.taskForm.attachments.push(file.raw)})},
接着我们需要修改 @api/task
中定义的 axios 后端接口 addTask
实现方式,如果直接将二进制文件数据构成的 attachments
作为 formData 的一个键值对传送给后端会被视为一个列表使得后端无法正确解析。为此,我们单独处理 attachments
的 formData 转换,将二进制文件数据分别封装如下:
export function addTask(data) {return request({method: 'post',url: '/task',headers: {'Content-Type': 'multipart/form-data'},transformRequest: [function(data) {const formData = new FormData()for (var key in data) {// 单独处理二进制文件数据 attachmentsif (key === 'attachments') {continue}formData.append(String(key), data[key])}// 单独处理二进制文件数据 attachmentsfor (var file of data['attachments']) {formData.append('attachments', file)}return formData}],data})
}
修改上述 Bug 之后再次测试结果如下,可以看到文件通过二进制的方式使用同一个 key attachments
传给后端
响应成功如下所示:
参考资料
Element UI 官方文档 Upload 上传
el-upload控件一次接口请求上传多个文件
更多推荐
上传,Element,UI,el
发布评论