美文网首页
vue +element-ui 实现的oss web直传文件

vue +element-ui 实现的oss web直传文件

作者: 好名字都让你们用了 | 来源:发表于2021-07-12 14:26 被阅读0次
    简述: 后台系统基本的上传功能,oss 的web直传, 因oss 获取音视频时长需要收费,所以采用的前端本地获取上传音视频资源的时长,并配了上传成功音视频预览功能, oss 直传官网文档

    1:oss上传流程,需要拿到后端返回的oss 签名信息

    // 获取后端返回的签名信息
    export function client(data) {
      //data后端提供数据
      return new OSS({
        region: data.region,
        accessKeyId: data.Credentials.AccessKeyId,
        accessKeySecret: data.Credentials.AccessKeySecret,
        stsToken: data.Credentials.SecurityToken,
        bucket: data.bucket
      })
    }
    

    2:主要上传组件ossUpload代码,用element 的自定义上传方法http-request ,

    <template>
      <div class="ossUpload">
        <el-upload
          ref="upload"
          action
          :class="{ has: hideUploadBtn, hidden: ModifyStyle }"
          :http-request="Upload"
          :before-upload="beforeAvatarUpload"
          :on-preview="handlePreview"
          :before-remove="beforeRemove"
          :on-remove="handleRemove"
          :on-exceed="handleExceed"
          :on-change="uploadChange"
          :list-type="listType"
          :limit="limit"
          :file-list="fileList"
          :accept="accept"
          :on-error="uploadFail"
          :on-success="uploadSuccess"
          :multiple="multiple"
        >
          <template #trigger>
            <i v-if="listType == 'picture-card'" class="el-icon-plus"></i>
            <el-button v-else type="primary">{{ text }}</el-button>
          </template>
          <el-button
            v-if="(type == 'audio' || type == 'video') && value != ''"
            type="primary"
            class="preview"
            @click="preview"
            >预览</el-button
          >
        </el-upload>
        <!-- 上传进度条 -->
        <el-progress
          v-show="showProgress"
          :text-inside="false"
          :stroke-width="5"
          :percentage="progress"
        ></el-progress>
        <!-- 音频预览 -->
        <el-dialog v-model="dialogAudioVisible" width="560px">
          <div v-if="dialogAudioVisible" class="audio-con">
            <audio-node :node="audio_info" image-height="250px"></audio-node>
          </div>
        </el-dialog>
        <!-- 视频预览 -->
        <el-dialog v-model="dialogVideoVisible" width="800px">
          <div v-if="dialogVideoVisible" class="video-con">
            <video class="videoPrview" controls controlsList="nodownload" preload="load">
              <source :src="video_url" type="video/mp4" />
            </video>
          </div>
        </el-dialog>
        <!-- 视频获取时长 -->
        <video id="myVideo" class="video" controls preload="load" :src="getVideoInfo"> </video>
        <!-- 音频获取时长 -->
        <audio id="myAudio" class="audio" preload="load" :src="getAudioInfo"> </audio>
      </div>
    </template>
    <script>
      import {
        client,
        isArrayFn,
        formatTime,
        formatMin,
        randomString,
        loadPreviewPlugin,
        previewImage
      } from '@/utils'
      import AudioNode from '@/components/viewComponent/audio.vue'
      import { createAjax } from '@/utils/ajax'
      import { deleteParame } from '@/utils'
      // import SparkMD5 from 'spark-md5'
      const Ajax = createAjax('common', 'v1', 'restapi')
      export default {
        name: 'Upload',
        components: {
          AudioNode
        },
        props: {
          limit: {
            type: Number,
            default: 1
          },
          text: {
            type: String,
            default: '选择图片'
          },
          listType: {
            type: String,
            default: 'picture-card'
          },
          // 上传文件大小
          measure: {
            type: String,
            default: '500'
          },
          type: {
            type: String,
            default: 'image'
          },
          accept: {
            type: String,
            default: 'image/*'
          },
          value: {
            type: [String, Object],
            default: () => {}
          },
          oss: {
            type: String,
            default: 'oss'
          },
          address: {
            type: String,
            default: 'admin/images'
          },
          // 音视频时长
          duration: {
            type: [String, Number],
            default: () => {}
          },
          name: {
            type: [String, Number, Object],
            default: () => ({})
          },
          unClick: {
            type: Boolean,
            default: false
          },
          // 上传是否为歌词
          isLyc: {
            type: Boolean,
            default: false
          },
          // 业务名
          business: {
            type: String,
            default: 'lesson'
          },
          // 图片尺寸
          imageSize: {
            type: [String, Object],
            default: () => {}
          },
          modality: {
            type: String,
            default: 'form'
          },
          isShow: {
            type: Boolean,
            default: false
          },
          multiple: {
            type: Boolean,
            default: false
          },
          check: {
            type: Boolean,
            default: false
          },
          imageShow: {
            type: Boolean,
            default: true
          }
        },
        emits: [
          'update:value',
          'update:size',
          'update:duration',
          'update:name',
          'removeChange',
          'uploadChange',
          'update:num'
        ],
        data() {
          return {
            // reviewShow: this.isShow,
            urlClone: this.value,
            videoName: this.name,
            fileList: [], //文件列
            showProgress: false, //进度条的显示
            dataObj: {}, //存签名信息
            progress: 0, //进度条数据
            id: this.$route.params.id,
            code: 0,
            message: '',
            fileCurrentLen: 0,
            getVideoInfo: '',
            getAudioInfo: '',
            dialogAudioVisible: false,
            dialogVideoVisible: false,
            uploadInfo: {},
            audio_info: {},
            err: '',
            isDelete: false,
            video_url: '',
            isSuccess: false
          }
        },
        computed: {
          isShowHasClass() {
            // 多张不去掉上传按钮
            if (this.multiple) return false
            // 单张判断是否已生成url
            if (!this.value) return false
            return this.value.length || this.urlClone.length
          },
          hideUploadBtn() {
            return this.fileCurrentLen >= this.limit && this.type === 'image' && this.imageShow
          },
          // 控制表格  表单 上传样式
          ModifyStyle() {
            return this.type === 'image' && this.modality == 'table'
          }
        },
        watch: {
          value(nVal) {
            // 监听父组件清空url的时候清空文件
            if (nVal) return
            this.clearFiles()
          }
        },
        beforeMount() {
          loadPreviewPlugin()
          this.initFieldList()
        },
    
        methods: {
          preview() {
            this.type == 'video' ? (this.dialogVideoVisible = true) : (this.dialogAudioVisible = true)
          },
          getImageList(fileList) {
            return fileList.map((item) => {
              if (item.status != 'success') return ''
              if (item.response) {
                // 新上传的图
                return deleteParame(item.response)
              } else {
                // 以前上传的图
                return deleteParame(item.url)
              }
            })
          },
          // 清空已上传的文件列表
          clearFiles() {
            this.$refs.upload.clearFiles()
          },
          // 文件超出个数限制时的钩子
          handleExceed(files, fileList) {
            this.$message.warning(`每次只能上传 ${this.limit} 个文件`)
          },
          // 点击文件列表中已上传的文件时的钩子
          handlePreview(file) {
            // 图片预览
            if (this.unClick || this.type != 'image') return
            previewImage(file.path || file.url)
            return false
          },
          uploadChange(file, fileList) {
            this.fileCurrentLen = fileList.length
            this.$emit('uploadChange', file)
          },
          uploadFail(err) {
            this.$message.error(`${JSON.parse(err.message).message},请重新上传`)
          },
    
          // 删除文件之前的钩子
          beforeRemove(file, fileList) {
            this.fileCurrentLen = fileList.length
            // this.showProgress = false
            const that = this
            if (
              (!this.isDelete && this.type == 'audio') ||
              this.type == 'video' ||
              this.type == 'text'
            ) {
              async function abortMultipartUpload() {
                const name = that.uploadInfo.name // Object所在Bucket的完整路径。
                const uploadId = that.uploadInfo.uploadId // 分片上传uploadId。
                client(that.dataObj)
                  .abortMultipartUpload(name, uploadId)
                  .then((result) => {
                    this.progress = 0
                    this.showProgress = false
                  })
              }
    
              abortMultipartUpload()
            }
          },
          // 文件列表移除文件时的钩子
          handleRemove(file, fileList) {
            this.progress = 0
            this.fileCurrentLen = fileList.length
            let imagePathList = []
            fileList.map((item, index) => {
              if (item.uid == file.uid) {
                fileList.splice(index, 1)
              }
            })
            imagePathList = this.getImageList(fileList)
    
            // 空数组 将imagePathList 转为为字符串
            if (imagePathList.length == 0) {
              imagePathList = ''
            } else if (imagePathList.length == 1) {
              imagePathList = imagePathList[0]
            }
    
            this.urlClone = []
    
            this.$emit('update:value', imagePathList)
            // 嵌套多层的情况手动更新数据
            this.$emit('removeChange', imagePathList)
          },
          // 上传成功
          uploadSuccess(response, file, fileList) {
            if (this.type == 'image' && this.isSuccess) {
              // 图片类
              let imageList = this.getImageList(fileList)
              let imagePath
              if (this.limit == 1) {
                // 单张直接返回图片地址
                imagePath = imageList[0]
              } else {
                // 多张返回图片地址列表
                imagePath = imageList
              }
              this.$emit('update:value', imagePath)
            }
          },
          //文件上传前的校验
          beforeAvatarUpload(file) {
            // 获取文件的md5 ,
            // var fileReader = new FileReader()
            // var spark = new SparkMD5() // 创建md5对象(基于SparkMD5)
            // fileReader.readAsBinaryString(file) // file 对应上传的文件
    
            // // 文件读取完毕之后的处理
            // fileReader.onload = (e) => {
            //   console.log('获取文件的md5')
            //   spark.appendBinary(e.target.result)
            //   const md5 = spark.end()
            //   console.log(md5)
            // }
            const that = this
            // 校验文件类型
            if (this.type && !file.type.startsWith(this.type) && !this.isLyc) {
              this.$message.error('格式不正确,请重新上传!')
              return false
            }
            if (this.type == 'audio') {
              this.showProgress = true
              //音频类型限制上传大小
              if (Number((file.size / 1024 / 1024).toFixed(3)) > this.measure) {
                this.$message.error(`音频不能超过${this.measure}M`)
                return false
              }
              this.getAudioInfo = URL.createObjectURL(file)
              document.getElementById('myAudio').addEventListener('canplaythrough', function (e) {
                const audioTime = formatMin(e.target.duration)
                that.$emit('update:duration', audioTime)
              })
            }
            if (this.type == 'image') {
              if (Number((file.size / 1024 / 1024).toFixed(3)) > this.measure) {
                that.$message.error(`${file.name}大小不对,请重新上传!`)
                return false
              }
            }
            // 上传本地获取视频时长
            if (this.type == 'video') {
              this.showProgress = true
              // 视频类限制上传大小
              if (Number((file.size / 1024 / 1024).toFixed(3)) > this.measure) {
                this.$message.error(`视频不能超过${this.measure}M`)
                return false
              }
              this.getVideoInfo = URL.createObjectURL(file)
              document.getElementById('myVideo').addEventListener('canplaythrough', function (e) {
                const time = formatTime(e.target.duration)
                that.$emit('update:duration', time)
              })
            }
            // 歌词 格式校验
    
            if (this.isLyc) {
              this.showProgress = true
              if (file.name.split('.')[1].toLowerCase() != 'lrc') {
                this.$message.error('请上传格式正确的歌词文件')
                return false
              }
            }
            if (this.code) {
              this.$message.error(this.message)
              return false
            }
            if (!that.imageSize) {
              return true
            }
            return new Promise(function (resolve, reject) {
              let reader = new FileReader()
              let size = JSON.parse(that.imageSize)
              reader.readAsDataURL(file)
              reader.onload = function (theFile) {
                let image = new Image()
                image.src = theFile.target.result
                image.onload = function () {
                  if (size.width && size.height) {
                    const noSizeLimit = !this.height || !this.width
                    const rightSize = size.width == this.width && size.height == this.height
                    if (noSizeLimit || rightSize) {
                      file.width = size.width
                      file.height = size.height
                      resolve(file)
                      return
                    } else {
                      that.$message.error(`${file.name}尺寸不对,请重新上传!`)
                      reject('图片尺寸不对')
                      return
                    }
                  }
                  if (!size.width || !size.height) {
                    if (size.width) {
                      if (size.width == this.width) {
                        file.width = size.width
                        resolve(file)
                      } else {
                        that.$message.error(`${file.name}尺寸不对,请重新上传!`)
                        reject('图片尺寸不对')
                      }
                    } else {
                      if (size.height == this.height) {
                        file.height = size.height
                        resolve(file)
                      } else {
                        that.$message.error(`${file.name}尺寸不对,请重新上传!`)
                        reject('图片尺寸不对')
                      }
                    }
                  }
                }
              }
            })
          },
          // http-request属性来覆盖默认的上传行为,自定义上传的实现
          async Upload(file) {
            await Ajax.get('/aliyun/oss', {
              params: {
                oss: this.oss
              }
            })
              .then((res) => {
                if (res.code == 500) {
                  ;(this.code = 1), this.$message.error(res.message)
                  return false
                }
                this.dataObj = res
              })
              .catch((err) => {
                this.$message.error(err.message)
              })
            let fileName = '.' + file.file.name.substring(file.file.name.lastIndexOf('.') + 1)
            const that = this
            that.$emit('update:size', (file.file.size / 1024 / 1024).toFixed(2))
            async function multipartUpload() {
              //增加时间戳和随机数 防止文件覆盖
              let temporary = new Date().getTime() + randomString(6) + fileName
              let date = new Date()
              let year = date.getFullYear()
              let month = date.getMonth() + 1
              let day = date.getDate()
              let fileAddress =
                that.type == 'audio'
                  ? 'admin/audio'
                  : that.type == 'video'
                  ? 'admin/video'
                  : that.type == 'image'
                  ? 'admin/images'
                  : 'admin/lyrics'
              client(that.dataObj)
                .multipartUpload(
                  `${fileAddress}/${that.business}/${year}${month}${day}/${temporary}`,
                  file.file,
                  {
                    progress: function (p, _checkpoint) {
                      Object.assign(that.uploadInfo, _checkpoint)
                      if (that.type != 'image') {
                        that.showProgress = true
                        that.progress = Math.floor(p * 100)
                      }
                    }
                  }
                )
                .then((result) => {
                  let url = `${that.dataObj.domain}/${fileAddress}/${that.business}/${year}${month}${day}/${temporary}`
                  if (result) {
                    that.isSuccess = true
                  //回调,可以使用element 的上传成功钩子函数
                    file.onSuccess(url)
                  }
                  // 上传成功 赋值 音视频预览
                  that.type == 'video' ? (that.video_url = url) : (that.audio_info.resource_url = url)
                  // 上传成功 隐藏进度条
                  if (Number(that.progress) == 100) that.showProgress = false
                  that.isDelete = true
                  if (!that.multiple) that.$emit('update:value', url)
                  that.$emit('update:name', temporary)
                })
                .catch((err) => {
                  that.showProgress = false
                  // 捕获超时异常。
                  if (err.code === 'ConnectionTimeoutError') {
                    that.$message.error('TimeoutError')
                  }
                })
            }
            multipartUpload()
          },
          initFieldList() {
            this.urlClone = isArrayFn(this.value) || !this.value ? this.value : [this.value]
            // 初始化
            if (this.urlClone && this.urlClone.length) {
              this.fileList = this.urlClone.map((item) => {
                // 初始化预览音视频赋值
                this.type == 'video'
                  ? (this.video_url = deleteParame(item))
                  : (this.audio_info.resource_url = deleteParame(item))
                // 返回音视频地址显示处理
                let name = ''
                const audioPathArr = deleteParame(item).split('/')
                name = audioPathArr[audioPathArr.length - 1]
                return {
                  name:
                    this.type == 'audio' || this.type == 'video' || this.isLyc
                      ? name || item.name || item.audio_name || item
                      : item.name || item.audio_name || item,
                  url: item
                }
              })
              this.fileCurrentLen = this.fileList.length
            }
          },
          // getFieldName(item) {
          //   if (this.videoName && Object.getOwnPropertyNames(this.videoName).length !== 0) {
          //     return this.videoName
          //   }
          //   if (this.name && Object.getOwnPropertyNames(this.name).length !== 0) {
          //     return this.name
          //   }
          //   if (this.audio_name && Object.getOwnPropertyNames(this.audio_name).length !== 0) {
          //     return this.audio_name
          //   }
          //   return item
          // },
          beforeDestroy() {
            document.getElementById('myVideo').addEventListener('canplaythrough', true),
              document.getElementById('myAudio').addEventListener('canplaythrough', true)
            // 组件销毁 释放对象url
            URL.revokeObjectURL(this.getVideoInfo)
            URL.revokeObjectURL(this.getAudioInfo)
          }
        }
      }
    </script>
    <style lang="less" scoped>
      .ossUpload {
        /deep/ .el-upload-list__item {
          transition: none !important;
        }
        .el-progress {
          width: 55%;
        }
        .el-progress-bar {
          width: 55%;
        }
        .el-upload-list__item {
          width: 30% !important;
        }
        .video {
          display: none;
        }
        .upload {
          line-height: 1.4;
        }
        .el-upload-list .focusing {
          border: 1px solid #c0ccda;
          outline: none;
        }
        .has {
          /deep/ .el-upload--picture-card:last-child {
            display: none;
          }
          :global(.el-upload--) {
            display: none;
          }
        }
        .unshow {
          :global(.el-upload-list--) {
            display: none;
          }
          :global(.el-upload-list--picture) {
            display: none;
          }
        }
        .audio {
          display: none;
        }
        .preview {
          margin-left: 10px;
        }
        .video-con {
          display: flex;
          align-items: center;
          justify-content: center;
        }
        .videoPrview {
          width: 100%;
          height: auto;
          max-height: 60vh;
        }
        .hidden {
          /deep/ .el-upload-list--picture-card .el-upload-list__item {
            height: 100px;
            width: 100px;
          }
          /deep/ .el-upload-list--picture-card .el-upload-list__item-actions {
            font-size: 14px;
          }
          /deep/.el-upload--picture-card {
            height: 100px;
            width: 100px;
            line-height: 100px;
          }
        }
      }
    </style>
    
    

    3:防止不同文件相同名字上传oss 覆盖, 对文件名处理拼接(看公司需求要求)

    // 随机字符串
    export function randomString(len) {
      len = len || 32
      const $chars =
        'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' /****默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1****/
      const maxPos = $chars.length
      let pwd = ''
      for (let i = 0; i < len; i++) {
        pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
      }
      return pwd
    }
    

    :有任何问题都可留言 ps: 此组件之前全是vue2 写法, 现用在vue3项目中,所以中有emits, 并没有全部改成3.0的写法,只是兼容了3,0 ,各种可按情况更改

    相关文章

      网友评论

          本文标题:vue +element-ui 实现的oss web直传文件

          本文链接:https://www.haomeiwen.com/subject/rqirpltx.html