Vue实现文件上传和oss上传

调后端接口的方式(带进度条和取消上传功能)

在这里插入图片描述

代码实现

这里我只写了单一文件上传的demo,并且只限制了文件大小不能超过5M。如果想限制上传的文件类型,可自行在input标签中进行设置,如果想要实现多个文件一起上传,先要设置input标签属性multiple=“multiple”,然后利用循环将每一个文件数据存入到formData中,最后作为参数传给服务端进行处理。

因为个人习惯原因,我先对axios进行了封装,创建service.js文件,代码如下:

import axios from "axios"; //引入axios
const instance = axios.create({
  timeout: 300000,
});
// let baseURL = process.env.BASE_URL.toString();
//这里根据自己项目接口的位置自行设定
let baseURL = "http://1xx.1xx.1xx.1xx/hyr";
if (process.env.NODE_ENV == "production") {
  baseURL = "http://1xx.1xx.1xx.1xx/hyr";
}
instance.defaults.baseURL = baseURL; //baseURL用于自动切换本地环境接口和生产环境接口
instance.defaults.headers.post["Content-Type"] ="application/x-www-form-urlencoded;charset=UTF-8";
instance.defaults.headers.post["Access-Control-Allow-Origin"] = "*";
instance.defaults.withCredentials = false; // 携带cookie

//上传附件axios接口封装
const upload = {
  uploadFile(url, payload, cancelToken, cd) {
    return instance({
      url: url,
      method: "post",
      data: payload,
      onUploadProgress: function(progressEvent) {
        if (progressEvent.lengthComputable) {
          cd(progressEvent);
        }
      },
      cancelToken: cancelToken,
    });
  },
}

export {
  upload,
  axios,
}

进度条的实现: 主要依靠axios中提供的onUploadProgress函数,该函数提供了文件已上传部分的大小progressEvent.loaded和文件总大小progressEvent.total,利用这两个数据我们就可以计算出已经上传文件的进度。

在创建一个vue文件,将上述封装方法引入,代码如下:

<template>
  <div class="upload-file-box">
    <div class="input-line">
      <div class="input-title">
        <span class="name">附件上传:</span>
      </div>
      <label v-if="loading">
        <div class="ban-button icon-btn">
          <span>附件上传</span>
        </div>
      </label>
      <label for="files" v-else>
        <div class="upload-btn icon-btn">
          <span>附件上传</span>
        </div>
      </label>
      <input type="file" @change="changeFiles" class="files" id="files" />
    </div>
    <div class="limit-info">
      支持文件格式:常用文件格式,单个文件不能超过5Mb
    </div>
    <div class="input-line" v-show="loading">
      <div class="input-title">
        <span class="name">上传状态:</span>
      </div>
      <div class="loading-box">
        <div class="current-loading" :style="{ width: percent + '%' }"></div>
      </div>
      <div class="percent-val">{{ Math.round(percent) }}%</div>
      <div class="cancel-btn" @click="cancelUpload">取消</div>
    </div>

    <div class="file-state" v-show="loading">
      <div class="file-name">
        <span>{{ originalName }}</span>
      </div>
      <div class="state">
        <span>正在上传</span>
      </div>
    </div>

    <div class="file-state" v-show="complete">
      <div class="file-name">
        <a :href="filePath" target="_blank">{{ originalName }}</a>
      </div>
      <div class="state" v-show="!!filePath">
        <span>已完成</span>
      </div>
    </div>
  </div>
</template>

<script>
  import { upload } from "./service.js";
  import axios from 'axios'
  export default {
    data() {
      return {
        source: null,
        fileName: "",
        percent: 0,
        originalName: "",
        uploadState: false,
        filePath: "",
        loading: false,
        complete: false,
      }
    },
    methods: {
      changeFiles(e) {
        let files = e.srcElement.files;
        if (files && files.length) {
          if (files[0].size / 1024 / 1024 > 5) {
            alert("单个文件不能超过5MB");
            document.getElementById("files").value = null;
            return;
          }
          this.originalName = files[0].name;
          this.source = axios.CancelToken.source();
          let formData = new FormData();
          formData.append("file", files[0]);
          this.loading = true;
          this.complete = false;
          upload
            .uploadFile(
              "api/common/file/upload",
              formData,
              this.source.token,
              (progressEvent) => {
                let completeVal =
                  (progressEvent.loaded / progressEvent.total) * 100  || 0;
                this.percent = completeVal;
                this.uploadState = false;
              }
            )
            .then((res) => {
              if (res.data.status) {
                this.percent = 100;
                this.uploadState = true;
                this.filePath = res.data.resultBody.filePath;
                this.loading = false;
                this.complete = true;
              }else{
                alert("上传失败");
                this.loading = false;
                this.complete = false;
              }
            }).catch((thrown)=>{
              if(axios.isCancel(thrown)){
                alert("用户取消上传");
                this.loading = false;
                this.complete = false;
              }else{
                alert("其它错误异常");
              }

            });
        }
        document.getElementById("files").value = null;
      },
      cancelUpload() {
        //取消上传
        this.source.cancel("Operation canceled by the user.");
      },
    },
  }
</script>
<style lang="scss" scoped>
  .upload-file-box {
    padding-top: 30px;
    padding-left: 20px;
    .input-line {
      height: 40px;

      .input-title {
        float: left;
        line-height: 40px;
        font-size: 14px;
        text-align: right;
        width: 80px;

        .text {
          color: #666;
        }
      }

      .files {
        display: none;
      }

      .input {
        float: left;
        width: 400px;
        margin-left: 16px;
      }

      .icon-btn {
        margin-top: 5px;
        margin-left: 16px;
        float: left;
        height: 30px;
        line-height: 30px;
        padding-right: 10px;
        padding-left: 10px;
        background-color: #0099ff;
        border-radius: 5px;
        color: #fff;
        font-size: 14px;
        cursor: pointer;
        transition: all 0.35s;
      }

      .upload-btn {
        &:hover {
          opacity: 0.8;
        }
      }

      .ban-button {
        cursor: not-allowed;
        background-color: #eeeff3;
      }

      .loading-box {
        margin-top: 15px;
        float: left;
        width: 270px;
        height: 10px;
        background-color: #f6f6f6;
        border-radius: 5px;
        margin-left: 15px;

        .current-loading {
          height: 10px;
          border-radius: 5px;
          background-color: #3399cc;
          /*width: 50%;*/
        }
      }

      .percent-val {
        line-height: 40px;
        width: 60px;
        text-align: center;
        font-size: 14px;
        color: #666;
        float: left;
      }

      .cancel-btn {
        line-height: 40px;
        font-size: 14px;
        color: #999;
        text-decoration: underline;
        cursor: pointer;
        user-select: none;

        &:hover {
          color: #3399CC;
        }
      }

      .del-btn {
        line-height: 40px;
        font-size: 14px;
        color: #999;
        text-decoration: underline;
        cursor: pointer;
        user-select: none;

        &:hover {
          color: #3399CC;
        }
      }
    }

    .limit-info {
      color: #999;
      font-size: 14px;
      padding-left: 96px;
      line-height: 34px;
      height: 34px;
    }

    .file-state {
      width: 270px;
      margin-left: 95px;
      overflow: hidden;

      .file-name {
        float: left;
        width: 210px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        font-size: 14px;
        color: #999;

        a {
          color: #999;
          font-size: 14px;
          text-decoration: none;

          &:hover {
            color: #3399cc;
          }
        }
      }

      .state {
        float: right;
        font-size: 14px;
        color: #1db396;
      }
    }
  }
</style>

结束语
以上代码复制粘贴到自己的vue项目中就能够正常运行,如有错误或需要改进的地方还请与我联系,我将及时进行改正。

转载自 简书:FTD止水

下一步考虑 vue文件上传的多文件的进度条如何去实现

调oss上传的方式(带进度条)

封装oss方法

const OSS = require('ali-oss')

const OSSConfig = {
  uploadHost: 'http://xxxxxx.oss-cn-shenzhen.aliyuncs.com', //OSS上传地址
  folder: process.env.VUE_APP_OSS_FOLDER, // 上传的文件夹地址 例如 test/file/xxx/、test/img/xxx/
  ossParams: {
    region: 'oss-cn-shenzhen',
    success_action_status: '200', // 默认200
    accessKeyId: 'LxxxxxxJxxxxxxaFoxxxxxxD',
    accessKeySecret: 'JxxxxxxPxxxxxxhZTxxxxxxFAxxxxxxv',
    bucket: 'xxxxxx',
  },
}

// 随机数函数
function random_string(len) {
  len = len || 32
  var chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
  var maxPos = chars.length
  var pwd = ''
  for (let i = 0; i < len; i++) {
    pwd += chars.charAt(Math.floor(Math.random() * maxPos))
  }
  return pwd
}

// 直接调取oss函数,返回URL结果
function uploadOSS(file) {
  return new Promise(async (resolve, reject) => {
    const fileName = `${OSSConfig.folder}${random_string(6)}_${file.name}`
    let client = new OSS({
      region: OSSConfig.ossParams.region,
      accessKeyId: OSSConfig.ossParams.accessKeyId,
      accessKeySecret: OSSConfig.ossParams.accessKeySecret,
      bucket: OSSConfig.ossParams.bucket,
      secure: true
    })
    const res = await client.multipartUpload(fileName, file)
    if (res.name) {
      resolve({
        fileName: file.name,
        url: `${OSSConfig.uploadHost}/${fileName}`
      })
    } else {
      reject('OSS上传失败')
    }
  })
}

// 用于上传文件进度条的oss函数
function client() {
  //后端提供数据
  return new OSS({
    region: OSSConfig.ossParams.region,
    accessKeyId: OSSConfig.ossParams.accessKeyId,
    accessKeySecret: OSSConfig.ossParams.accessKeySecret,
    bucket: OSSConfig.ossParams.bucket,
    secure: true
  })
}

export {
  OSSConfig,
  random_string,
  uploadOSS,
  client
}

页面中使用

<template>
  <div class="content-box">
    <div class="container">
      <div class="title">
        点击上传图像(支持image/jpg,image/jpeg,image/png,image/gif格式图片且大小不能超过10MB)
      </div>
      <el-upload
        :http-request="Upload"
        :data="dataObj"
        :multiple="false"
        :show-file-list="false"
        class="image-uploader"
        drag
        action=""
      >
        <i class="el-icon-upload" />
        <div class="el-upload__text">
          将图片拖到此处,或
          <em>点击上传</em>
        </div>
      </el-upload>
      <el-progress v-if="isImportLoading" :percentage="progress" color="#61C5C1"></el-progress>
      <img :src="imgSrc" alt="" style="width:300px;height:300px" v-if="imgSrc">
    </div>
  </div>
</template>

<script>
import { OSSConfig, client , random_string } from '@/utils/ossFile'

export default {
  props: {},
  data() {
    return {
      // dataObj: {}, // 存签名信息
      progress: 0, //进度条
      isImportLoading: false,
      imgSrc: ''
    }
  },
  created() {},
  methods: {
    // 是为了获取Ali配置信息的,但是封装ossFile 时已写好配置,就不需要了
    // beforeUpload() {
    //   return new Promise((resolve, reject) => {
    //     //从后台获取第一步所需的数据
    //     getAliToken()
    //       .then(response => {
    //         this.dataObj = response.data
    //         resolve(true)
    //       })
    //       .catch(err => {
    //         console.log(err)
    //         reject(false)
    //       })
    //   })
    // },
    Upload(file) {
      const that = this
      //判断扩展名
      const lastName= file.file.name.lastIndexOf('.')
      const expandedName = file.file.name.substring(lastName + 1)
      const typeNames = ['jpg', 'jpeg', 'webp', 'png', 'bmp']
      if (typeNames.indexOf(expandedName) < 0) {
        this.$message.error('不支持的格式!')
        return
      }
      async function multipartUpload() {
        // 上传的路径地址 + 随机数 + 文件名称
        const fileName = process.env.VUE_APP_OSS_FOLDER + random_string(6) + file.file.name
        //定义唯一的文件名
        // client 是第一步中的 client
        // progress 函数就是进度条回调的函数,提供进度条数据
        client().multipartUpload(fileName, file.file, {
            progress: function (p) {
              that.isImportLoading= true
              //获取进度条的值
              // console.log(p)
              that.progress = parseInt(p * 100)
            }
          })
          .then(result => {
            //下面是如果对返回结果再进行处理,根据项目需要
            that.isImportLoading= false
            console.log(result)
            // http://xxxxxx.oss-cn-shenzhen.aliyuncs.com/xxx/xxx/rjdpXG_图片测试1.jpg 
            that.imgSrc = OSSConfig.uploadHost + fileName
          })
          .catch(err => {
            console.log('err:', err)
          })
      }
      multipartUpload()
    }
  }
}
</script>

<style lang="scss" scoped>

</style>

elementUI的拖拽上传,点击导入再调接口上传,单文件且覆盖上次文件(并加loading效果)

在这里插入图片描述
样式还没有调整,姑且先看实现效果吧

主要代码我抽离处理,不过多解释

	<el-upload
          class="upload-demo-xls"
          drag
          ref="speechDemoUpload"
          action=""
          :file-list="fileList"
          :auto-upload="false"
          :show-file-list="false"
          accept=".xls,.xlsx,.csv"
          :on-change="handleChange"
        >
          <!-- <i class="el-icon-upload"></i> -->
          <div class="el-upload__text" v-if="!uploadStatus">将文件拖到此处,或<em>点击上传</em></div>
          <div class="el-upload__text" v-else>
            {{ this.file && this.file.name }}
            <em>点击上传</em>
          </div>
        </el-upload>


	// 这里封装的代码和上面加载进度条的封装函数一样
	import { upload, service } from '@/utils/request'
	
	this.fileList = []
	this.file = null

	// 上传的文件改变时(覆盖原来的文件)
    handleChange(file, fileList){
      // console.log(file);
      let extension = file.raw.name.substring(file.raw.name.lastIndexOf(".") + 1);
      let size = file.size / 1024 / 1024;
      // let size = file.size / 1024;
      // console.log(extension, extension.toLowerCase() !== "xlsx");
      if (!['xlsx','xls','cvs'].includes(extension.toLowerCase())) {
        this.$message.warning("文件格式不正确,请上传xls / xlsx / csv格式");
        return false;
      }
      if (size > 10) {
        this.$message.warning("文件过大,请进行拆分后分多次上传");
        return false
      }
      // console.log(file.raw, fileList);
      if (fileList.length > 0) {
        this.fileList = [fileList[fileList.length - 1]]  // 这一步,是展示最后一次选择的csv文件
        this.file = this.fileList[0].raw
      }
      // console.log(this.file);
      this.uploadStatus = true
    },
    async xlsDemoExport(){
      if (!this.file) {
        return this.$message.error('请上传文件')
      }
      // await this.$refs.speechDemoUpload.submit()
      const formData = new FormData()
      formData.append('file', this.file)
      //  调用上传接口...
      this.$loading.show()
      // `${process.env.VUE_APP_BASE_BRAND_API}/v1/inspectionSpeechArt/importExcel
      upload.uploadFile('http://10.44.62.108:31009/v1/inspectionSpeechArt/importExcel', formData).then(res => {
        console.log(res);
        this.exportStatus = true
        this.tableData = res.item.failList.map(item => {
          let obj = {}
          obj.lineNum = item.lineNum
          obj.failReason = item .failReason
          obj.projectCode = item. projectCode
          return obj
        })
        this.failNum  = res.item.failNum
        this.successNum = res.item.successNum
        this.$loading.hide()
      }).catch((error) => {
        console.log(error);
        this.$message.error('上传失败,请稍后再试或联系IT解决')
        this.$loading.hide()
      })
    },

// 样式调整
<style lang="scss" scoped>
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
  transition: none;
}
::v-deep .el-list-enter,
::v-deep .el-list-leave-active {
  opacity: 0;
}
::v-deep .el-upload-list {
  height: 40px;
}
</style>
Logo

前往低代码交流专区

更多推荐