封装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>
即可实现下面的效果:

浙公网安备 33010602011771号