vue2上传zip功能,显示zip内文件并依次上传处理

复制代码
 <a-upload
          name="file"
          :accept="fileAcceptType"
          :multiple="false"
          :show-upload-list="false"
          :before-upload="file => { beforeFileUpload(file) }"
          :customRequest="file => { selfFileUpload(file) }"
          @change="info => { handleFileChange(info) }"
        >
          <a-button> <a-icon type="upload" /> 上传文件 </a-button>
          </p>
        </a-upload>


 <a-table
          :columns="columns"
          :data-source="fileList"
          rowKey="num"
        >
          <span
            slot="size"
            slot-scope="text, record"
          ><span>{{text?text+"kb":'-'}}</span>
            <a-progress
              :percent="record.uploadPercent/1"
              v-if="(record.uploadLoading || record.uploadStatus) && record.status=='上传中'"
            />
          </span>
          <span
            slot="status"
            slot-scope="text, record"
          >
            <span :class="text=='上传成功'?'success':(text=='上传失败'?'fail':'')">{{text}}</span>
          </span>
          <span
            slot="action"
            slot-scope="text, record"
          >
            <a
              :disabled="disabled"
              @click="del(record)"
            >删除</a>
          </span>
        </a-table>

  beforeFileUpload(file) {
      console.log("beforeFileUpload", file);
      const mimeType = file.type.toLowerCase();
      // 检查MIME类型是否以特定字符串开头
      let isDoc =
        mimeType.startsWith("application/zip") ||
        mimeType.startsWith("application/x-zip"); 
      if (!isDoc) {
        this.$message.warning("上传文件格式不正确,请上传正确的文件!");
      }
      let isLt1G = file.size < this.fileAcceptSize;
      if (!isLt1G) {
        this.$message.warning(
          `封面文件过大,请上传不超过${this.fileAcceptSizeStr}的文件!`
        );
      }
      const isLt0 = file.size == 0;
      if (isLt0) {
        this.$message.warning("上传文件为空,请检查!");
      }
      console.log("beforeFileUpload", isDoc && !isLt0 && isLt1G);
      return isDoc && !isLt0 && isLt1G;
    },
    selfFileUpload(file) {
      let type = file.file.type;
      const mimeType = type.toLowerCase();
      // 检查MIME类型是否以特定字符串开头
      let isDoc =
        mimeType.startsWith("application/zip") ||
        mimeType.startsWith("application/x-zip") 
      let isLt1G = file.file.size < this.fileAcceptSize;
      const isLt0 = file.file.size == 0;
      if (isDoc && !isLt0 && isLt1G) {
        this.file = file;
        // 加载文件
        if (this.zipProcessor) {
          this.zipProcessor = null;
        }
        let that = this;
        this.zipProcessor = new ZipProcessor({
          maxSize: that.fileAcceptSize,
          maxCount: 100,
          viewUnabled: true,
          validateFileType: function (ext, fullFileName) {
            // 定义允许的类型
            const allowedTypes = ["png", "jpg", "mp3", "mp4"];
            return allowedTypes.includes(ext);
          },
          onStatusUpdate: function (fileName, status) {
            console.log(`文件 ${fileName} 状态更新为: ${status}`);
            // 更新页面上对应文件的状态显示
            if (that.fileList && that.fileList.length > 0) {
              that.fileList.forEach((element) => {
                if (element.name == fileName) {
                  element.status = status;
                }
              });
            }
          },
          onLoadComplete: function (results) {
            console.log("所有文件加载完毕。结果:", results);
            that.fileList = [...results];
            // 设置序号
            that.fileList.forEach((item, index) => {
              item.orderNo = index + 1;
            });
            if (that.fileList.length > 0) {
              // 开始上传按钮启用
              that.disabled = false;
              that.btnText = "开始上传";
            }
          },
          onComplete: function (results) {
            console.log("所有文件处理完毕。结果:", results);
            that.finalStatus = true;
          },
          onError: function (error) {
            console.error("处理过程中发生错误:", error);
          },
        });

        if (this.file.file) {
          that.zipProcessor.processZip(this.file.file); // 开始处理
        }
      } else {
        this.file = null;
      }
    },
    handleFileChange(info) {},
async onStartUpload() {
      // 开始上传
      let that = this;
      that.uploading = true;
      that.disabled = true;
      that.btnText = "上传中";
      for (let i = 0; i < this.fileList.length; i++) {
        // viewList
        const fileType = this.fileList[i].type;
        const fileName = this.fileList[i].name;
        const fileEntry = this.fileList[i].file;

        // --- 上传逻辑 ---
        try {
          // 读取文件内容(如果需要上传到服务器, 如果显示的包含不可上传的,需要特殊处理一下)
          // 这里要处理三种文件类型,且需要加入上传进度条
          if (
            fileType == "jpg" ||
            fileType == "png" ||
            fileType == "mp3" ||
            fileType == "mp4"
          ) {
            let config = {
              timeout: 120000, //设置超时时长
              onUploadProgress: function (progress) {
                this.fileList[i].uploadPercent = parseFloat(
                  (progress.loaded * 100) / progress.total
                ).toFixed(2);
              }.bind(this),
            };
            that.fileList[i].uploadLoading = true; // record.uploadStatus
            that.fileList[i].status = "上传中";
            const formData = new FormData();
            formData.append("bookId", that.bookId);
            formData.append("title", fileName || "");
            const fileData = await fileEntry.async("blob");
            if (fileType == "jpg" || fileType == "png") {
              // 上传图片
              //formData.append("description", "test");
              formData.append("imageFile", new File([fileData], fileName));
              console.log("formData", formData);
               // 异步,一条条上传,否则服务器压力过大
              let picRes = await createPic(formData, config);
              let data = picRes.data;
              if (data.success) {
                that.fileList[i].uploadLoading = false;
                that.fileList[i].uploadStatus = true;
                that.fileList[i].status = "上传成功";
              } else {
                that.fileList[i].uploadLoading = false;
                that.fileList[i].uploadStatus = false;
                that.fileList[i].status = "上传失败";
              }
            } else if (fileType == "mp3") {
              // 上传音频
              
            } else if (fileType == "mp4") {
              // 上传视频
             
             
            }
          }
        } catch (uploadError) {
          // console.error(`上传文件 ${fileName} 时失败:`, uploadError);
        }
        // --- 模拟上传逻辑结束 ---
        if (i == this.fileList.length - 1) {
          this.uploading = false;
          this.btnText = "上传结束";
          that.finalStatus = true;
        }
      }
    },

ZipProcessor.js:

复制代码
// zipProcessor.js
import { filetype } from "@/config/filetype";
import JSZip from "jszip";
/**
 * ZIP 文件处理器类
 */
class ZipProcessor {
  /**
   * 构造函数
   * @param {Object} config - 配置对象
   * @param {Function} config.validateFileType - 用于验证单个文件类型的函数,接收文件名作为参数,返回布尔值
   * @param {Function} [config.onProgress] - 可选的进度回调函数,接收 (current, total) 参数
   * @param {Function} [config.onStatusUpdate] - 可选的状态更新回调函数,接收 (fileName, status) 参数
   * @param {Function} [config.onLoadComplete] - 可选的加载文件完成回调函数,接收 (results) 参数
   * @param {Function} [config.onComplete] - 可选的处理完成回调函数,接收 (results) 参数
   * @param {Function} [config.onError] - 可选的错误处理回调函数,接收 (error) 参数
   * @param {boolean} [config.viewUnabled] - 是否显示格式错误的文档
   * @param {number} [config.maxCount] - 包含文件的最大文件数量
   * @param {number} [config.maxSize] - zip最大文件大小
   */
  constructor(config) {
    this.validateFileType = config.validateFileType;
    if (typeof this.validateFileType !== "function") {
      throw new Error("validateFileType is required and must be a function.");
    }
    this.onProgress = config.onProgress || (() => {});
    this.onStatusUpdate = config.onStatusUpdate || (() => {});
    this.onLoadComplete = config.onLoadComplete || (() => {});
    this.onComplete = config.onComplete || (() => {});
    this.onError =
      config.onError ||
      ((error) => {
        console.error("ZipProcessor Error:", error);
      });
    this.viewUnabled = config.viewUnabled ? config.viewUnabled : false;
    this.maxCount = config.maxCount ? config.maxCount : 100;
    this.maxSize = config.maxSize ? config.maxSize : 1024 * 1024 * 1024;
    this.fileList = []; // 符合条件的文件
    this.results = []; // 全部文件
    this.viewList = []; //config.postFiles ? config.postFiles : [];
    this.zipContent = undefined;
  }

  getFileNameFromPath(fullName) {
    // 查找最后一个 '/' 或 '\' 的位置
    const lastSlashIndex = Math.max(
      fullName.lastIndexOf("/"),
      fullName.lastIndexOf("\\"),
    );
    // 如果找到了路径分隔符,则返回其后的部分;否则返回原字符串
    return fullName.substring(lastSlashIndex + 1);
  }
  /**
   * 处理上传的 ZIP 文件
   * @param {File} zipFile - 用户选择的 ZIP 文件
   */
  async processZip(zipFile) {
    if (!zipFile || !zipFile.name.toLowerCase().endsWith(".zip")) {
      this.onError(new Error("请选择一个zip文件"));
      return;
    }

    try {
      //const JSZip = window.JSZip;
      if (!JSZip) {
        this.onError(
          new Error(
            "JSZip library is not loaded. Please include JSZip before using ZipProcessor.",
          ),
        );
        return;
      }
      let _this = this;
      const fileReader = new FileReader();
      // 转换文件为ArrayBuffer
      fileReader.readAsArrayBuffer(zipFile);
      const decoder = new TextDecoder("gbk");
      // 监听完成事件
      fileReader.onload = async function fn() {
      
        const zip = new JSZip();
        _this.zipContent = await zip.loadAsync(fileReader.result, {
          decodeFileName: (bytes) => {
            return decoder.decode(bytes);
          },
        });
        const files = Object.keys(_this.zipContent.files).filter(
          (name) => !_this.zipContent.files[name].dir,
        ); // 过滤掉目录
        const totalFiles = files.length;
        console.log("totalFiles", totalFiles, _this.maxCount);
        if (totalFiles > _this.maxCount) {
          this.onError(new Error("zip包含文件数量超过" + this.maxCount));
          return;
        }
        // const results = [];

        console.log(`开始处理 ZIP 文件,共发现 ${totalFiles} 个文件。`);

        for (let i = 0; i < files.length; i++) {
          console.log("files[i]", files[i]);
          const fileName = files[i];
          const fileEntry = _this.zipContent.files[fileName];
          console.log("fileEntry", fileEntry);
          // 获取文件扩展名并验证
          const lastDotIndex = fileName.lastIndexOf(".");
          let extension = "";
          if (lastDotIndex > 0) {
            extension = fileName.substring(lastDotIndex + 1).toLowerCase();
          }
          // 更新当前文件状态为 "checking"
          let realName = _this.getFileNameFromPath(fileName);
          let currentFile = {
            num: i + 1,
            realName: fileName,
            name: realName,
            type: extension,
            status: "checking",
            size: fileEntry._data.uncompressedSize
              ? Math.round(fileEntry._data.uncompressedSize / 1024)
              : "",
            uploadPercent: 0, // 上传百分比
            uploadLoading: false, // 上传加载
            uploadStatus: false, // 上传成功
            file: fileEntry,
          };
          _this.results.push(currentFile);

          const isValidType = _this.validateFileType(extension, fileName);

          if (!isValidType) {
            console.warn(`文件 ${fileName} 类型不符合要求,跳过处理。`);
            currentFile.status = "文件类型错误";
            _this.onStatusUpdate(fileName, "skipped_invalid_type");
            // this.onProgress(i + 1, totalFiles);
          } else {
            // 符合类型条件的文档push正确的文件列表
            currentFile.status = "待上传"; // check-finish
            _this.fileList.push(currentFile);
          }
          // this.onProgress(i + 1, totalFiles);
        }
        _this.viewList = _this.viewUnabled ? _this.results : _this.fileList;

        _this.onLoadComplete(_this.viewList);
      };
    } catch (error) {
      this.onError(error);
    }
  }
}

export default ZipProcessor;