封装bootstrap的Toasts组件实现的多个下载任务弹框

最近要改一个下载任务的需求,原来的代码要么使用ajax异步请求看不到下载进度,要么使用window.open(url,‘__blank’)打开一个新页面既看不到下载进度也要手动关闭新打开的窗口,于是决定自己封装一个下载任务的进度条提示框。由于前端框架使用的bootstrap,还是使用Toasts组件来提示吧。能提示下载进度的前提是可以获得要下载文件的大小,服务器端使用响应头Content-length返回文件大小

<link href="./html-demos/plugins/bootstrap-5.1.3-dist/css/bootstrap.min.css" rel="stylesheet" />
<script src="./html-demos/plugins/bootstrap-5.1.3-dist/js/bootstrap.bundle.js"></script>

<button type="button" class="btn btn-outline-primary mt-3 ms-3" onclick="downloadTask.show()">
  Download Task
  <span class="badge bg-danger"> 0 </span>
</button>

<div class="toast-container position-fixed top-0 end-0 mt-3 me-3 p-2">
  <div class="toast" role="alert" aria-live="assertive" aria-atomic="true" id="liveToast">
    <div class="toast-header">Download Task</div>
    <div class="toast-body">
      <div id="liveTask" style="display: none" class="mb-2">
        <div style="height: 1.5rem">
          <strong class="me-auto filename"></strong>
          <small class="text-muted progress-num"></small>
        </div>
        <div class="progress" role="progressbar" aria-label="Basic example" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
          <div class="progress-bar" style="width: 0%"></div>
        </div>
      </div>
    </div>
  </div>
</div>

<script>
  const downloadTask = {
    begin: 0,
    num: 0,
    taskList: {},
    toast: null,
    init: function (id) {
      this.toast = new bootstrap.Toast(document.getElementById(id));
    },
    show: function () {
      if (this.toast) this.toast.show();
    },
    create: function () {
      this.begin += 1;
      this.num += 1;
      const key = "liveTask" + this.begin;
      const newTask = document.getElementById("liveTask").cloneNode(true);
      newTask.setAttribute("id", key);
      newTask.style.display = "block";
      newTask.querySelector(".filename").innerText = "processing...";
      document.querySelector(".toast-body").appendChild(newTask);
      this.taskList[key] = key;

      const badgeEle = document.querySelector(".badge");
      badgeEle.innerText = this.num;
      badgeEle.style.display = "inline";
      return key;
    },
    remove: function (key) {
      delete this.taskList[key];
      document.getElementById(key).remove();
    },
    download: function (url) {
      this.show();
      const newTaskId = this.create();
      const filenameEle = document.getElementById(newTaskId).querySelector(".filename");
      const progressNumEle = document.getElementById(newTaskId).querySelector(".progress-num");
      const downloadProgressBarEle = document.getElementById(newTaskId).querySelector(".progress-bar");
      const badgeEle = document.querySelector(".badge");
      const this_obj = this;

      let xhr = new XMLHttpRequest();
      xhr.responseType = "blob";
      xhr.open("GET", url, true);
      xhr.addEventListener("progress", function (event) {
        if (event.total) {
          const progress = (event.loaded / event.total) * 100;
          console.log(progress);
          downloadProgressBarEle.style.width = progress + "%";
          progressNumEle.innerText = progress + "%";
          if (progress == 100) {
            this_obj.num -= 1;
            badgeEle.innerText = this_obj.num;
            if (this_obj.num < 1) badgeEle.style.display = "none";
            setTimeout(() => {
              this_obj.remove(newTaskId);
            }, 2000);
          }
        }
      });
      xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && xhr.status == 200) {
          var link = document.createElement("a");
          link.href = window.URL.createObjectURL(new Blob([xhr.response]));
          const filename = this_obj.getFileName(xhr.getResponseHeader("content-disposition"));
          link.download = filename;
          link.click();
          var headers = xhr.getAllResponseHeaders().toLowerCase();
          filenameEle.innerText = filename;
        }
      };
      xhr.ontimeout = function (event) {
        alert("请求超时!");
      };
      xhr.send();
    },
    getFileName: function (str) {
      //兼容中文文件名
      let name = this.handleFileName(str, "filename*=utf-8''", true);
      if (name == "") name = this.handleFileName(str, "filename=");
      return name;
    },
    handleFileName: function (str, file_str, decode = false) {
      let start = str.indexOf(file_str);
      let filename = "";
      let end = 0;
      if (start != -1) {
        end = str.indexOf(";", start);
        if (end == -1) {
          filename = str.substring(start, str.length);
        } else {
          filename = str.substring(start, end);
        }
        if (decode) {
          return decodeURI(filename.substring(file_str.length));
        } else {
          return filename.substring(file_str.length);
        }
      }
      return "";
    },
  };

  downloadTask.init("liveToast");

  downloadTask.download("./test1.php");

  downloadTask.download("./test1.php");

  setTimeout(() => {
    downloadTask.download("./test1.php");
  }, 4000);

  setTimeout(() => {
    downloadTask.download("./test1.php");
  }, 10000);
</script>

 即可实现下面的效果:

 

posted @ 2023-03-07 15:26  carol2014  阅读(142)  评论(0编辑  收藏  举报