umi文件下载:Blob文件下载与进度显示

umi文件下载:Blob文件下载与进度显示

【免费下载链接】umi A framework in react community ✨ 【免费下载链接】umi 项目地址: 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文件下载的完整方案,包括:

  1. 基础下载功能:使用Blob对象和URL.createObjectURL实现文件下载
  2. 进度监控:通过XMLHttpRequest的progress事件实时跟踪下载进度
  3. 错误处理:完善的异常处理和用户反馈机制
  4. 高级功能:断点续传、批量下载、并发控制等
  5. 性能优化:内存管理、流式处理等优化策略

这种方案不仅提供了更好的用户体验,还具备了更强的灵活性和可控性。在实际项目中,可以根据具体需求选择适合的实现方式,为用户提供流畅可靠的文件下载体验。

记住良好的用户体验是关键——及时的进度反馈、清晰的错误信息和简洁的操作流程都能显著提升用户满意度。

【免费下载链接】umi A framework in react community ✨ 【免费下载链接】umi 项目地址: https://gitcode.com/GitHub_Trending/um/umi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值