Foreword
For front-end developers who frequently build enterprise management backends, working with tables to manipulate data is essential. When refactoring existing projects, it’s a good practice to encapsulate some reusable custom hooks. This article is ‘useDownloadFile.js’
Hooks encapsulated based on a personal project environment; this article only shares insights and best practices for hook encapsulation, so the accompanying code may not be directly applicable to others.
Project Environment
Vue 3.x + Ant Design Vue 3.x + Vite 3.x
For all kinds of resource files (pictures, documents, audio and video, etc.) that are common in the enterprise management background, it is normal to download and save them locally. In order to ensure uniformity and avoid repeated writing of redundant codes in each single-page file, this method is encapsulated.
Prerequisites for Encapsulation: Method Comparisons
In summary and combined with the actual project, and finally use the Blob object to package the download file method.
Encapsulation and Decomposition: Download the Core Code
xhr.onloadend = function (e) {
if (e.target.status === 200 || e.target.status === 304) {
const aElement = document.createElement('a');
const blob = e.target.response;
const url = window.URL.createObjectURL(blob);
aElement.style.display = 'none';
aElement.href = url;
aElement.download = `${options.fileName}.${fileType}`;
document.body.appendChild(aElement);
aElement.click();
if (window.URL) {
window.URL.revokeObjectURL(url);
} else {
window.webkitURL.revokeObjectURL(url);
}
document.body.removeChild(aElement);
}
};
xhr.send();
Component Decomposition: User Experience Design
-During the download process, the Ant Design Vue framework used in conjunction with the project can enhance the user’s perception of file download progress, -To prevent users from repeatedly triggering downloads through forceful clicks, a loading flag is used as an identifier. -After a download failure, prompt the user to retry the download.
createVNode('div', {}, ['文件下载过程中请勿关闭当前页面']),
createVNode('div', { className: 'mt-2' }, [`当前下载进度 ${progress.value}%`]),
catch (e) {
console.error(e);
downloading = false;
infoModal && infoModal.destroy();
Modal.error({
title: '提示',
content: '下载发生异常,请重试',
});
}
Complete code for useDownloadFile.js
export function useDownloadFile() {
let xhr = null;
let downloading = false; // 限制同一文件同时触发多次下载
let infoModal;
onBeforeUnmount(() => {
xhr && xhr.abort();
});
const downloadFile = options => {
try {
if (downloading || !options.url || !options.fileName) return;
downloading = true;
options.url = options.url.replace('http://', 'https://');
const progress = ref(0);
const fileType = options.url.split('.').pop();
xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.open('get', options.url, true);
infoModal = Modal.info({
title: '文件下载',
okText: '取消下载',
content: () => {
return createVNode('div', {}, [
createVNode('div', {}, ['文件下载过程中请勿关闭当前页面']),
createVNode('div', { className: 'mt-2' }, [`当前下载进度 ${progress.value}%`]),
]);
},
onOk() {
xhr.abort();
return Promise.resolve();
},
});
xhr.onprogress = function (e) {
progress.value = Math.floor((e.loaded / e.total) * 100);
if (progress.value === 100) {
downloading = false;
infoModal.destroy();
}
};
xhr.onloadend = function (e) {
if (e.target.status === 200 || e.target.status === 304) {
const aElement = document.createElement('a');
const blob = e.target.response;
const url = window.URL.createObjectURL(blob);
aElement.style.display = 'none';
aElement.href = url;
aElement.download = `${options.fileName}.${fileType}`;
document.body.appendChild(aElement);
aElement.click();
if (window.URL) {
window.URL.revokeObjectURL(url);
} else {
window.webkitURL.revokeObjectURL(url);
}
document.body.removeChild(aElement);
}
};
xhr.send();
} catch (e) {
console.error(e);
downloading = false;
infoModal && infoModal.destroy();
Modal.error({
title: '提示',
content: '下载发生异常,请重试',
});
}
};
return {
downloadFile,
};
}