bootstrap下载进度条
原来的 web 应用中文件下载都是打开一个新的窗口,文件自动下载后需手动关闭新打开的窗口,且没有下载进度显示。
window.open(downloadUrl,'__blank');
利用 bootstrap 和 Toasts 和 Progress 组件写一个可以显示下载进度的弹出框,下载完成自己关闭。
用本地文件模拟下载,服务器端 php 代码
//本地文件
$file = 'zz.csv';
header("Content-type: " . filetype($file));
header("Content-length: " . filesize($file));
header("Content-Type:application/octet-stream");
header("Content-Disposition: attachment;filename=" . $file);
header("Content-Transfer-Encoding: binary");
header('Pragma: no-cache');
header('Expires: 0');
echo file_get_contents($file);
exit;
bootstrap 的 Toasts 封装,这里使用 Toasts 组件堆叠显示
<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>
<div class="toast-container position-fixed top-0 end-0">
<div class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-autohide="false" id="liveToast">
<div class="toast-header">
<strong class="me-auto filename"></strong>
<small class="text-muted progress-num"></small>
</div>
<div class="toast-body">
<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>
<script>
const toast = {
begin: 0,
list: {},
create: function () {
this.begin += 1;
const key = "liveToast" + this.begin;
const newToast = document.getElementById("liveToast").cloneNode(true);
newToast.setAttribute("id", key);
document.querySelector(".toast-container").appendChild(newToast);
this.list[key] = new bootstrap.Toast(document.getElementById(key));
return key;
},
remove: function (key) {
this.list[key].hide();
delete this.list[key];
document.getElementById(key).remove();
},
};
</script>
使用 XMLHttpRequest 请求后端地址下载
<script>
function downloadProgress(url) {
const newToastId = toast.create();
const filenameEle = document.getElementById(newToastId).querySelector(".filename");
const progressNumEle = document.getElementById(newToastId).querySelector(".progress-num");
const downloadProgressBarEle = document.getElementById(newToastId).querySelector(".progress-bar");
toast.list[newToastId].show();
let xhr = new XMLHttpRequest();
xhr.timeout = 3000;
xhr.responseType = "blob";
xhr.open("GET", url, true);
xhr.addEventListener("progress", function (event) {
if (event.total) {
const progress = (event.loaded / event.total) * 100;
downloadProgressBarEle.style.width = progress + "%";
progressNumEle.innerText = progress + "%";
if (progress == 100) {
setTimeout(() => {
toast.remove(newToastId);
}, 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]));
filename = xhr.getResponseHeader("content-disposition").split("=")[1];
link.download = filename;
link.click();
filenameEle.innerText = filename;
}
};
xhr.ontimeout = function (event) {
alert("请求超时!");
};
xhr.send();
}
downloadProgress("./test.php");
</script>
经测试,可以正常显示弹出框、进度条和下载文件。
将服务器端代码换成 PhpSpreadsheet 包生成的文件,看下是否可以正常下载:
require 'vendor/autoload.php';
use PhpOffice\PhpSpreadsheet\Spreadsheet;
$spreadSheet = new Spreadsheet();
$sheet = $spreadSheet->getActiveSheet();
for ($i = 0; $i < 20000; $i++) {
$sheet->setCellValueByColumnAndRow(1, $i, $i);
$sheet->setCellValueByColumnAndRow(2, $i, "hello");
$sheet->setCellValueByColumnAndRow(3, $i, 555);
}
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename=simple.xlsx');
header('Cache-Control: max-age=0');
header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadSheet, 'Xlsx');
$writer->save('php://output');
exit;
发现响应头没有 Content-length
使用 ob 函数修改导出部分的代码
$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadSheet, 'Xlsx');
ob_start();
$writer->save('php://output');
$file_size = ob_get_length();
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename=simple.xlsx');
header('Cache-Control: max-age=0');
header('Cache-Control: cache, must-revalidate');
header("Content-length: " . $file_size);
ob_get_flush();
exit;
测试结果:页面右上角显示这样的下载进度弹出框,下载完成 2s 后关闭弹出框

使用 bootstrap 的 Toasts 弹出框可以同时出现多个弹出框,堆叠显示,可用于同时多个下载任务
若同一个时间只会有一个下载任务,也可以使用 sweetalert2 和 bootstrap 的 Progress 组件组合使用
<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>
<link href="./html-demos/plugins/sweetalert2/sweetalert2.min.css" rel="stylesheet" />
<script src="./html-demos/plugins/sweetalert2/sweetalert2.all.min.js"></script>
<script>
function downloadProgress(url) {
const Toast = Swal.mixin({
toast: true,
position: "top-start",
showConfirmButton: false,
});
Toast.fire({
icon: false,
title: `<div class="d-flex">
<span class="filename"></span>
<span class="d-inline-block ms-auto progress-num" style="width: 4rem"></span>
</div>`,
html: `<div class="progress" role="progressbar" aria-label="Basic example" aria-valuenow="1" aria-valuemin="0" aria-valuemax="100">
<div class="progress-bar" style="width: 1%"></div>
</div>`,
});
const filenameEle = Toast.getTitle().querySelector(".filename");
const progressNumEle = Toast.getTitle().querySelector(".progress-num");
const downloadProgressBarEle = Toast.getHtmlContainer().querySelector(".progress-bar");
let xhr = new XMLHttpRequest();
xhr.timeout = 3000;
xhr.responseType = "blob";
xhr.open("GET", url, true);
xhr.addEventListener("progress", function (event) {
console.log(event);
if (event.total) {
const progress = (event.loaded / event.total) * 100;
downloadProgressBarEle.style.width = progress + "%";
progressNumEle.innerText = progress + "%";
if (progress == 100) {
setTimeout(() => {
Toast.close();
}, 2000);
}
}
});
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr);
var link = document.createElement("a");
link.href = window.URL.createObjectURL(new Blob([xhr.response]));
filename = xhr.getResponseHeader("content-disposition").split("=")[1];
link.download = filename;
link.click();
filenameEle.innerText = filename;
}
};
xhr.ontimeout = function (event) {
alert("请求超时!");
};
xhr.send();
}
downloadProgress("./test1.php");
</script>
网络请求使用 axios 时:
axios.get(url, {
responseType: "blob",
onDownloadProgress: function (progressEvent) {
....
},
})
.then((response) => {
...
});
浙公网安备 33010602011771号