公共Hooks封装之文件下载useDownloadBlob
项目环境
Vue3.x + Ant Design Vue3.x + Vite3.x
封装分解:创建 a 标签下载文件
export function createDownload(blob, fileName, fileType) {
if (!blob || !fileName || !fileType) return;
const element = document.createElement("a");
const url = window.URL.createObjectURL(blob);
element.style.display = "none";
element.href = url;
element.download = `${fileName}.${fileType}`;
document.body.appendChild(element);
element.click();
if (window.URL) {
window.URL.revokeObjectURL(url);
} else {
window.webkitURL.revokeObjectURL(url);
}
document.body.removeChild(element);
}
封装分解:下载 Blob 文件
const downloadBlob = (
url,
fileName = "",
fileType = "",
autoDownload = false
) => {
return new Promise((resolve, reject) => {
xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open("get", url, true);
xhr.onprogress = function (e) {
progress.value = Math.floor((e.loaded / e.total) * 100);
if (progress.value === 100) {
progress.value = 0;
downloading = false;
}
};
xhr.onloadend = function (e) {
if ([200, 304].includes(e.target.status)) {
const blob = e.target.response;
if (autoDownload) {
createDownload(blob, fileName, fileType);
}
xhr = null;
resolve(blob);
}
};
xhr.onerror = function (e) {
downloading = false;
Modal.error({
title: "温馨提示",
content: "下载发生异常,请重试",
});
reject(e);
};
xhr.send();
});
};
相信已经有读者盆友已经看出来以上两段代码均与useDownloadFile.js
内容一致,这也是出于实际工作中代码优化的考虑,对于封装应当尽可能的模块化,这样在处理可能多场景下的内容依旧适用。
先前的封装useDownloadFile
已经可以解决管理后台中对于文件下载的需求,结合实际业务,当需要进行管理同一用户/业务内的多个资料统一打包时,可以尝试封装一下下载文件压缩包的方式来解决该业务场景。
下面的内容则是介绍相关的实现方法与封装思路。
封装分解:JS 压缩--JSZip
A library for creating, reading and editing .zip files with JavaScript, with a lovely and simple API.
JSZip 支持各种类型的资源uint8array、blob、arraybuffer、nodebuffer、string
等,结合现有封装,非常适用。实际使用到的 API 有 2 个,分别是 zip.file() 和 zip.generateAsync()。
相关的 API 文档已经介绍很详细了,在此不再赘述,毕竟官方文档写的还是很好的~ Promise 风格的 API 用起来是非常舒服的~
const zip = new JSZip(); // 创建一个Zip对象
for (let i = 0, len = fileList.length; i < len; i++) {
const item = fileList[i];
const fileType = item.fileType ? item.fileType : item.url.split('.').pop();
curDownloadFileName.value = item.fileName;
const blob = await downloadBlob(item.url);
zip.file(`${item.fileName}.${fileType}`, blob); // 创建/更新文件到Zip File内,blob数据流
successCount.value++;
}
downloading = false;
infoModal && infoModal.destroy();
zip.generateAsync({ type: 'blob' } // 在当前文件夹级别生成完整的 zip 文件
封装分解:用户体验设计
下载过程中,配合项目使用的 Ant Design Vue 框架,可以加强用户感知文件下载进度
infoModal = Modal.info({
title: "文件批量下载",
okText: "取消下载",
icon: h("span"),
width: 580,
content: () => {
return h("div", { class: "mt-4" }, [
h("div", { class: "fs-16 font-bold" }, [
"文件下载过程中请勿关闭当前页面",
]),
h("div", { className: "mt-2" }, [
`总文件数:${fileList.length},已下载文件数:${successCount.value}`,
]),
h("div", { className: "mt-2 ellipsis" }, [
`当前下载文件名:${curDownloadFileName.value}`,
]),
h("div", { className: "mt-2" }, [`当前文件下载进度:${progress.value}%`]),
]);
},
onOk() {
xhr.abort();
xhr = null;
return Promise.resolve();
},
});
封装分解:下载文件压缩包 Zip
const downloadZip = async (fileList = [], fileName) => {
let infoModal;
const successCount = ref(0);
const curDownloadFileName = ref("");
infoModal = Modal.info({
title: "文件批量下载",
okText: "取消下载",
icon: h("span"),
width: 580,
content: () => {
return h("div", { class: "mt-4" }, [
h("div", { class: "fs-16 font-bold" }, [
"文件下载过程中请勿关闭当前页面",
]),
h("div", { className: "mt-2" }, [
`总文件数:${fileList.length},已下载文件数:${successCount.value}`,
]),
h("div", { className: "mt-2 ellipsis" }, [
`当前下载文件名:${curDownloadFileName.value}`,
]),
h("div", { className: "mt-2" }, [
`当前文件下载进度:${progress.value}%`,
]),
]);
},
onOk() {
xhr.abort();
xhr = null;
return Promise.resolve();
},
});
const zip = new JSZip();
for (let i = 0, len = fileList.length; i < len; i++) {
const item = fileList[i];
const fileType = item.fileType ? item.fileType : item.url.split(".").pop();
curDownloadFileName.value = item.fileName;
const blob = await downloadBlob(item.url);
zip.file(`${item.fileName}.${fileType}`, blob);
successCount.value++;
}
downloading = false;
infoModal && infoModal.destroy();
zip
.generateAsync({ type: "blob" })
.then((content) => {
createDownload(content, fileName, "zip");
})
.catch((error) => {
console.error(error);
});
};
到这里,就是针对之前的useDownloadFile
改造的主要内容~
在实际工作中,其实一个良好的习惯就在于保持对代码的"更新",毕竟随着时间的推移,每个人都会收获成长,那么以前写的代码或多或少有些许不合理。又或许你接手的是前任埋下的"屎山"代码,但毕竟不是每个老板/公司都愿意给你时间大刀阔斧的重构~不止是封装 Hooks,亦或是其他的,利用闲碎时间优化一下代码吧,毕竟在这个"寒冷的环境下",适当的优化也是提高人效的一部分哦~
useDownloadFile.js 完整代码
import { h, onBeforeUnmount, ref } from "vue";
import { Modal } from "ant-design-vue";
import JSZip from "jszip";
import { createDownload } from "@/utils/util";
export function useDownloadFile() {
let xhr = null;
let downloading = false; // 限制同一文件同时触发多次下载
const progress = ref(0);
onBeforeUnmount(() => {
if (xhr) {
xhr.abort();
xhr = null;
}
});
// 下载文件blob
const downloadBlob = (
url,
fileName = "",
fileType = "",
autoDownload = false
) => {
return new Promise((resolve, reject) => {
xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.open("get", url, true);
xhr.onprogress = function (e) {
progress.value = Math.floor((e.loaded / e.total) * 100);
if (progress.value === 100) {
progress.value = 0;
downloading = false;
}
};
xhr.onloadend = function (e) {
if ([200, 304].includes(e.target.status)) {
const blob = e.target.response;
if (autoDownload) {
createDownload(blob, fileName, fileType);
}
xhr = null;
resolve(blob);
}
};
xhr.onerror = function (e) {
downloading = false;
Modal.error({
title: "温馨提示",
content: "下载发生异常,请重试",
});
reject(e);
};
xhr.send();
});
};
// 下载文件
const downloadFile = async (options) => {
try {
let infoModal;
if (downloading || !options.url || !options.fileName) return;
downloading = true;
options.url = options.url.replace("http://", "https://");
let fileType = "";
if (options.fileType) {
fileType = options.fileType;
} else {
fileType = options.url.split(".").pop();
}
infoModal = Modal.info({
title: "文件下载",
okText: "取消下载",
icon: h("span"),
content: () => {
return h("div", { class: "mt-4" }, [
h("div", { class: "fs-16 font-bold" }, [
"文件下载过程中请勿关闭当前页面",
]),
h("div", { className: "mt-2" }, [
`当前下载进度 ${progress.value}%`,
]),
]);
},
onOk() {
xhr.abort();
xhr = null;
return Promise.resolve();
},
});
await downloadBlob(options.url, options.fileName, fileType, true);
downloading = false;
infoModal && infoModal.destroy();
} catch (e) {
console.error(e);
}
};
// 下载文件压缩包zip
const downloadZip = async (fileList = [], fileName) => {
let infoModal;
const successCount = ref(0);
const curDownloadFileName = ref("");
infoModal = Modal.info({
title: "文件批量下载",
okText: "取消下载",
icon: h("span"),
width: 580,
content: () => {
return h("div", { class: "mt-4" }, [
h("div", { class: "fs-16 font-bold" }, [
"文件下载过程中请勿关闭当前页面",
]),
h("div", { className: "mt-2" }, [
`总文件数:${fileList.length},已下载文件数:${successCount.value}`,
]),
h("div", { className: "mt-2 ellipsis" }, [
`当前下载文件名:${curDownloadFileName.value}`,
]),
h("div", { className: "mt-2" }, [
`当前文件下载进度:${progress.value}%`,
]),
]);
},
onOk() {
xhr.abort();
xhr = null;
return Promise.resolve();
},
});
const zip = new JSZip();
for (let i = 0, len = fileList.length; i < len; i++) {
const item = fileList[i];
const fileType = item.fileType
? item.fileType
: item.url.split(".").pop();
curDownloadFileName.value = item.fileName;
const blob = await downloadBlob(item.url);
zip.file(`${item.fileName}.${fileType}`, blob);
successCount.value++;
}
downloading = false;
infoModal && infoModal.destroy();
zip
.generateAsync({ type: "blob" })
.then((content) => {
createDownload(content, fileName, "zip");
})
.catch((error) => {
console.error(error);
});
};
return {
downloadFile,
downloadZip,
};
}
备注说明
由于本篇文章是基于实际封装的优化,故实际项目中 Hooks 代码依然叫做 useDownloadFile。【优化代码/叠加功能,尽可能不破坏原有的结构或引入】
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南