前端文件上传 、预览、 优化 真不简单

3,008 阅读3分钟

文件上传看似很常规的需求,要做极致真不简单,以下这些点你都做到了吗?

  • 上传进度条(交互优化)

  • 同一个文件多次上传不起效

  • 传错文件了,图片不满意重新上传

  • 文件大小校验

    • fileObj.size校验
    • 文件二进制头信息校验
  • 校验类型(扩展名)

    • fileObj.name 做校验不靠谱(修改的扩展名如何校验)
    • 文件二进制头信息校验
  • 校验宽高

  • 是否支持拖拽上传

  • 是否支持复制粘贴上传

  • 是否支持大文件断网续传

  • 大文件切片上传添加指纹

  • 大文件MD5怎么优化

  • 考虑性能和优化(web-worker/time-slice)

  • 文件压缩(如 canvas压缩)

  • 上传超时重试

  • 。。。

基本满足上传刚性需求

    <div class="file-upload-wrap">
      <div class="file-item-wrap">
        <img class="icon-camara" src="../assets/img/camara.svg" >
        <img
          v-show="isShow"
          class="icon-close"
          @click="deletePreview"
          src="../assets/img/close.svg" >
        <p class="des">请选择身份证正面照片或者拍照上传</p>
        <!-- 上传预览 -->
        <img class="preview"
          v-show="isShow"
          :src="imgSrc" >
        <input
          class="inp"
          type="file"
          ref="inp"
          @click="clearInp"
          @change="showPreview">
      </div>
      <button @click="upload">上传</button>
    </div>
    
    
    data() {
        return {
          imgSrc: '',
          fileObj: {}
        }
      },
      computed: {
        isShow() {
          return !!this.imgSrc
        }
      },
      methods: {
        //本地预览
        showPreview() {
          const vm = this
          this.fileObj = vm.$refs.inp.files[0]
          if (!window.FileReader) return
          const reader = new FileReader()
          reader.readAsDataURL(vm.fileObj)
          reader.onloadend = function() {
            // 此时this 指向fileRender
            const that = this
            vm.$nextTick(() => {
              vm.imgSrc = that.result
            })
          }
        },
    
        upload() {
          // 校验文件格式
          this.checkType(this.fileObj)
          // 校验大小
          this.checkSize(this.fileObj)
          // 上传
          this.uplodHandler(this.fileObj)
        },
        // 转换图片为二进制类型
        dataURLtoBlob(dataurl) {
          const vm = this
          vm.hexStr = ''
          let dataViewObj = {}
          if (!window.FileReader) return
          const reader = new FileReader()
          reader.readAsArrayBuffer(vm.fileObj)
          reader.onloadend = function(res) {
            const that = this
            vm.$nextTick(() => {
              dataViewObj = new DataView(that.result)
              for (let i = 0; i < dataViewObj.byteLength; i++) {
                vm.hexStr += dataViewObj.getUint8(i).toString(16)
              }
              // 校验文件格式
              vm.checkType(vm.hexStr)
            })
          }
        },
        // 校验图片类型
        checkType(hexStr) {
          const mimeTypeList = [{ typeName: 'jpg/jpeg', hexStr: 'FFD8FF' }, { typeName: 'png', hexStr: '89504E47' }]
          // 依据二进制文件头信息来做判断,防止修改扩展名
          const jpegHex = hexStr.slice(0, 6).toUpperCase()
          const pngHex = hexStr.slice(0, 8).toUpperCase()
          const matchTypeList = mimeTypeList.filter((item) => item.hexStr === jpegHex || item.hexStr === pngHex)
          if (!matchTypeList.length) {
            this.$dialog.alert({
              title: '温馨提示',
              message: '请上传格式为png,jpg 或jpeg的照片格式'
            })
            return false
          }
    
          /* // 获取文件name
          const name = fileObj.name
          // 文件扩展名
          const ext = name.split('.')[1].toLowerCase()
          // 允许上传的格式
          const imgType = ['png', 'jpg', 'jpeg', 'PNG', 'JPG', 'JPEG']
          const filterType = imgType.filter((item) => item === ext)
          if (!filterType.length) {
            this.$dialog.alert({
              title: '温馨提示',
              message: '请上传格式为png,jpg 或jpeg的照片格式'
            })
            return false
          } */
        },
        // 校验大小
        checkSize(fileObj) {
          if (fileObj.size >= 10 * 1024) {
            this.$dialog.alert({
              title: '温馨提示',
              message: '照片大小不能超过10k'
            })
            return false
          }
        },
        // 上传
        uplodHandler(fileObj) {
          // TODO 上传接口
          axios.post(...)
          
        },
        // 清除input值
        clearInp() {
          this.$refs.inp.value = ''
        },
        // 删除重新上传
        deletePreview() {
          this.imgSrc = ''
          this.$refs.inp.value = ''
        }
    

持续更新中。。。

// TODO

添加进度条

拖拽上传

复制粘贴上传

文件转二进制进行头信息校验(防止篡改文件扩展名)

  • 文件类型头信息标识
    #文件头信息
    JPEG (jpg),文件头:FFD8FF
    PNG (png),文件头:89504E47
    GIF (gif),文件头:47494638
    TIFF (tif),文件头:49492A00
    Windows Bitmap (bmp),文件头:424D
    CAD (dwg),文件头:41433130
    Adobe Photoshop (psd),文件头:38425053
    Rich Text Format (rtf),文件头:7B5C727466
    XML (xml),文件头:3C3F786D6C
    HTML (html),文件头:68746D6C3E
    Email [thorough only] (eml),文件头:44656C69766572792D646174653A
    Outlook Express (dbx),文件头:CFAD12FEC5FD746F
    Outlook (pst),文件头:2142444E
    MS Word/Excel (xls.or.doc),文件头:D0CF11E0
    MS Access (mdb),文件头:5374616E64617264204A
    WordPerfect (wpd),文件头:FF575043
    Postscript (eps.or.ps),文件头:252150532D41646F6265
    Adobe Acrobat (pdf),文件头:255044462D312E
    Quicken (qdf),文件头:AC9EBD8F
    Windows Password (pwl),文件头:E3828596
    ZIP Archive (zip),文件头:504B0304
    RAR Archive (rar),文件头:52617221
    Wave (wav),文件头:57415645
    AVI (avi),文件头:41564920
    Real Audio (ram),文件头:2E7261FD
    Real Media (rm),文件头:2E524D46
    MPEG (mpg),文件头:000001BA
    MPEG (mpg),文件头:000001B3
    Quicktime (mov),文件头:6D6F6F76
    Windows Media (asf),文件头:3026B2758E66CF11
    MIDI (mid),文件头:4D546864
    
    
  • 文件转化
    /**
     * 网络图像文件转Base64
     */
    function getBase64Image(img) {
        var canvas = document.createElement("canvas");
        canvas.width = img.width;
        canvas.height = img.height;
        var ctx = canvas.getContext("2d");
        ctx.drawImage(img, 0, 0, img.width, img.height);
        var ext = img.src.substring(img.src.lastIndexOf(".") + 1).toLowerCase();
        var dataURL = canvas.toDataURL("image/" + ext);
        return dataURL;
    }
     
     
    /**
    *Base64字符串转二进制
    */
    function dataURLtoBlob(dataurl) {
        var arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);
        while (n--) {
            u8arr[n] = bstr.charCodeAt(n);
        }
        return new Blob([u8arr], {
            type: mime
    });