vue文件直传阿里云,有进度条,支持中断取消上传

作为后端开发兼顾前端页面的实现,说实话,遇到的问题绝不会少,但方法总比问题多,坚持努力总会有成果的。

最近项目使用vue的el-upload上传文件,因为后期对文件的加载速度及并发数有较高要求,所以文件需上传至阿里云,在实现过程主要有两个功能点需自己实现,其他均可参考文档

1、实现进度条展示

API文档中进度条的实现是通过上传接口返回的,但我们实际的接口基本很难满足这个数据返回,所以我们必须通过监控请求进度从而实现进度条的展示,主要使用onUploadProgress,具体实现如下(省略全部代码,只只展示相关实现,全部实现在最后展示)

<template>
  <div>
    <!-- 进度条 -->
    <el-progress :percentage="uploadPercentage"></el-progress>
  </div>
</template>

<script>
import axios from 'axios'
// 创建实例
const onUploadProgress = axios.onUploadProgress
export default {
  name: 'FileUpload',
  data(){
    return{
      uploadPercentage: 0
    }
  },
  methods: {
    // 文件上传
    fileUpload(url, data){
      axios.post(url, data, {
        onUploadProgress: progressEvent => {
          if (event.lengthComputable) {
            var percent = Math.floor(event.loaded / event.total * 100);
            if (percent >= 100) {
              // 不给100,避免延迟误导用户用户
              percent = 97;
            }
            this.uploadPercentage = percent;
          }
        }
      }).then(res => {
        this.uploadPercentage = 100;
      }).catch(error => {
      })  
    }
  }
}


 

2、中断上传,如取消文件列表数据或关闭上传页面时,应该撤回上传,不然将造成大量垃圾数据,消耗资源,具体实现与进度条类似,都是利用axios的功能,主要使用CancelToken函数,具体如下:

<script>
import axios from 'axios'
const CancelToken = axios.CancelToken
let uploadCancel
export default {
  name: 'FileUpload',
  data(){
    return{
      uploadPercentage: 0
    }
  },
  methods: {
    // 文件上传
    fileUpload(url, data){
      axios.post(url, data, {
        cancelToken: new CancelToken(function executor(c) {
          // executor 函数接收一个 cancel 函数作为参数
          uploadCancel = c;
        })
      }).then(res => {
        this.uploadPercentage = 100;
      }).catch(error => {
      })  
    }
  }
}

注:两者实现一定要先创建实例

3、组件封装全实现

<template>
  <div>
    <el-upload  class="upload-demo" :id="id" action="" :accept="ext" :limit="countLimit"
      :on-remove="handleRemove" :file-list="fileList" :auto-upload="false" :on-change="onChange"
      :on-exceed="overCountLimit">
        <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
        <el-button style="margin-left: 10px;" size="small" type="success" @click="submitUpload" :disabled="uploadFlag">上传到服务器</el-button>
        <div slot="tip" class="el-upload__tip">{{tip}}</div>
    </el-upload> 
    <!-- 进度条 -->
    <el-progress v-if="progressFlag" :percentage="uploadPercentage"></el-progress>
  </div>
</template>

<script>
import {uploadAliyun } from '@/api/api'
import axios from 'axios'
const CancelToken = axios.CancelToken
// 创建实例
const onUploadProgress = axios.onUploadProgress
let uploadCancel
export default {
  name: 'FileUpload',
  props: {
    id: {
      type: String,
      default: function(){
        return "filePicker";
      }
    },
    //上传提示
    tip: {
      type: String,
      default: function(){
        return "";
      }
    },
    // 文件类型限制
    ext: {
      type: String,
      default: function(){
        return "jpg,jpeg,png,pdf,mp4,avi.mp3";
      }
    },
    //上传个数限制
    countLimit: {
      type: Number,
      default: function(){
        return 5;
      }
    },
    // 文件大小限制
    sizeLimit: {
      type: Number,
      default: function(){
        return 209715200;
      }
    },
  },
  data(){
    return{
      fileList: [],
      tempIndex: 1,
      loadProgress: 0, // 动态显示进度条
      progressFlag: false, // 关闭进度条
      uploadFlag: true,
      uploadPercentage: 0,
      chooseFileFlag: false
    }
  },
  methods: {
    submitUpload() {
      if(this.fileList.length == 0){
        this.$message.error("请选择文件")
        return
      }
      //上传,只能一个一个上传
      this.progressFlag = true
      this.uploadFlag = true
      this.chooseFileFlag = true
      for(let i=0; i<this.fileList.length; i++){
        let pro = new Promise((resolve, rej) => {
          //获取签名
          uploadAliyun({}).then( (res) => {
            resolve(res.data)
          })
        })
        pro.then( success => {
          this.fileUpload(success, this.fileList[i], i)
        })
      }
    },

    // 上传
    fileUpload(data,file, i) {
      let str_start = "website_"
      let filename = file.name
      filename = filename.substring(filename.lastIndexOf("."))
      filename = str_start +new Date().getFullYear()+new Date().getMonth() + '_' + new Date().getTime() + filename
      let fileUrl = data.dir+"/"+filename
      let allUrl = data.host + data.dir + "/"+filename
      var ossData = new FormData()
      // 添加配置参数
      ossData.append('OSSAccessKeyId', data.accessid)
      ossData.append('policy', data.policy)
      ossData.append('signature', data.signature)
      ossData.append( "key",fileUrl)
      ossData.append('success_action_status', 200) // 指定返回的状态码
      ossData.append('file', file.raw, file.name)
      axios.post(data.host, ossData, {
        headers: {
          "Content-Type": "multipart/form-data",
          "Access-Control-Allow-Origin": "*"
        },
        cancelToken: new CancelToken(function executor(c) {
          // executor 函数接收一个 cancel 函数作为参数
          uploadCancel = c;
        }),
        onUploadProgress: progressEvent => {
          this.updateProgress(progressEvent)
        }
      }).then(res => {
        if (res.status == 200) {  //上传成功 上传的路径就是要使用的路径 
          if (this.fileList){
            this.fileList[i].url = allUrl;
            this.fileList[i].status = 'success'
            this.fileList[i].percentage = 100
            this.progressFlag = false 
            this.uploadPercentage = 0
            this.chooseFileFlag = false
          }
        }
      }).catch(error => {
        var tempList = this.fileList.filter(item => item.status != 'success')
        if(tempList.length > 0){
          this.$message.error("上传失败,请重新上传!")
          this.uploadFlag = false
        }else{
          this.uploadFlag = true
        }
        this.progressFlag = false 
        this.uploadPercentage = 0
        this.chooseFileFlag = false
      })    
          
    },
    // 删除文件
    handleRemove(file, fileList) {
      this.uploadFlag = true
      for(let i=0; i<this.fileList.length; i++){
        if(file.uid === this.fileList[i].uid){
          this.fileList.splice(i, 1)
        }
        if(file.status != 'success'){
          this.uploadFlag = true
          this.progressFlag = false
          uploadCancel()
        }
      }
    },
    // 获取上传成功的list
    getFileList(){
      var returnList = []
      for(let i=0; i<this.fileList.length; i++){
        if(this.fileList[i].status != 'success'){
          this.$message({
            message: '有文件未上传,请先上传服务器',
            type: 'error'
          })
          return
        }
        returnList.push(this.fileList[i])
      }
      return returnList
    },
    //文件超出个数限制时的钩子
    overCountLimit(files, fileList){
       if(this.countLimit == 1){
         if(this.chooseFileFlag){
          this.$message({
            message: '文件上传中,请稍后选择文件',
            type: 'error'
          })
          return
        }
        for(let i=0; i<files.length; i++){
          let file = files[i]
          if(this.ext.indexOf(file.name.substring(file.name.lastIndexOf('.')+1)) < 0){
            this.$message({
              message: '文件格式错误,请选择文件',
              type: 'error'
            })
            return
          } else if(file.size > this.sizeLimit){
            this.$message({
              message: '大小不符',
              type: 'error'
            })
            return
          }
          this.fileList = []
          this.fileObject = {}
          //如果是限制文件个数为1的话,直接替换原来的文件此时往需要上传的文件列表中添加文件
          let tempFile = {
            uid: Date.now() + this.tempIndex++,
            name: files[0].name,
            type: files[0].type,
            fileSize: files[0].size,
            status: "ready",
            percentage: 0,
            raw: files[0]
          }
          this.fileObject = tempFile
          this.fileList.push(this.fileObject)
          this.uploadFlag = false
        }
      }else{
        this.$message({
          message: '已经达到上传文件限制数量',
          type: 'error'
        });
      }
    },
    //文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
    onChange(file, fileList){
      if(this.chooseFileFlag){
        for(let i=0; i<fileList.length; i++){
          if(file.uid === fileList[i].uid){
            fileList.splice(i, 1)
          }
        }
        this.$message({
          message: '文件上传中,请稍后选择文件',
          type: 'error'
        })
        return
      }
      if(this.ext.indexOf(file.name.substring(file.name.lastIndexOf('.')+1)) < 0){
        for(let i=0; i<fileList.length; i++){
          if(file.uid === fileList[i].uid){
            fileList.splice(i, 1)
          }
        }
        this.$message({
          message: '文件格式错误,请选择文件',
          type: 'error'
        })
        return
      }else if(file.size > this.sizeLimit){
        for(let i=0; i<fileList.length; i++){
          if(file.uid === fileList[i].uid){
            fileList.splice(i, 1)
          }
        }
        this.$message({
          message: '文件超过指定大小',
          type: 'error'
        });
        return
      }else if(this.fileList.length > 0 && this.fileList[this.fileList.length -1].status != 'success'){
        for(let i=0; i<fileList.length; i++){
          if(file.uid === fileList[i].uid){
            fileList.splice(i, 1)
          }
        }
        this.$message({
          message: '有文件未上传,请先上传列表中的文件',
          type: 'error'
        });
        return
      }
      this.fileList.push(file)
      this.uploadFlag = false
    }, 
    // 取消上传任务
    cancelUpload(){
      let tempFileList = this.fileList.filter(item => item.status != 'success')
      if(tempFileList.length > 0 && this.uploadFlag){
        uploadCancel()
      }
    },
    // 上传进度
    updateProgress(event) {
      // 设置进度显示
      if (event.lengthComputable) {
        var percent = Math.floor(event.loaded / event.total * 100);
        if (percent >= 100) {
          percent = 97;
        }
        this.uploadPercentage = percent;
      }
    }

  },
  mounted(){
  }
}
</script>

    

该组件是基于vue-cli3实现的,直传阿里云先通过请求获取了相关签名认证信息,后端获取签名不明白可查看相关文档。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值