一、需求
文件上传有个常见的需求,就是允许用户取消上传文件,特别是在大文件上传时很有必要。

在网上找了很多资料,没有现成的代码给我CV(淦)
翻了半天,只找到了一个提供思路的帖子,对我还是很有帮助的。
二、思路
这就涉及几个问题:
1.如何取消接口请求的问题?
从上面的帖子我得出:
问题1的解决: 使用
xhr
原生方法
abort()
可以取消请求,其他
xhr
库如
axios
,也可以提供了
cancelToken
的API取消请求。
这里介绍一下:
1.如何取消请求
1)xhr原生取消请求方法
在
XMLHttpRequest
对象中可以通过
abort
方法取消。
let xhr = newXMLHttpRequest();
xhr.open('GET or POST', url);
xhr.send();
// 取消请求使用 xhr.abort()
2)axios使用canceltoken取消
在axios中,有两种取消当前请求的方式:
第一种通过其内部提供的CancelToken来取消
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post(url, {data}, {cancelToken: source.token})
// 调用source.cancel()取消请求(可以传参数)
第二种通过CancelToken的构造函数方式取消请求
letCancelToken = axios.CancelToken;
let cancel = null;
axios.get(url, {
cancelToken: newCancelToken(functionexecutor(c) {
cancel = c;
})
})
// 取消请求cancel()
当然我们可以把
cancel
函数挂载到window对象上,在需要取消请求的组建或页面中调用
window.acncel()
,或者绑定到
vue
组件实例的
data
里,或是
vuex
的
$store
里。
如何批量取消接口
上面的方式一次只能取消一个接口。如果我们一次性要取消多个接口怎么呢?
可以通过传递一个
executor
函数到
CancelToken
的构造函数创建
cancelToken
。
axios
有一个
CancelToken
属性,他是一个类,用于获取取消请求的
cancel
方法,获取了该方法之后就可以在合适的地方执行cancel()取消请求了。
这种方式比较麻烦,但是可以用于取消多个请求,你可以将
c
这个取消请求的方法push进一个数组,然后在你需要取消多个请求的时候,循环这个数组,依次执行里面的方法即可取消多个请求。
let arr = [];
const CancelToken = axios.CancelToken;
axios.get('http://localhost:6003/axios/4',{
cancelToken: new CancelToken(function executor(c){
arr.push(c);
cancel = c;
})
}).then(function(res) {
console.log(res.data);
})
axios.get('http://localhost:3000/axios/3',{
cancelToken: new CancelToken(function executor(c){
arr.push(c);
cancel = c;
})
}).then(function(res) {
console.log(res.data);
})
for (let i = 0; i < arr.length; i++) {
arr[i]('请求取消');
}
注意事项:
注意点1:
cancel
取消请求方法,在调用取消请求的时候,可以将取消原因——message字符串传递进去。这样请求在被取消之后,会被catch捕获,你可以在这里将取消原因打印出来或者提示给用户,比如提示用户不要频繁点击发送请求。
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('http://localhost:6003/axios/4',{
cancelToken: source.token
}).then(function(res) {
console.log(res.data);
}).catch(function(err) {
if (axios.isCancel(err)) {
console.log(err.message);
}
})
source.cancel('不想请求了');
注意点2:
get
的
cancelToken
放置在第二个参数的对象里面,
post
的
cancelToken
放置在第三个参数对象里面
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.post('http://localhost:6003/axios',{
username: 'lisi',
content: 123
},{
headers:{
"Content-Type": "application/json"
},
cancelToken: source.token
}
).then(function(ret){
console.log(ret.data);
})
source.cancel('不想请求了');
如何取消接口请求的问题解决了,但此时就出现问题2:
这里通过antd的文件上传组件是封装了请求逻辑的,如何自定义文件上传的接口处理?
问题2的解决: antd文档中提到了,可以使用
customRequest
这个API,覆盖默认的上传行为,可以自定义自己的上传接口实现。
2.antd文件上传组件的方法:customRequest
注意事项:
- 定义
,之前定义customRequest
行为会被覆盖,可以注释掉。action
- 接口响应后,要处理file上传的成功(
)和失败(onSuccess
),还需要改变file的状态(onError
),状态有四类:status
、uploading
、done
、error
removed
customRequest
代码示例如下:
import axios from 'axios'
customRequest (data) {
let { file, onSuccess, onError } = data
const formData = new FormData()
formData.append('file', file)
formData.append('token', 'aiufpaidfupipiu')//随便写一个token示例
axios(
{
method: 'post',
url: 'http://localhost:4785/api/values/PostSingle',
data: formData
}).then((res) => {
if (res.data.sccess) {
file.status = 'done'
onSuccess(res.data, file)
}
}).catch((res) => {
file.status = 'error'
onError(res.data, file)
})
},
三、代码实现
OK,现在到我们实现需求的时候,通过定义
customRequest
来覆写请求逻辑,再通过
cancelToken
来取消请求,这里我们是批量取消所有的文件上传,所以组件data里用了
cancelSourceList
存储
cancelToken
,用于后面弹窗的取消请求实现。代码如下:
HTML:
<template>
<a-upload-dragger name="file" accept=".xls,.xlsx,.csv" :showUploadList="false" :multiple="true"
:before-upload="beforeUpload"
:customRequest="customRequest" @change="handleImportExcelTemp">
</a-upload-dragger>
<status-modal :visible.sync="fileVisible" :status="fileLoadingStatus" :title="fileModalTitle"
@cancel="cancelUpload">
<div>{{fileModalDescribe}}</div>
</status-modal>
</template>
export default {
data() {
return {
//存储axios的cancelToken,用于取消请求
cancelSourceList: [],
}
},
methods: {
customRequest(options) {
let { file, onSuccess, onError } = options
const CancelToken = axios.CancelToken
const source = CancelToken.source()
const formData = new FormData()
formData.append('file', file)
this.cancelSourceList.push(source)
this.fileVisible = true
//importExcelFinalUrl是你的接口url
axios.post(this.importExcelFinalUrl, formData, {
headers: this.tokenHeader,
cancelToken: source.token
}).then((res) => {
file.status = 'done'
//这里onSuccess的第一个参数是接口响应结果,会传到change事件中去
onSuccess(res.data, file)
}).catch((res) => {
file.status = 'error'
onError(res.data, file)
})
},
cancelUpload() {
this.cancelSourceList.forEach(source => {
source.cancel('取消请求')
})
this.cancelSourceList = []
},
// 导入
handleImportExcelTemp(info) {
this.$refs.editableTable.getValues((error, values, notPassedMsg) => {
switch (info.file.status) {
case 'error':
//info.file.response就是上面onSuccess的第一个参数:接口响应结果
if (!!!info.file.response) {
this.$notify['error'].call(this, {
key: 'fileUploadFailedNotificationKey',
message: '文件上传失败',
description: `文件上传失败!`
})
this.setFileLoading(false)
return
}
if (info.file.response.status === 500) {
let data = info.file.response
const token = Vue.ls.get(ACCESS_TOKEN)
if (token && data.message.includes('Token失效')) {
Modal.error({
title: '登录已过期',
content: '很抱歉,登录已过期,请重新登录',
okText: '重新登录',
mask: false,
onOk: () => {
store.dispatch('Logout').then(() => {
Vue.ls.remove(ACCESS_TOKEN)
window.location.reload()
})
}
})
}
}
break
case 'uploading':
break
case 'done':
//处理报错
if (!info.file.response.success) {
this.$notify['error'].call(this, {
key: 'fileUploadFailedNotificationKey',
message: '文件上传失败',
description: `${info.file.name} ${info.file.response.message}.`
})
this.setFileLoading(false)
return
}
//后续逻辑
this.setUploadedList(info.file.response.list)
break
default:
break
}
})
},
}
}