一文讲透antd文件上传,如何实现取消上传功能

2,178 阅读2分钟

一、需求

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

在网上找了很多资料,没有现成的代码给我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:

getcancelToken放置在第二个参数的对象里面,postcancelToken放置在第三个参数对象里面

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

注意事项:

  1. 定义customRequest,之前定义action行为会被覆盖,可以注释掉。
  2. 接口响应后,要处理file上传的成功(onSuccess)和失败(onError),还需要改变file的状态(status),状态有四类:uploadingdoneerrorremoved

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>

JS部分:

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
          }
        })
      },
     }
  }

四、后续封装

现在只是实现了单独的一个需求,但这个取消接口还是需要封装一下的。 建议看看这篇文章,作者用vuex做了cancelToken的存储,使用更方便。 blog.csdn.net/weixin_4220…

参考文档:blog.csdn.net/qq_37866866…