6.基于vue封装的上传文件等功能

  1. 效果图如下:在这里插入图片描述
  2. 文件所在位置:
    在这里插入图片描述
  3. view/huge_file_upload/huge_file_upload.vue:(在项目中使用?)
<template>
  <div>
    <div style="width: 350px">
      <FileUploader
        :config="config"
        @fileUploadFinished="fileUploadFinishedHandle"
        @fileDeleteFinished="fileDeleteFinishedHandle"
        ref="fileUploader1"
      />
    </div>
    <sg-button type="primary" @click="clear">重置</sg-button>
  </div>
</template>

<script>
import FileUploader from "@/components/FileUploader";
import { IFileConfig } from "@/components/FileUploader/IFileConfig";
const config = new IFileConfig();
config.domId = "file";
config.isMultiple = true;
// config.cantDeleteServerFile = true
export default {
  name: "HugeFileUpload",
  components: {
    FileUploader,
  },
  data() {
    return {
      config: config,
    };
  },
  methods: {
    fileUploadFinishedHandle(e) {
      console.log(e);
    },
    fileDeleteFinishedHandle(e) {
      console.log(e);
    },
    clear() {
      this.$refs.fileUploader1.clear();
    },
  },
  mounted() {},
};
</script>

<style lang="scss" scoped></style>
  1. components/FileUploader/api/index.js:
import request from "@/utils/axios";

/**
 * 上传分片
 * @returns
 */
export function fileSliceUpload(data) {
  return request({
    url: "/ami/ma01-02-056/minio-file/fileSliceUpload",
    method: "POST",
    data,
  });
}

/**
 * 合并文件分片
 * @returns
 */
export function mergeSlice(data) {
  return request({
    url: "/ami/ma01-02-056/minio-file/mergeSlice",
    method: "POST",
    data,
  });
}

/**
 * 小文件上传,不分片
 * @returns
 */
export function fileUpload(data) {
  return request({
    url: "/ami/ma01-02-056/minio-file/fileUpload",
    method: "POST",
    data,
  });
}

/**
 * 删除文件
 * @returns
 */
export function deleteFile(data) {
  return request({
    url: "/ami/ma01-02-056/minio-file/deleteFile",
    method: "POST",
    data,
  });
}
  1. components/FileUploader/assets/图片(图片所在位置)
    在这里插入图片描述
  2. components/FileUploader/IFileConfig.js:(文件限制封装)
export class IFileConfig {
  constructor() {}
  // id
  domId = "";
  // 是否多文件
  isMultiple = false;
  // 文件类型
  fileAccept = ".zip, .rar";
  // 切片长度
  sliceLength = 1024 * 1024 * 5; // 5M
  // 文件大小限制
  maxFileSize = 1024 * 1024 * 1024 * 5; // 5G
  // 禁止删除上传成功的文件
  cantDeleteServerFile = false
}

  1. components/FildeUploader/index.vue:(上传文件的主要逻辑)
<template>
  <div>
    <div class="uploader-container">
      <input
        v-if="config.isMultiple"
        type="file"
        name="file"
        multiple="multiple"
        :id="config.domId"
        style="display: none"
        @change="fileChosen"
        :accept="config.fileAccept"
      />
      <input
        v-else
        type="file"
        name="file"
        :id="config.domId"
        style="display: none"
        @change="fileChosen"
        :accept="config.fileAccept"
      />
      <img
        src="./assets/upload.png"
        class="upload-img-btn"
        alt=""
        @click="openFileWindow()"
        v-if="uploadStatus === 'n'"
      />
      <div class="file-list" v-else>
        <sg-row v-for="(item, index) in fileList" :key="'01_' + index" class="file-item">
          <sg-col :span="2">
            <img src="./assets/uploaded.png" style="width: 16px; height: 16px; margin-top: 13px" alt="" />
          </sg-col>
          <sg-col :span="19">
            <div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden">
              {{ item.file.name }}
            </div>
          </sg-col>
          <sg-col :span="3" style="text-align: right">
            <span v-if="uploadStatus === 'u' || uploadStatus === 'f'">{{ item.percent }}%</span>
            <span v-if="item.isDelServerFile">0%</span>
            <img
              src="./assets/del.png"
              style="width: 15px; height: 16px; margin: 0 auto; margin-top: 13px; cursor: pointer"
              alt=""
              @click="delFile(index, item)"
              v-if="(uploadStatus === 'w' || uploadStatus === 's') && !item.isDelServerFile && delImgBtnFlag"
            />
          </sg-col>
        </sg-row>
      </div>
      <div style="width: 100px; margin: 0 auto">
        <sg-button type="primary" :loading="true" v-if="uploadStatus === 'u'">上传中</sg-button>
        <sg-button
          type="primary"
          @click="uploadBtnClickHandle()"
          :disabled="uploadStatus === 'n'"
          v-if="uploadStatus === 'n' || uploadStatus === 'w'"
          >上传文件</sg-button
        >
        <sg-button type="primary" @click="reUpload()" v-if="uploadStatus === 'f'">断点续传</sg-button>
      </div>
      <div style="text-align: center; margin-top: 20px">注:限上传3个文件,单个文件最大为{{ maxFileSize }}G</div>
    </div>
  </div>
</template>

<script>
import { fileSliceUpload, mergeSlice, fileUpload, deleteFile } from "./api";
import { deepClone } from "@/utils/util";
import { IFileConfig } from "./IFileConfig";
const uuid = () => {
  const temp_url = URL.createObjectURL(new Blob());
  const uuid = temp_url.toString(); // blob:https://xxx.com/b250d159-e1b6-4a87-9002-885d90033be3
  URL.revokeObjectURL(temp_url);
  return uuid.substr(uuid.lastIndexOf("/") + 1);
};
export default {
  name: "HugeFileUpload",
  props: {
    config: {
      type: IFileConfig,
      default: () => {},
    },
  },
  data() {
    return {
      // DOM相关
      fileIdForData: "",
      fileDom: null,
      fileList: [],
      uploadImgBtnFlag: true,
      maxFileSize: 5,
      // 删除按钮显示标志
      delImgBtnFlag: true,

      // 配置相关
      bytesPerPiece: this.config.sliceLength, // 每个文件切片大小定为5M

      // 上传相关
      taskList: [],
      sliceUrlList: {},
      // 文件切片列表
      fileSliceList: [],
      uploadStatus: "n", // n: 没有选择文件, w: 等待,u:正在上传,f: 任务执行完,但是有失败任务,s: 全部执行成功
      fileIdList: {},
      // 返参
      mergeResult: [],
    };
  },
  methods: {
    openFileWindow() {
      this.fileDom.click();
    },

    fileChosen() {
      //   console.log(e);
      if (this.fileDom.files && this.fileDom.files.length) {
        const len = this.fileDom.files.length < 4 ? this.fileDom.files.length : 3;
        for (let i = 0; i < len; i++) {
          const f = this.fileDom.files[i];
          if (f.size <= this.config.maxFileSize) {
            let blob = deepClone(this.fileDom.files[i]);
            // console.log(this.fileDom.files[i]);
            this.fileList.push({
              file: blob,
              percent: 0,
              filename: blob.name,
              isDelServerFile: false,
            });
          }
        }
        //   this.uploadImgBtnFlag = false
        // 变更组件状态 等待上传
        this.uploadStatus = "w";
        this.fileDom.value = "";
        //   console.log(this.fileDom.files);
      }
    },

    delFile(index, item) {
      if (this.uploadStatus === "w") {
        this.fileList.splice(index, 1);
        if (this.fileList.length === 0) {
          // this.uploadImgBtnFlag = true;
          // 变更组件状态 没有选择文件
          this.uploadStatus = "n";
        }
      } else {
        //   const formData = new FormData();
        //   formData.append("fileId", this.fileIdList[filename] * 1);
        this.deleteFile(this.fileIdList[item.filename] * 1, item);
      }
    },

    // 删除文件
    deleteFile(formData, fileInfo) {
      fileInfo.isDelServerFile = true;
      deleteFile(formData).then(
        (res) => {
          if (res.status === 200) {
            this.fileList = this.fileList.filter((item) => item.filename !== fileInfo.filename);
            delete this.fileIdList[fileInfo.filename];
            if (this.fileList.length === 0) {
              this.uploadStatus = "n";
            }
            this.$emit("fileDeleteFinished", { fileInfo, res });
          } else {
            console.log(res);
            fileInfo.isDelServerFile = false;
          }
        },
        (err) => {
          console.log(err);
          fileInfo.isDelServerFile = false;
        }
      );
    },

    mergeSlice(fileSliceList, totalPieces, filesize, filename) {
      return new Promise((resolve) => {
        // 回调计数器
        let count = 0;
        for (const item of fileSliceList) {
          if (item.status === "1") {
            count++;
          }
        }

        if (count == totalPieces) {
          console.log("merge happened");
          const param = {
            fileSize: Math.ceil(filesize / 1024),
            fullName: filename,
            sliceList: this.sliceUrlList[filename],
            sliceNum: totalPieces,
          };
          mergeSlice(param).then(
            (res) => {
              if (res.status === 200) {
                // console.log(res);
                resolve(res);
              } else {
                resolve({
                  type: "ErrorOrException",
                  msg: res,
                });
              }
            },
            (err) => {
              // console.log(err);
              resolve({
                type: "ErrorOrException",
                msg: err,
              });
            }
          );
        } else {
          resolve({
            type: "ErrorOrException",
            msg: null,
          });
        }
      });
    },

    fileSliceUpload(formData) {
      return new Promise(function (resolve) {
        fileSliceUpload(formData).then(
          (res) => {
            if (res.status === 200) {
              resolve(res);
            } else {
              resolve({
                type: "ErrorOrException",
                msg: res,
              });
            }
          },
          (err) => {
            // reject(err)
            // console.log(err);
            resolve({
              type: "ErrorOrException",
              msg: err,
            });
          }
        );
      });
    },

    // 小文件上传
    fileUpload(formData) {
      return new Promise(function (resolve) {
        fileUpload(formData).then(
          (res) => {
            if (res.status === 200) {
              resolve(res);
            } else {
              resolve({
                type: "ErrorOrException",
                msg: res,
              });
            }
          },
          (err) => {
            // reject(err)
            // console.log(err);
            resolve({
              type: "ErrorOrException",
              msg: err,
            });
          }
        );
      });
    },

    cutFileIntoPieces(blob, bytesPerPiece, totalPieces) {
      const uuidfolder = uuid();
      // 开始切割的位置
      let start = 0;
      // 切割的结束位置
      let end;
      // 切片的索引
      let index = 0;
      // 文件的大小
      const filesize = blob.size;
      // console.log(filesize);
      // 启动while循环对文件切片
      while (start < filesize) {
        // 设置切片的结束位置
        end = start + bytesPerPiece;
        // 对最后一片数据进行处理(可以省略)
        if (end > filesize) {
          end = filesize;
        }
        // 切割文件
        const chunk = blob.slice(start, end); //切割文件
        const sliceIndex = uuidfolder + "_" + index;
        // 利用formData来传递数据
        const formData = new FormData();
        formData.append("sliceFile", chunk);
        formData.append("sliceName", sliceIndex);
        formData.append("sliceNo", index);
        formData.append("sliceNum", totalPieces);
        formData.append("sliceUrl", "");

        this.fileSliceList.push({
          formData,
          sliceIndex,
          index,
          status: "0", // 0: 未上传或上传失败,1: 成功上传
        });

        start = end;
        index++;
      }
    },

    createTasks() {
      if (this.fileList && this.fileList.length) {
        for (const item of this.fileList) {
          const blob = item.file;
          // 文件的大小
          const filesize = blob.size;
          // console.log(filesize);
          // 文件的名称
          const filename = blob.name;
          if (filesize <= this.bytesPerPiece) {
            // 利用formData来传递数据
            const formData = new FormData();
            formData.append("multipartFile", blob);
            this.taskList.push({
              type: "small",
              status: "0",
              filename,
              formData,
              callback: this.fileUpload,
            });
          } else {
            //计算文件切片总数
            let totalPieces = Math.ceil(filesize / this.bytesPerPiece);
            // 清空文件切片
            this.fileSliceList = [];
            this.cutFileIntoPieces(blob, this.bytesPerPiece, totalPieces);
            if (this.fileSliceList && this.fileSliceList.length) {
              for (const slice of this.fileSliceList) {
                this.taskList.push({
                  type: "slice",
                  status: "0",
                  slice,
                  filename,
                  totalPieces,
                  // callback: this.fileSliceUpload(slice.formData),
                  callback: this.fileSliceUpload,
                });
              }
              const t = [];
              for (const item of this.fileSliceList) {
                t.push({
                  index: item.index,
                  status: "0", // 0: 未上传或上传失败,1: 成功上传
                });
              }
              this.taskList.push({
                type: "merge",
                status: "0",
                filename,
                fileSliceList: t,
                totalPieces,
                filesize,
                // callback: this.mergeSlice(deepClone(this.fileSliceList), totalPieces, filesize, filename),
                callback: this.mergeSlice,
              });
            }
          }
        }
      }
    },

    async runTask() {
      const setPercent = (filename, percent) => {
        const file = this.fileList.filter((item) => item.filename === filename);
        if (file && file.length) {
          file[0].percent = percent;
        }
      };

      for (const task of this.taskList) {
        if (task.status === "0") {
          console.log(task);
          if (task.type === "slice") {
            const res = await task.callback(task.slice.formData);
            if (!res.type) {
              // 变更状态
              task.status = "1"; // 成功上传
              // task.slice.status = '1'
              // 更新合并任务中储存的切片列表的状态
              const t = this.taskList.filter((item) => item.type === "merge" && item.filename === task.filename);
              if (t && t.length) {
                const s = t[0].fileSliceList.filter((fs) => fs.index === task.slice.index);
                if (s && s.length) {
                  s[0].status = "1";
                }
              }
              // 已经成功上传的切片
              const sib = this.taskList.filter(
                (item) => item.type === "slice" && item.filename === task.filename && item.status === "1"
              );
              const p = sib && sib.length ? sib.length : 0;
              // 变更文件上传百分比 最大 90
              setPercent(task.filename, parseInt((p / task.totalPieces) * 90));
              // const file = this.fileList.filter(item => item.filename === task.filename)
              // if (file && file.length) {
              //     file[0].percent = p / task.totalPieces * 90
              // }

              // 保存返回数据
              const {
                data: {
                  data: { sliceUrl },
                },
              } = res;
              if (!this.sliceUrlList[task.filename]) {
                this.sliceUrlList[task.filename] = [];
              }
              this.sliceUrlList[task.filename].push({
                sliceFile: null,
                sliceName: task.slice.sliceIndex,
                sliceNo: task.slice.index,
                sliceUrl,
              });
            }
          } else if (task.type === "merge") {
            const res = await task.callback(task.fileSliceList, task.totalPieces, task.filesize, task.filename);
            if (!res.type) {
              task.status = "1";
              // const file = this.fileList.filter(item => item.filename === task.filename)
              // if (file && file.length) {
              //     file[0].percent = 100
              // }
              setPercent(task.filename, 100);
              this.fileIdList[task.filename] = res.data.fileId;
              this.mergeResult.push(res);
            }
          } else if (task.type === "small") {
            const res = await task.callback(task.formData);
            console.log(res);
            if (!res.type) {
              task.status = "1";
              setPercent(task.filename, 100);
              this.fileIdList[task.filename] = res.data.data.fileId;
              this.mergeResult.push(res);
            }
          }
        }
      }
      // 执行状态判断
      const t = this.taskList.filter((task) => task.status === "0");
      if (t && t.length) {
        // 变更组件状态 任务执行完,但是有失败任务
        this.uploadStatus = "f";
      } else {
        // 变更组件状态 全部执行成功
        this.uploadStatus = "s";
        this.taskList = [];
        this.fileSliceList = [];
        this.sliceUrlList = {};
        this.$emit("fileUploadFinished", deepClone(this.mergeResult));
        this.mergeResult = [];
        //
        if (this.config.cantDeleteServerFile) {
          this.delImgBtnFlag = false;
        }
      }
    },

    uploadBtnClickHandle() {
      // 变更组件状态 正在上传
      this.uploadStatus = "u";
      this.createTasks();
      this.runTask();
    },

    clear() {
      this.fileList = [];
      this.uploadImgBtnFlag = true;
      this.delImgBtnFlag = true;
      this.taskList = [];
      this.sliceUrlList = {};
      this.fileSliceList = [];
      this.mergeResult = [];
      this.uploadStatus = "n";
    },
    reUpload() {
      // 变更组件状态 正在上传
      this.uploadStatus = "u";
      // this.sliceUpload(0)
      this.runTask();
    },
  },
  created() {
    // this.fileIdForData = this.config.fileId
    // this.fileIdForData = this.config.fileId
  },
  mounted() {
    this.fileDom = document.getElementById(this.config.domId);
    // console.log(this.fileDom);
    this.maxFileSize = parseInt((this.config.maxFileSize * 1) / 1024 / 1024 / 1024);
  },
  watch: {
    // fileId(newVal) {
    //     if (newVal) {
    //         this.fileIdForData = newVal
    //     }
    // }
  },
};
</script>

<style lang="scss" scoped>
.uploader-container {
  border: 1px dashed #bacfe7;
  height: 250px;
  border-radius: 5px;
  background: #f4f7fa;
  .upload-img-btn {
    width: 80px;
    height: 80px;
    margin: 40px auto;
    cursor: pointer;
  }
  .file-list {
    height: 160px;
    .file-item {
      margin: 5px 8px;
      background: #fff;
      height: 44px;
      line-height: 44px;
      padding: 0 10px;
    }
  }
}
</style>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值