umi文件下载:Blob文件下载与进度显示
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
在现代Web应用中,文件下载是一个常见的需求场景。无论是导出报表、下载用户生成的内容,还是获取服务器资源,都需要高效可靠的文件下载机制。本文将深入探讨如何在umi框架中实现Blob文件下载,并展示如何添加进度显示功能,提升用户体验。
为什么需要Blob文件下载?
传统文件下载通常通过<a>标签或window.open()实现,但这些方式存在一些局限性:
- 无法控制下载过程:无法获取下载进度
- 无法处理复杂逻辑:如权限验证、文件处理等
- 用户体验差:缺乏进度反馈和错误处理
Blob(Binary Large Object)下载通过JavaScript直接处理二进制数据,提供了更灵活的控制能力。
umi中的请求处理基础
umi内置了强大的请求处理能力,通过request方法可以轻松处理各种HTTP请求:
import { request } from 'umi';
// 基本GET请求
const response = await request('/api/data');
// 带参数的POST请求
const result = await request('/api/submit', {
method: 'POST',
data: { name: 'value' }
});
实现Blob文件下载
基本Blob下载实现
import { request } from 'umi';
const downloadFile = async (url: string, filename: string) => {
try {
const response = await request(url, {
responseType: 'blob',
getResponse: true,
});
// 创建Blob对象
const blob = new Blob([response.data]);
// 创建下载链接
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
// 触发下载
document.body.appendChild(link);
link.click();
// 清理资源
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
return true;
} catch (error) {
console.error('下载失败:', error);
return false;
}
};
支持多种文件类型
const downloadWithType = async (
url: string,
filename: string,
fileType: string = 'application/octet-stream'
) => {
const response = await request(url, {
responseType: 'blob',
getResponse: true,
});
const blob = new Blob([response.data], { type: fileType });
// 常见文件类型映射
const typeMap: Record<string, string> = {
pdf: 'application/pdf',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
jpg: 'image/jpeg',
png: 'image/png',
};
const extension = filename.split('.').pop() || '';
const contentType = typeMap[extension] || fileType;
return new Blob([response.data], { type: contentType });
};
添加进度显示功能
进度监控实现
import { useState } from 'react';
interface DownloadProgress {
loaded: number;
total: number;
percentage: number;
isDownloading: boolean;
}
const useDownloadProgress = () => {
const [progress, setProgress] = useState<DownloadProgress>({
loaded: 0,
total: 0,
percentage: 0,
isDownloading: false,
});
const downloadWithProgress = async (url: string, filename: string) => {
setProgress(prev => ({ ...prev, isDownloading: true }));
try {
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const percentage = Math.round((event.loaded / event.total) * 100);
setProgress({
loaded: event.loaded,
total: event.total,
percentage,
isDownloading: true,
});
}
};
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
}
setProgress(prev => ({ ...prev, isDownloading: false }));
};
xhr.open('GET', url);
xhr.send();
} catch (error) {
console.error('下载失败:', error);
setProgress(prev => ({ ...prev, isDownloading: false }));
}
};
return { progress, downloadWithProgress };
};
React组件集成
import React from 'react';
import { Progress, Button, Space } from 'antd';
const FileDownloader: React.FC = () => {
const { progress, downloadWithProgress } = useDownloadProgress();
const handleDownload = () => {
downloadWithProgress('/api/export/data', 'report.xlsx');
};
return (
<Space direction="vertical" style={{ width: '100%' }}>
<Button
type="primary"
onClick={handleDownload}
loading={progress.isDownloading}
disabled={progress.isDownloading}
>
{progress.isDownloading ? '下载中...' : '下载文件'}
</Button>
{progress.isDownloading && (
<Progress
percent={progress.percentage}
status="active"
showInfo={true}
/>
)}
</Space>
);
};
高级功能实现
断点续传支持
const resumeDownload = async (
url: string,
filename: string,
existingSize: number = 0
) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.setRequestHeader('Range', `bytes=${existingSize}-`);
xhr.responseType = 'blob';
xhr.onload = () => {
if (xhr.status === 206) { // Partial Content
const blob = xhr.response;
// 处理分块下载逻辑
}
};
xhr.send();
};
多文件批量下载
const batchDownload = async (files: Array<{url: string; filename: string}>) => {
const downloadPromises = files.map(file =>
downloadFile(file.url, file.filename)
);
try {
const results = await Promise.allSettled(downloadPromises);
const successful = results.filter(
result => result.status === 'fulfilled'
).length;
console.log(`成功下载 ${successful}/${files.length} 个文件`);
} catch (error) {
console.error('批量下载失败:', error);
}
};
错误处理与用户体验优化
完善的错误处理
const safeDownload = async (url: string, filename: string) => {
try {
const response = await request(url, {
responseType: 'blob',
getResponse: true,
timeout: 30000, // 30秒超时
});
if (response.status !== 200) {
throw new Error(`下载失败: HTTP ${response.status}`);
}
if (!response.data) {
throw new Error('服务器返回空数据');
}
// 文件大小检查
const maxSize = 100 * 1024 * 1024; // 100MB
if (response.data.size > maxSize) {
throw new Error('文件过大,请联系管理员');
}
// 执行下载
const blob = new Blob([response.data]);
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
// 清理资源
setTimeout(() => {
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
}, 100);
return { success: true, message: '下载成功' };
} catch (error: any) {
const errorMessage = error.message || '下载过程中发生未知错误';
return { success: false, message: errorMessage };
}
};
下载状态管理
interface DownloadState {
status: 'idle' | 'downloading' | 'success' | 'error';
progress: number;
currentFile?: string;
error?: string;
}
const useDownloadManager = () => {
const [state, setState] = useState<DownloadState>({
status: 'idle',
progress: 0,
});
const download = async (url: string, filename: string) => {
setState({
status: 'downloading',
progress: 0,
currentFile: filename,
});
const xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onprogress = (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
setState(prev => ({ ...prev, progress }));
}
};
xhr.onload = () => {
if (xhr.status === 200) {
const blob = xhr.response;
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
setState({ status: 'success', progress: 100 });
} else {
setState({
status: 'error',
progress: 0,
error: `下载失败: HTTP ${xhr.status}`,
});
}
};
xhr.onerror = () => {
setState({
status: 'error',
progress: 0,
error: '网络错误,请检查连接',
});
};
xhr.open('GET', url);
xhr.send();
};
const reset = () => {
setState({ status: 'idle', progress: 0 });
};
return { state, download, reset };
};
性能优化建议
内存管理优化
// 使用流式处理大文件
const streamDownload = async (url: string, filename: string) => {
const response = await fetch(url);
const reader = response.body?.getReader();
const contentLength = response.headers.get('Content-Length');
const total = contentLength ? parseInt(contentLength) : 0;
let received = 0;
const chunks: Uint8Array[] = [];
if (reader) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
received += value.length;
// 更新进度
const progress = total > 0 ? Math.round((received / total) * 100) : 0;
console.log(`下载进度: ${progress}%`);
}
}
const blob = new Blob(chunks);
// ...后续下载逻辑
};
并发控制
class DownloadQueue {
private concurrentLimit: number;
private activeDownloads: number = 0;
private queue: Array<() => Promise<void>> = [];
constructor(concurrentLimit: number = 3) {
this.concurrentLimit = concurrentLimit;
}
async add(downloadTask: () => Promise<void>) {
return new Promise<void>((resolve, reject) => {
const task = async () => {
try {
this.activeDownloads++;
await downloadTask();
resolve();
} catch (error) {
reject(error);
} finally {
this.activeDownloads--;
this.runNext();
}
};
this.queue.push(task);
this.runNext();
});
}
private runNext() {
if (this.activeDownloads < this.concurrentLimit && this.queue.length > 0) {
const task = this.queue.shift();
task?.();
}
}
}
完整示例应用
import React from 'react';
import { Card, Button, Progress, Space, message } from 'antd';
import { DownloadOutlined } from '@ant-design/icons';
const FileDownloadDemo: React.FC = () => {
const { state, download, reset } = useDownloadManager();
const handleDownload = () => {
download('/api/files/report.pdf', '月度报告.pdf');
};
const getStatusText = () => {
switch (state.status) {
case 'downloading':
return `正在下载 ${state.currentFile}...`;
case 'success':
return '下载完成';
case 'error':
return state.error;
default:
return '准备下载';
}
};
return (
<Card title="文件下载演示" style={{ maxWidth: 500, margin: '20px auto' }}>
<Space direction="vertical" style={{ width: '100%' }}>
<Button
type="primary"
icon={<DownloadOutlined />}
onClick={handleDownload}
loading={state.status === 'downloading'}
disabled={state.status === 'downloading'}
block
>
下载文件
</Button>
{state.status === 'downloading' && (
<Progress percent={state.progress} status="active" />
)}
<div style={{ textAlign: 'center', color: '#666' }}>
{getStatusText()}
</div>
{(state.status === 'success' || state.status === 'error') && (
<Button onClick={reset} block>
{state.status === 'success' ? '继续下载' : '重试'}
</Button>
)}
</Space>
</Card>
);
};
总结
通过本文的详细介绍,我们学习了在umi框架中实现Blob文件下载的完整方案,包括:
- 基础下载功能:使用Blob对象和URL.createObjectURL实现文件下载
- 进度监控:通过XMLHttpRequest的progress事件实时跟踪下载进度
- 错误处理:完善的异常处理和用户反馈机制
- 高级功能:断点续传、批量下载、并发控制等
- 性能优化:内存管理、流式处理等优化策略
这种方案不仅提供了更好的用户体验,还具备了更强的灵活性和可控性。在实际项目中,可以根据具体需求选择适合的实现方式,为用户提供流畅可靠的文件下载体验。
记住良好的用户体验是关键——及时的进度反馈、清晰的错误信息和简洁的操作流程都能显著提升用户满意度。
【免费下载链接】umi A framework in react community ✨ 项目地址: https://gitcode.com/GitHub_Trending/um/umi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



