做Vue项目的时候, 提交数据基本上都是用Axios, 之前做过的表单方面的提交, 并没有过多关注客户端和服务器之间的通信过程. 所以一直对HTTP的head请求头, body内容之类的不明不白, 为了短期(是的估计过半年又忘了?)解决这个疑惑, 再次复习了一遍. 顺便总结了 Vue中使用Axios处理包含上传文件的表单提交
场景说明
项目使用的Vue(Nuxt)框架, 数据请求用的Axios插件, 表单包含了一些基本的用户信息填写, 同时还有身份证上传, 和后端沟通过, 提交数据的时候, 接口全部使用POST请求, 那么有文件上传的一般来说只能用formData格式.
实践及代码示例
我项目中使用的是ElementUI, 对于上传组件不熟悉的朋友, 需要注意几个事情:
- action是必填, 那么留空也许是个不错的做法.
- 获取到上传的文件的办法很多. 官方提供了几种事件来获取, 例如:
on-success
,on-change
(首次上传会触发两次), 我这里使用了on-success
-
那么拿到上传的回调, 这里特别需要注意的, 我以
on-success
三个参数来看-
response
是服务器返回的响应 -
file
一个文件 -
fileList
存放多个文件的数组
-
可能看到有file
或者fileList
会直接将它的数据提交给后台, 一开始我也是没注意到这点, 始终无法正确提交数据. 那么经过一番研究和排查, 得知: 真正的File对象是fileList
数组中某个元素的raw
属性!, 那么下面先看一段错误的示范:
页面部分结构代码如下:
点击上传
只能上传jpg/png文件,且不超过500kb
提交
取消
这里我将上传文件数量限制为1
个, 接下来是JavaScript部分:
import AppLogo from '~/components/AppLogo.vue'
export default {
components: {
AppLogo
},
data() {
return {
form: {
name: '',
region: ''
},
fileList: []
}
},
methods: {
handleSuccess(response, file, fileList) {
this.fileList = fileList
},
handleExceed(files, fileList) {
this.$message.warning(`最多上传 ${files.length} 个文件`)
},
onSubmit() {
this.$axios
.$post('/api/active', {
name: this.form.name,
region: this.form.region,
file: this.fileList
})
.then(response => {
if (response.code === 200) {
// 提交成功将要执行的代码
}
})
.catch(function(error) {
// console.log(error)
})
}
}
}
上面的这段 onSubmit
能提交成功就是真的见了鬼呢
问题分析
问题在哪呢, 前面提到, 后台接受数据的格式是multipart/form-data
, 你发个json对象是什么鬼, 没有这方面经验的人肯定就搞不清怎么回事了. 所以一般对这块不熟悉的人容易犯以下的几个错误:
- 不了解上传文件应该以什么方式提交, 比如后台是
multipart/form-data
, 而习惯性以json对象发送数据(因为大量插件对数据对象也封装了方法, 所以容易忽略) - 不知道上传文件提交的格式, 以为将
this.fileList
改成this.fileList[0]
就万事大吉 - 当多种疑惑无法解决的时候, 可能会尝试很多次都不行, 陷入误区而久久无法解决困难. 开始怀疑是否是Axios插件不支持文件上传.
- 其他各种别的问题...
问题解决
其实, 熟悉的话, 解决这个问题很简单. 前面也说过, elementUI将返回的file
对象封装了一下, 首先我们要拿到真正的文件对象, 实际上就是file.raw
或者fileList[0].raw
!
不要以为这样就可以提交数据了. 我们还要使用form-data
特有的提交方式来提交带有文件内容的表单. 废话不多说上一段, 修正后的部分代码:
点击上传
只能上传jpg/png文件,且不超过500kb
onSubmit() {
let form = this.$refs['form'].$el
let formData = new FormData(form)
formData.append('name', this.form.name)
formData.append('region', this.form.region)
formData.append('file', this.fileList[0])
this.$axios
.$post('/api/active', formData)
.then(response => {
if (response.code === 200) {
// 提交成功将要执行的代码
}
})
.catch(function(error) {
// console.log(error)
})
}
简单说明下
- 其实elementUI中提供了一个
http-request
事件来覆盖默认的action, 这样很好的避免了一些异常(比如我在测试环境的时候, 用了不太好的的on-success
通过了验证, 但是在生产环境中由于action地址空所以默认请求当前地址, 出现了404). - formData似乎只能一个个对应的append进去, console出来也看不到, 具体用法可以参考文章末尾的MDN~
- axios是可以很好地完成formData表单数据提交的, 这里虽然也是一个对象, 但是不是普通的json对象, 他对这个数据处理很正常, 所以放心使用.
额外补充: axios配置
Axios可以说在Vue中相当重要, 经常我们对简单的重新封装或者配置, 就这个插件来说完全可以写一篇新文章了, 这里他不是重点我就简单介绍下我用它做的配置
import qs from 'qs'
import { Message } from 'element-ui'
export default function({ $axios, redirect }) {
let apiUrl = process.env.apiUrl
$axios.defaults.baseURL = apiUrl
$axios.defaults.timeout = 15000
$axios.defaults.headers.post['Content-Type'] = 'multipart/form-data'
$axios.onRequest(config => {
// 与后台配合post请求字符串传参
let reqParams = qs.stringify(config.data)
let url = config.url + (reqParams ? '?' : '') + reqParams
config.url = url
})
$axios.onResponse(res => {
if (res.data.code !== 200) {
// 后台返回session过期或异常的情况
if (res.data.code === 401 || res.config.url === apiUrl + '/logout') {
window.sessionStorage.clear()
redirect('/platform/login')
} else {
// 返回到一个错误页面或者提示错误
Message.error(res.data.message)
// redirect('/')
}
}
})
$axios.onError(error => {
Message.error('服务器异常,请稍后再试')
})
}
上面对发送数据请求的相关参数配置了, 也做了拦截器. qs
插件是个亮点, 我为了vue代码书写更清晰, 将json
对象传过来处理为name=whidy&age=30
类似这样的拼接到url后再发送请求给服务器的.
总结
好了说了一大堆, 其实最重要的事情是, 理解以下几点
- 和后端沟通好, 请求格式
- 了解上传文件需要的表单数据格式
- 尽量多的去熟悉第三方UI插件, 尤其像elementUI这样相对完善的组件, 应该是有较好的处理方法的
- 耐心的一步步查找错误
最后献上一些参考资料:
- MDN的FormData介绍
- MDN的使用FormData数据
- 对我帮助很大的来自Axios的Issue
文中难免也有一些描述不准确的地方, 希望大佬们多多指点~ 本文提到的代码的存放在GitHub上面nuxt-spa-demo项目的分支nuxt-axios-formdata, 有兴趣也可以看看~