Vue 文件批量下载组件封装完整使用方法及优化方案解析

一、批量下载功能使用方法

(一)方案一:前端打包方案(file-saver + jszip)

  1. 安装依赖
npm install file-saver jszip
 
 
  1. 创建工具函数
    在项目中创建utils/batchDownload.js文件:
import {
    saveAs } from 'file-saver';
import JSZip from 'jszip';

/**
 * 批量下载文件并打包为ZIP
 * @param {Array} fileList - 文件列表,每个元素包含url和name属性
 * @param {String} zipName - 可选,ZIP文件名称
 */
export const batchDownload = async (fileList, zipName = '批量下载.zip') => {
   
  if (!fileList || fileList.length === 0) {
   
    alert('请选择要下载的文件');
    return;
  }

  const zip = new JSZip();
  const failedFiles = [];

  // 逐个下载文件并添加到ZIP
  for (const file of fileList) {
   
    try {
   
      const response = await fetch(file.url);
      if (!response.ok) throw new Error(`下载失败: ${
     response.statusText}`);

      const blob = await response.blob();
      const fileName = file.name || file.url.split('/').pop();
      zip.file(fileName, blob);
    } catch (error) {
   
      console.error(`文件 ${
     file.name || file.url} 下载失败:`, error);
      failedFiles.push(file.name || file.url);
    }
  }

  // 生成并下载ZIP文件
  const content = await zip.generateAsync({
    type: 'blob' });
  saveAs(content, zipName);

  // 提示下载失败的文件
  if (failedFiles.length > 0) {
   
    alert(`以下文件下载失败:\n${
     failedFiles.join('\n')}`);
  }
};
 
 
  1. 在组件中使用
import {
    batchDownload } from '@/utils/batchDownload';

export default {
   
  data() {
   
    return {
   
      selectedFiles: [
        {
    url: 'https://example.com/file1.pdf', name: '文档1.pdf' },
        {
    url: 'https://example.com/file2.jpg', name: '图片2.jpg' }
      ]
    };
  },
  methods: {
   
    async handleBatchDownload() {
   
      await batchDownload(this.selectedFiles, '资料合集.zip');
    }
  }
};
 
 

(二)方案二:后端打包方案

  1. 前端调用示例
import axios from 'axios';

export default {
   
  methods: {
   
    async batchDownloadByBackend() {
   
      const fileIds = this.selectedFiles.map(file => file.id);

      try {
   
        const response = await axios.post('/api/downloadBatch', {
    fileIds }, {
   
          responseType: 'blob'
        });

        const url = window.URL.createObjectURL(new Blob([response.data]));
        const link = document.createElement('a');
        link.href = url;
        link.setAttribute('download', '批量下载.zip');
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
      } catch (error) {
   
        console.error('下载失败', error);
        this.$message.error('批量下载失败,请稍后重试');
      }
    }
  }
};
 
 
  1. 后端接口要求
  • 接收文件ID列表
  • 返回ZIP文件流
  • 设置正确的Content-Type和Content-Disposition头

二、组件封装方案

(一)通用批量下载组件

下面是一个基于方案一的可复用组件实现:

<template>
  <div class="batch-download">
    <el-button 
      :loading="isLoading" 
      @click="handleDownload"
      :disabled="selectedFiles.length === 0"
    >
      <i class="el-icon-download"></i> 批量下载
      <span v-if="selectedFiles.length > 0" class="file-count">({
   {
   selectedFiles.length}})</span>
    </el-button>

    <el-progress 
      v-if="isLoading && progress > 0" 
      :percentage="progress" 
      :color="progressColor"
      status="active"
    ></el-progress>

    <el-alert 
      v-if="failedFiles.length > 0" 
      title="部分文件下载失败" 
      type="warning"
      :description="failedFiles.join('、')"
      show-icon
    ></el-alert>
  </div>
</template>

<script>
import {
    batchDownload } from '@/utils/batchDownload';

export default {
   
  name: 'BatchDownload',
  props: {
   
    // 待下载的文件列表
    files: {
   
      type: Array,
      default: () => []
    },
    // 已选择的文件ID列表
    selectedFileIds: {
   
      type: Array,
      default: () => []
    },
    // ZIP文件名
    zipName: {
   
      type: String,
      default: '批量下载.zip'
    }
  },
  data() {
   
    return {
   
      isLoading: false,
      progress: 0,
      failedFiles: [],
      timer: null
    };
  },
  computed: {
   
    // 当前选中的文件
    selectedFiles() {
   
      if (!this.selectedFileIds || this.selectedFileIds.length === 0) {
   
        return this.files;
      }
      return this.files.filter(file => this.selectedFileIds.includes(file.id));
    },
    // 进度条颜色
    progressColor() {
   
      if (this.progress < 50) return '#20a0ff';
      if (this.progress < 80) return '#ff9900';
      return '#67c23a';
    }
  },
  methods: {
   
    async handleDownload() {
   
      if (this.selectedFiles.length === 0) {
   
        this.$message.warning('请选择要下载的文件');
        return;
      }

      this.isLoading = true;
      this.progress = 0;
      this.failedFiles = [];

      // 模拟进度条
      this.timer = setInterval(() => {
   
        if (this.progress < 90) {
   
          this.progress += Math.random() * 10;
        }
      }, 300);

      try {
   
        await batchDownload(this.selectedFiles, this.zipName);
        this.$message.success('下载任务已启动');
      } catch (error) {
   
        console.error('批量下载出错:', error);
        this.$message.error('批量下载失败,请稍后重试');
      } finally {
   
        this.isLoading = false;
        this.progress = 100;
        clearInterval(this.timer);

        // 发送下载完成事件
        this.$emit('download-complete', {
   
          total: this.selectedFiles.length,
          failed: this.failedFiles.length
        });
      }
    }
  },
  beforeDestroy() {
   
    clearInterval(this.timer);
  }
};
</script>

<style scoped>
.batch-download {
   
  display: inline-flex;
  align-items: center;
  gap: 10px;
}

.file-count {
   
  margin-left: 5px;
  font-size: 12px;
  color: #606266;
}
</style>
 
 

(二)组件使用示例

<template>
  <div class="file-management">
    <el-table 
      :data="fileList" 
      @selection-change="handleSelectionChange"
    >
      <el-table-column type="selection" width="55"></el-table-column>
      <el-table-column label="文件名" prop="name"></el-table-column>
      <el-table-column label="大小" prop="size"></el-table-column>
      <el-table-column label="操作" width="120">
        <template #default="scope">
          <el-button 
            size="mini" 
            @click="downloadSingleFile(scope.row)"
          >
            下载
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <BatchDownload 
      :files="fileList" 
      :selectedFileIds="selectedIds"
      zipName="项目资料.zip"
      @download-complete="handleDownloadComplete"
    />
  </div>
</template>

<script>
import BatchDownload from '@/components/BatchDownload.vue';

export default {
   
  components: {
    BatchDownload },
  data() {
   
    return {
   
      fileList: [],
      selectedIds: []
    };
  },
  methods: {
   
    handleSelectionChange(selection) {
   
      this.selectedIds = selection.map(item => item.id);
    },
    handleDownloadComplete(result) {
   
      console.log('下载完成统计:', result);
    },
    downloadSingleFile(file) {
   
      // 单文件下载逻辑
    }
  }
};
</script>
 
 

三、高级功能扩展

(一)添加下载进度监控

对于大文件或大量文件的下载,可以使用fetchReadableStreamAPI监控下载进度:

// 在batchDownload函数中添加进度监控
const response = await fetch(file.url);
const reader = response.body.getReader();
const contentLength = response.headers.get('Content-Length');
let receivedLength = 0;
const chunks = [];

while (true) {
   
  const {
    done, value } = await reader.read();
  if (done) break;

  chunks.push(value);
  receivedLength += value.length;

  // 更新进度
  const percent = Math.round((receivedLength / contentLength) * 100);
  this.$emit('download-progress', {
    file, percent });
}

const blob = new Blob(chunks);
 
 

(二)支持断点续传

对于特别大的文件,可以结合后端实现断点续传功能:

// 带断点续传的下载函数
async downloadFileWithResume(url, fileName) {
   
  const chunkSize = 1024 * 1024; // 1MB
  let downloadedBytes = 0;

  // 检查是否有已下载的部分
  const storedProgress = localStorage.getItem(`download_progress_${
     fileName}`);
  if (storedProgress) {
   
    downloadedBytes = parseInt(storedProgress);
  }

  const response = await fetch(url, {
   
    headers: {
    Range: `bytes=${
     downloadedBytes}-` }
  });

  const totalBytes = parseInt(response.headers.get('Content-Length')) + downloadedBytes;
  const reader = response.body.getReader();
  const writer = fs.createWriteStream(fileName, {
    flags: 'a' });

  while (true) {
   
    const {
    done, value } = await reader.read();
    if (done) break;

    writer.write(value);
    downloadedBytes += value.length;

    // 保存下载进度
    localStorage.setItem(`download_progress_${
     fileName}`, downloadedBytes);

    // 更新进度条
    const percent = Math.round((downloadedBytes / totalBytes) * 100);
    this.$emit('download-progress', {
    fileName, percent });
  }

  // 下载完成,清除进度记录
  localStorage.removeItem(`download_progress_${
     fileName}`);
}
 
 

四、注意事项

  1. 跨域问题

    • 如果下载的文件来自第三方域名,需要确保对方服务器设置了正确的CORS头
    • 或者通过自己的后端服务器转发请求
  2. 性能考虑

    • 前端打包方案适合小文件批量下载(总大小建议不超过100MB)
    • 大文件或大量文件建议使用后端打包方案
  3. 用户体验优化

    • 添加下载进度提示
    • 提供下载失败的文件列表
    • 支持取消下载功能

通过以上封装和使用方法,你可以在Vue项目中快速集成批量下载功能,并根据实际需求进行定制扩展。

posted @ 2025-07-24 16:09  yijg9998  阅读(67)  评论(0)    收藏  举报