需求:使用element的upload组件,上传文件到阿里云服务器。上传前,先请求自己后台的接口,返回阿里云上传相关的凭证key等信息后才能正确上传。
那如何解决呢?
答案是element上传组件中的 http-request 自定义上传。

<el-upload
  ref="upload"
  class="upload-demo"
  action
  :http-request="handleUpload"
  :auto-upload="false"
  multiple
  :on-exceed="handleExceed"
  :file-list="fileList"
  :on-change="onChangeFile"
  :on-remove="handleRemove"
  :on-preview="handlePreview"
  :before-remove="beforeRemove"
>
  <el-button slot="trigger" :disabled="type !== 'view' ? false : true" size="small" type="primary">选取文件</el-button>
  <el-button :disabled="type !== 'view' ? false : true" style="margin-left: 10px;" size="small" type="success" @click="submitUpload">上传到服务器</el-button>
</el-upload>

说明一下:
action设置为空,因为我们需要自己来控制上传。
:http-request=“handleUpload” 我们自己定义的上传方法。具体见后面。
:auto-upload=“false” 我关掉了选择文件后自动上传,改为手动触发。
:file-list=“fileList” 上传文件的文件列表
:on-change=“onChangeFile” 选择文件或是上传成功后都会触发,不仅仅是选择文件后触发。
其他看文档。没有什么好说的。

我在data中定义了两个文件列表相关的

data() {
return {
// 真实的附件列表,提交表单的时候,真实数据
      realFileList: [],
      // 上传文件列表,el-upload使用,临时保存数据。
      fileList: [
        // { name: 'aaa', url: 'image/196b62fd-fc70-4634-9bca'}
      ]}
}

fileList是给upload组件绑定使用的,没有上传的文件都会在里面
realFileList是上传成功后传递后台的文件列表数据。

为啥要分开呢? 因为fileList中,不好控制。选择文件后,没有上传,fileList中是没有的。上传成功后,fileList才会有数据。点击删除的时候,已经上传的fileList还是会保留数据,
这个原因导致我需要维护两个realFileList和fileList属性。

定义的methods中相关的上传函数

methods: {
    // 选择上传文件,上传成功后都会出发
    onChangeFile(file, fileList) {
      console.log('选择文件', file, fileList)
      if (file.response) {
        // this.fileList.push(file.response)
        // 上传成功后,把成功的结果添加到realFileList中
        this.realFileList.push(file.response)
      }
    },
        // 自定义上次操作
    handleUpload(op) {
      // console.log('上传', op)
      // op是element返回的上传相关的东西,通过它可以操作很多东西,进度条什么的,可以打印出来看,有很多回调方法,
      // 这里的upload是我自己的封装的上传处理函数
      myupload(
        op.file,
        res => {
          let temp = {
            name: res.attachment,
            url: res.aliyunAddress
          }
          // 成功后的回调,把结果返回,并且把上传列表的状态改变,打上成功的√
          op.onSuccess(temp)
        },
        err => {
          console.log(err)
        },
        res => {
        // 处理进度条的东西,把进度数据传进去实现进度条走动,传参res应该是这样的类型{percent: 48}
          op.onProgress(res)
        }
      )
    },
        // 监听移除文件列表
    handleRemove(file, fileList) {
      // 删除的是未上传的文件
      console.log('删除文件', file, fileList)
      if (file.status === 'success') {
        this.realFileList.map((v, i, arr) => {
          if (v.url === file.url || v.url === file.response.url) {
            arr.splice(i, 1)
          }
        })
      }
    },
        // 预览(下载文件)
    handlePreview(file) {
      // console.log(file)
      // 没有上传成功,不可以预览
      if (file.status !== 'success') {
        return
      }
      let fileurl = file.url || file.response.url // 前者是获取详情回来的文件,后者是刚刚新上传成功的文件
      window.open(fileurl )
    },
    handleExceed(files, fileList) {
      this.$message.warning(`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`)
    },
    beforeRemove(file) {
      return this.$confirm(`确定移除 ${file.name}?`)
    }

upload函数是我自定义的,使用axios实现的。具体内容如下:

// myupload.js
/*
 * @Description: 附件上传
 * @Autor: bolingsun
 * @Date: 2020-09-09 10:03:43
 */
import { v4 as uuidv4 } from 'uuid'
// import { fetchUploadPara } from '@/api/upload'
import axios from 'axios'
let basePath
if (process.env.NODE_ENV === 'production') {
  basePath = window.init_params.basePath
} else {
  basePath = '/portal/pages/vue/undefined'
}
/**
 * @description: 文件附件上传
 * file: 文件raw对象
 * successCallback: 成功的回调函数
 * errCallBack: 错误的回调函数
 * progressCallback: 上传进度的回调函数
 */
const myupload= function(file, successCallback = new Function(), errCallBack = new Function(), progressCallback = new Function()) {
  let fileName = file.name
  // options = options || {}
  axios({
    method: 'get',
    url: basePath + '/aliyun/get',
    params: {
      dir: 'image'
    }
  })
    .then(res => {
      let obj = res.data.data
      let config = {}
      config.host = obj['host']
      config.policyBase64 = obj['policy']
      config.accessid = obj['accessId']
      config.signature = obj['signature']
      config.expire = parseInt(obj['expire'])
      config.callbackbody = obj['callback']
      config.dir = obj['dir']
      let fd = new FormData(),
        uuid = uuidv4(),
        key = config.dir + uuid
      fd.append('key', key)
      fd.append('success_action_status', '200')
      fd.append('x-oss-object-acl', 'public-read')
      fd.append('x-oss-meta-fullname', fileName)
      fd.append('OSSAccessKeyId', config.accessid)
      fd.append('policy', config.policyBase64)
      fd.append('signature', config.signature)
      fd.append('success_action_status', '200')
      fd.append('file', file)
      if (config.host.indexOf('http:') > -1) {
        var protocol = window.location.protocol || 'http:'
        var subUrl = config.host.substring(5, config.host.length)
        config.host = protocol + subUrl
      }
      axios({
        url: config.host,
        method: 'POST',
        data: fd,
        processData: false,
        cache: false,
        contentType: false,
        onUploadProgress: function(progressEvent) {
          if (progressEvent.lengthComputable) {
            let percent = ((progressEvent.loaded / progressEvent.total) * 100) | 0
            progressCallback({ percent: percent })
          }
        }
      })
        .then(() => {
          let size = file.size > 1000000 ? parseFloat(file.size / 1000000).toFixed(2) + 'M' : parseFloat(file.size / 1000).toFixed(2) + 'KB'
          successCallback({
            attachment: fileName,
            aliyunAddress: key,
            size: size
          })
        })
        .catch(err => {
          errCallBack(err)
        })
    })
    .catch(err => {
      errCallBack(err)
      // console.log(err)
    })
}
export default myupload

具体内容大家就根据自己公司的情况去写具体的逻辑了。
我大概解释一下。

myupload= function(file, successCallback = new Function(), errCallBack = new Function(), progressCallback = new Function()) {}

四个参数, file传入的是文件对象,他上面有二进制数据的。successCallback 是成功后的回调,errCallBack 是失败后的回调,progressCallback 是进度条的回调。
上传进度条,相关的是这个内容:

axios({
        url: config.host,
        method: 'POST',
        data: fd,
        processData: false,
        cache: false,
        contentType: false,
        // 这个是axios关于上传进度的配置,progressEvent是原生的上传进度对象
        onUploadProgress: function(progressEvent) {
          if (progressEvent.lengthComputable) {
            let percent = ((progressEvent.loaded / progressEvent.total) * 100) || 0
            progressCallback({ percent: percent })
          }
        }
      })

在上传的过程中, 会触发progressEvent事件,我们把进度通过progressEvent.loaded / progressEvent.total计算出来后,通过progressCallback回调函数,把结果返回回去。

然后在element-ui upload的地方,使用。
op.onProgress({ percent: percent }) 这样就可以顺利显示进度条啦。

补充说明一下关于element-ui upload的相关属性:

onChangeFile 这个是,你选择了两个文件,就会触发两次,上传成功了,也会触发。

handleUpload 这个是,你选择了两个文件,两个文件会先后依次调用这个方法。选择几个文件,点击上传操作,就会触发调用几次这个函数

handleRemove 这个是,你删除了没有上传成功的文件,就是删除了,但是,你删除上传成功了的文件,列表显示是不见了,但是 绑定fileList内,还是有数据的,没有真的删除掉。

还有一点,这个上传的本质,就是new FormData 实例化一个表单对象,把文件二进制数据,以表单的形式提交而已。就这么回事。