jinlin
2023-11-24 c041523ff100a6a43ebc4411125a3e566e19f193
web/packages/components/zt-uploader/src/zt-uploader.vue
@@ -4,39 +4,65 @@
    <el-upload
      ref="upload"
      class="upload-input"
      :class="{hide: uploadDisabled || !isShowUpload}"
      :accept="accept"
      :action="uploadAction"
      :before-upload="handleBeforeUpload"
      :data="data"
      :disabled="uploading"
      :drag="false"
      :limit="limit"
      list-type="text"
      :multiple="multiple"
      :name="name"
      :on-change="handleChange"
      :on-error="handleUploadError"
      :auto-upload="true"
      :before-upload="handleBeforeUpload"
      :http-request="checkedFile"
      :before-remove="removeFile"
      :on-exceed="handleExceed"
      :on-success="handleUploadSuccess"
      :on-progress="onUploadProgress"
      :show-file-list="false"
      :file-list="fileList"
      v-if="!image && !crop"
    >
      <el-button class="upload-btn" size="mini" type="primary" icon="el-icon-upload"
                 v-show="!uploadDisabled && isShowUpload">{{ buttonText }}
      <!--      <el-upload-->
      <!--        ref="upload"-->
      <!--        class="upload-input"-->
      <!--        :class="{hide: uploadDisabled || !isShowUpload}"-->
      <!--        :accept="accept"-->
      <!--        :action="uploadAction"-->
      <!--        :before-upload="handleBeforeUpload"-->
      <!--        :data="data"-->
      <!--        :disabled="uploading"-->
      <!--        :drag="false"-->
      <!--        :limit="limit"-->
      <!--        list-type="text"-->
      <!--        :multiple="multiple"-->
      <!--        :name="name"-->
      <!--        :on-change="handleChange"-->
      <!--        :on-error="handleUploadError"-->
      <!--        :on-exceed="handleExceed"-->
      <!--        :on-success="handleUploadSuccess"-->
      <!--        :on-progress="onUploadProgress"-->
      <!--        :show-file-list="false"-->
      <!--        :file-list="fileList"-->
      <!--        v-if="!image && !crop"-->
      <!--        :auto-upload="true"-->
      <!--        :http-request="checkedFile"-->
      <!--        :before-remove="removeFile"-->
      <!--      >-->
      <div style="display:inline-block;width:600px;text-align: center" v-if="onlyUploadFile" class="el-upload__tip">
        {{computerFileName}}
      </div>
      <el-button slot="trigger" v-if="onlyUploadFile" size="small" type="primary">选取文件</el-button>
      <el-button type="primary" v-if="onlyUploadFile" size="small" style="margin-left:20px" @click="myCheckedFile">提 交
      </el-button>
      <el-button class="upload-btn" size="mini" type="primary" icon="el-icon-upload"
                 v-show="!uploadDisabled && isShowUpload && !onlyUploadFile">{{ buttonText }}
      </el-button>
      <div slot="tip" class="el-upload__tip" v-show="!uploadDisabled && isShowUpload && tip">{{ tip }}</div>
    </el-upload>
    <br/>
    <div v-show="progressFlag">
    <el-progress :text-inside="true" :stroke-width="30" :percentage="progressPercent" :format="format"></el-progress>
</div>
    <ul class="el-upload-list el-upload-list--text" v-if="!image && !crop && showFileList">
    <ul class="el-upload-list el-upload-list--text" v-if="!image && !crop && !onlyUploadFile">
      <li tabindex="0" class="el-upload-list__item is-success" v-for="(file, index) in uploadList" :key="file.id">
        <a class="el-upload-list__item-name" :href="file.url" target="_blank"><i
          class="el-icon-document"></i>{{ file.name }}</a>
@@ -71,6 +97,7 @@
          :limit="limit"
          :list-type="drag ? 'picture' : 'picture-card'"
          :multiple="multiple"
          :name="name"
          :on-change="handleChange"
          :on-error="handleUploadError"
          :on-exceed="handleExceed"
@@ -164,6 +191,9 @@
import Cookies from 'js-cookie'
import Cropper from 'vue-image-crop-upload'
import EleGallery from '../../vue-ele-gallery'
  import SparkMD5 from "spark-md5";
  import axios from "axios";
  import {getUUID} from "../../../utils";
export default {
  name: 'ZtUploader',
@@ -175,9 +205,19 @@
        return this.multiple ? [] : null
      }
    },
      dataForm: Object,
    action: {
      type: String,
      default: '/sys/oss/upload'
        default: '/sys/oss/uploadNew'
      },
      // 是否为图片
      showFileList: {
        type: Boolean,
        default: true
      },
      onlyUploadFile: {
        type: Boolean,
        default: false
    },
    // 是否为图片
    image: {
@@ -257,10 +297,6 @@
      default: '上传'
    },
    tip: String,
    showFileList: {
      type: Boolean,
      default: true
    },
    disabled: {
      type: Boolean,
      default: false
@@ -277,8 +313,25 @@
  },
  data() {
    return {
        isUpload: false,
        maxSize: 50 * 1024 * 1024 * 1024, // 上传最大文件限制  最小单位是b
        multiUploadSize: 10 * 1024 * 1024, // 大于这个大小的文件使用分块上传(后端可以支持断点续传)  100mb
        eachSize: 10 * 1024 * 1024, // 每块文件大小   100mb
        requestCancelQueue: [], // 请求方法队列(调用取消上传
        // 每上传一块的进度
        eachProgress: 0,
        // 总共有多少块。断点续传使用
        chunksKeep: 0,
        // 切割后的文件数组
        fileChunksKeep: [],
        // 这个文件,断点续传
        fileKeep: null,
        // 断点续传,文件md5
        fileMd5Keep: "",
      progressPercent: 0,
      uploadAction: `${window.SITE_CONFIG['apiURL']}${this.action}?token=${Cookies.get('token')}`,
        uploadAction: `${window.SITE_CONFIG['apiURL']}/sys/oss/upload_chunk?token=${Cookies.get('token')}`,
        uploadCheckAction: `${window.SITE_CONFIG['apiURL']}/sys/oss/upload_success?token=${Cookies.get('token')}`,
        uploadProcessAction: `${window.SITE_CONFIG['apiURL']}${this.action}?token=${Cookies.get('token')}`,
      cropData: {},
      isShowCrop: false,
      uploading: false,
@@ -287,10 +340,24 @@
      progressFlag:false
    }
  },
    created() {
      if (this.onlyUploadFile) {
        this.fileList = [{id: '1', name: '', url: ''}]
      } else {
        this.fileList = []
      }
    },
  computed: {
    // 是否显示提示
    showTip() {
      return this.isShowTip && (this.fileType.length || this.fileSize)
      },
      computerFileName(computerFileName) {
        let fileName = 'aaaa'
        if (this.fileList.length > 0) {
          fileName = this.fileList[0].name
        }
        return fileName
    },
    galleryValues() {
      let urls = []
@@ -313,7 +380,7 @@
      if (this.multiple) {
        return this.uploadList.length < this.limit
      } else {
        return this.uploadList.length === 0
          return this.uploadList.length === 0 || this.onlyUploadFile
      }
    },
    uploadDisabled() {
@@ -327,12 +394,15 @@
    }
  },
  watch: {
      // eslint-disable-next-line no-unused-vars
    value(val, oldval) {
      if (this.uploadList !== val) {
        this.uploadList = this.getUploadList(val)
        this.fileList = this.getUploadList(val)
        console.log(this.fileList,'this.fileList this.fileList')
        this.$emit('getUploaderImg',this.fileList)
          if (this.fileList.length>0){
            console.log(this.fileList[0].status, 'val.files[0].status23')
            // alert('2222'+this.fileList[0].status)
          }
      }
    },
    isShowCrop(value) {
@@ -343,28 +413,31 @@
  },
  methods: {
    format(percentage) {
      return percentage === 100 ? '后台正在处理' : `${percentage}%`;
        return percentage === 100 ? '后台正在处理' : percentage === 99 ? '正在进行文件校验' : `已上传${percentage}%`;
    },
    // 获取已上传的文件列表
    getUploadList(val) {
      if (val) {
        // console.log(val, 'getUploadList val')
          if (val.length > 0) {
        if (!this.multiple) { // 单选
          return [val]
              console.log(val, 'getUploadList val')
              return val
        } else {
          // console.log([...val], 'getUploadList [...val]')
              console.log([...val], 'getUploadList [...val]')
          return [...val]
        }
      } else {
        return []
      }
        }
    },
      // eslint-disable-next-line no-unused-vars
    onUploadProgress(event, file, fileList) {
      this.progressFlag = true
      let per = Number(
        /*let per = Number(
        ((event.loaded / event.total) * 100).toFixed(2)
      )
      this.progressPercent = per > 100 ? 100 : per
        this.progressPercent = per > 100 ? 100 : per*/
    },
    handleSetFileSet(fileName, fileType, fileSize) {
      const uid = this.cropData.uid || new Date().getTime()
@@ -385,6 +458,7 @@
      this.$emit('error', status)
    },
    handleCropUploadSuccess(response) {
        console.log(response, 'handleCropUploadSuccess response')
      this.cropData.status = 'success'
      this.cropData.percentage = 100
      this.cropData.response = response
@@ -416,10 +490,14 @@
          return false
        }
      }
        if (this.dataForm) {
          // eslint-disable-next-line vue/no-mutating-props
          this.dataForm.hasUploadFinsh = 50
        }
      this.uploading = true
      return true
    },
    handleChange() {
      async handleChange() {
      this.uploading = false
      /*const config = {
        onUploadProgress: (progressEvent) => {
@@ -436,8 +514,14 @@
      })*/
    },
    // 文件个数超出
    handleExceed() {
      this.$message.error(`最多上传${this.limit}个`)
      handleExceed(files, fileList) {
        if (this.onlyUploadFile) {
          this.$set(fileList[0], 'raw', files[0])
          this.$set(fileList[0], 'name', files[0].name)
          /*          this.$refs.upload.clearFiles()
                    this.$refs.upload.handleStart(files[0])*/
        }
        //this.$message.error(`最多上传${this.limit}个`)
    },
    // 上传失败
    handleUploadError(err) {
@@ -446,8 +530,9 @@
      this.$emit('error', err)
    },
    // 上传成功回调
      // eslint-disable-next-line no-unused-vars
    handleUploadSuccess(response, file) {
      console.log(response, file,'response, file')
        console.log(response, 'handleUploadSuccess response')
      if (response.code === 0) {
        this.progressPercent = 100
        this.uploading = false
@@ -477,6 +562,7 @@
      }
    },
    handRemoveAndSetValue(index) {
        console.log(index, 'handRemoveAndSetValue')
      if (this.multiple) {
        this.uploadList.splice(index, 1)
        this.$refs.upload.uploadFiles.splice(index, 1)
@@ -484,6 +570,377 @@
      } else {
        this.$emit('input', null)
      }
      },
      nullFunction(param) {
      },
      //开始分片上传myCheckedFile
      async myCheckedFile() {
        if (!this.handleBeforeUpload(this.fileList[0].raw))
          return
        let options = {
          file: this.fileList[0].raw,
          onProgress: function (val) {
          },
          onSuccess: function (val) {
          },
          onError: function (val) {
          }
        }
        await this.checkedFile(options)
      },
      async checkedFile(options) {
        if (!this.isUpload) {
          this.isUpload = true;
        } else {
          return this.$message({
            message: `当前文件正在上传`,
            type: "warning"
          });
        }
        this.progressFlag = true
        const {
          maxSize,
          multiUploadSize,
          getSize,
          splitUpload,
          singleUpload
        } = this; // 解构赋值
        const {file, onProgress, onSuccess, onError} = options; // 解构赋值
        file.uid = getUUID().toString()
        if (file.size > maxSize) {
          return this.$message({
            message: `您选择的文件大于${getSize(maxSize)}`,
            type: "error"
          });
        }
        this.fileKeep = file;
        const uploadFunc =
          file.size > multiUploadSize ? splitUpload : singleUpload; // 选择上传方式
        try {
          await uploadFunc(file, onProgress);
          onSuccess();
        } catch (e) {
          console.error(e);
          this.$message({
            message: e.message,
            type: "error"
          });
          this.progressFlag = false;
          this.progressPercent = 0;
          onError();
        }
      },
      // 格式化文件大小显示文字
      getSize(size) {
        return size > 1024
          ? size / 1024 > 1024
            ? size / (1024 * 1024) > 1024
              ? (size / (1024 * 1024 * 1024)).toFixed(2) + "GB"
              : (size / (1024 * 1024)).toFixed(2) + "MB"
            : (size / 1024).toFixed(2) + "KB"
          : size.toFixed(2) + "B";
      },
      // 单文件直接上传
      async singleUpload(file, onProgress) {
        await this.postFile(
          {file, uid: file.uid, fileName: file.fileName, chunk: 0},
          onProgress
        );
        const reader = new FileReader();
        reader.readAsArrayBuffer(file);
        console.log(file, 'singleUpload file');
        let hashMd5 = "";
        console.log(hashMd5);
        const that = this;
        function getHash(cb) {
          console.log(cb, "进入单个上传的getHash");
          console.log("进入单个上传的getHash");
          reader.onload = function (e) {
            console.log("进入单个上传的getHash的函数2");
            console.log(hashMd5);
            console.log(this);
            // console.log(e)
            const hash = SparkMD5.ArrayBuffer.hash(e.target.result, false);
            console.log(e.target.result, 'e.target.result');
            console.log(hash, 'hash');
            that.hashMd5 = hash;
            console.log(that.hashMd5, 'hashMd5');
            that.fileMd5Keep = hash;
            cb(hash);
          };
        }
        await getHash(function (hash) {
          console.log(hash, 'getHash(function (hash) hash');
          console.log(that, 'getHash(function (hash) that');
          // 请求接口
          that.validateFile({
            name: file.name,
            uid: file.uid,
            md5: hash,
            chunks: 1,
            filter_type: "user_data_file"
          });
        });
      },
      // 大文件分块上传
      splitUpload(file, onProgress) {
        console.log(file, 'splitUpload(file');
        // eslint-disable-next-line no-async-promise-executor
        return new Promise(async (resolve, reject) => {
          try {
            const {eachSize} = this;
            const chunks = Math.ceil(file.size / eachSize);
            this.chunksKeep = chunks;
            const fileChunks = await this.splitFile(file, eachSize, chunks);
            this.fileChunksKeep = fileChunks;
            //判断每上传一个文件,进度条涨多少,保留两位小数
            this.eachProgress = 100 / chunks;
            this.progressFlag = true;
            let currentChunk = 0;
            let percent = 0;
            for (let i = 0; i < fileChunks.length; i++) {
              // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
              // 此时需要判断进度条
              if (Number(currentChunk) === i) {
                // 每块上传完后则返回需要提交的下一块的index
                await this.postFile(
                  {
                    chunked: true,
                    chunk: i,
                    chunks,
                    eachSize,
                    fileName: file.name,
                    fullSize: file.size,
                    uid: file.uid,
                    file: fileChunks[i]
                  },
                  onProgress
                );
                currentChunk++;
                // 上传完一块后,进度条增加
                percent = percent + this.eachProgress;
                // 不能超过100
                this.progressPercent = percent > 100 ? 99 : Math.floor(percent);
              }
            }
            const spark = new SparkMD5.ArrayBuffer();
            let currentChunkMd5 = 0;
            const that = this;
            const reader = new FileReader();
            reader.onload = async function (e) {
              spark.append(e.target.result);
              currentChunkMd5++;
              if (currentChunkMd5 < chunks) {
                await loadNext();
              } else {
                // console.log(spark.end());
                const hashMd5111 = spark.end();
                that.fileMd5Keep = hashMd5111;
                // 在这里请求接口
                await that.validateFile({
                  name: file.name,
                  uid: file.uid,
                  md5: hashMd5111,
                  chunks: fileChunks.length,
                  filter_type: "git_secret_file"
                });
              }
            };
            // eslint-disable-next-line no-inner-declarations
            async function loadNext() {
              const start = currentChunkMd5 * eachSize;
              const end =
                start + eachSize >= file.size ? file.size : start + eachSize;
              await reader.readAsArrayBuffer(file.slice(start, end));
            }
            await loadNext();
            resolve();
          } catch (error) {
            reject(error);
          }
        });
      },
      // 断点续传
      againSplitUpload(file, array) {
        console.log("file,array");
        console.log(file);
        console.log(array);
        // eslint-disable-next-line no-async-promise-executor
        return new Promise(async (resolve, reject) => {
          try {
            // eslint-disable-next-line no-unused-vars
            const {eachSize, fileKeep} = this;
            const chunks = this.chunksKeep;
            const fileChunks = this.fileChunksKeep;
            this.progressFlag = true;
            // let currentChunk = 0;
            for (let i = 0; i < array.length; i++) {
              // 服务端检测已经上传到第currentChunk块了,那就直接跳到第currentChunk块,实现断点续传
              // 此时需要判断进度条
              // 每块上传完后则返回需要提交的下一块的index
              await this.postFile({
                chunked: true,
                chunk: array[i],
                chunks,
                name: file.name,
                fullSize: fileKeep.size,
                uid: file.uid,
                file: fileChunks[array[i]]
              });
              // currentChunk++
              // 上传完一块后,进度条增加
              this.progressPercent += this.eachProgress;
              // 不能超过100
              this.progressPercent = this.progressPercent > 100 ? 100 : this.progressPercent;
            }
            let fileMd5KeepTwo = this.fileMd5Keep;
            const isValidate = await this.validateFile({
              name: file.name,
              uid: file.uid,
              md5: fileMd5KeepTwo,
              chunks: fileChunks.length,
              filter_type: "git_secret_file"
            });
            if (!isValidate) {
              throw new Error("文件校验异常");
            }
            // 关闭进度条
            this.progressFlag = false;
            // 重置进度条
            this.progressPercent = 0;
            resolve();
          } catch (e) {
            reject(e);
          }
        });
      },
      // 文件分块,利用Array.prototype.slice方法
      splitFile(file, eachSize, chunks) {
        return new Promise((resolve, reject) => {
          try {
            setTimeout(() => {
              const fileChunk = [];
              for (let chunk = 0; chunks > 0; chunks--) {
                fileChunk.push(file.slice(chunk, chunk + eachSize));
                chunk += eachSize;
              }
              resolve(fileChunk);
            }, 0);
          } catch (e) {
            console.error(e);
            reject(new Error("文件切块发生错误"));
          }
        });
      },
      removeFile(file) {
        this.requestCancelQueue[file.uid]();
        delete this.requestCancelQueue[file.uid];
        return true;
      },
      // 提交文件方法,将参数转换为FormData, 然后通过axios发起请求
      postFile(param, onProgress) {
        const formData = new FormData();
        formData.append("file", param.file); //  改了
        formData.append("fileName", param.fileName); //  改了
        formData.append("uid", param.uid);
        formData.append("chunk", param.chunk);
        formData.append("filter_type", "git_secret_file");
        formData.append("chunks", param.chunks);
        const {requestCancelQueue} = this;
        const config = {
          cancelToken: new axios.CancelToken(function executor(cancel) {
            if (requestCancelQueue[param.uid]) {
              requestCancelQueue[param.uid]();
              delete requestCancelQueue[param.uid];
            }
            requestCancelQueue[param.uid] = cancel;
          }),
          onUploadProgress: e => {
            if (param.chunked) {
              e.percent = Number(
                (
                  ((param.chunk * (param.eachSize - 1) + e.loaded) /
                    param.fullSize) *
                  100
                ).toFixed(2)
              );
            } else {
              e.percent = Number(((e.loaded / e.total) * 100).toFixed(2));
            }
            onProgress(e)
          }
        }
        //return axios.post('sys/oss/upload_chunk/', formData, config).then(rs => rs.data)
        return this.$http({
          url: "/sys/oss/upload_chunk/",
          method: "POST",
          data: formData
        }).then(rs => rs.data
        );
      },
      // 文件校验方法
      validateFile(file) {
        //return axios.post('sys/oss/upload_success/', file).then(rs => rs.data)
        return this.$http({
          url: "/sys/oss/upload_success/",
          method: "POST",
          data: file
        }).then(res => {
          console.log(res, "validateFile res")
          if (res && !res.data) {
            this.againSplitUpload(file, res.data.error_file)
            this.$message({
              message: "有文件上传失败,正在重新上传",
              type: "warning"
            });
          } else if (res && res.data) {
            this.progressPercent = 100
            if (!this.onlyUploadFile) {
              this.$http({
                url: this.action,
                method: "POST",
                data: file
              }).then(res => {
                console.log(res, '后台处理 res1')
                let aa = res.data.status
                console.log(aa, 'res.data.status')
                if (this.multiple) {
                  this.uploadList.push(res.data)
                  this.$emit('input', this.uploadList)
                } else {
                  this.$emit('input', res.data)
                }
                this.uploading = false
                this.progressFlag = false
                this.progressPercent = 0
                this.isUpload = false
              })
            } else {
              console.log(file.uid, 'file.uid')
              this.$emit('recall', file.uid)
              this.uploading = false
              this.progressFlag = false
              this.progressPercent = 0
              this.isUpload = false
            }
          } else if (res && res.status == 40008) {
            this.$message.error(res.message);
            this.progressFlag = false;
            this.progressPercent = 0;
          }
        })
    }
  },
  mounted() {