一、批量下载功能使用方法
(一)方案一:前端打包方案(file-saver + jszip)
- 安装依赖
npm install file-saver jszip
- 创建工具函数
在项目中创建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')}`);
}
};
- 在组件中使用
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');
}
}
};
(二)方案二:后端打包方案
- 前端调用示例
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('批量下载失败,请稍后重试');
}
}
}
};
- 后端接口要求
- 接收文件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>
三、高级功能扩展
(一)添加下载进度监控
对于大文件或大量文件的下载,可以使用fetch
的ReadableStream
API监控下载进度:
// 在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}`);
}
四、注意事项
跨域问题
- 如果下载的文件来自第三方域名,需要确保对方服务器设置了正确的CORS头
- 或者通过自己的后端服务器转发请求
性能考虑
- 前端打包方案适合小文件批量下载(总大小建议不超过100MB)
- 大文件或大量文件建议使用后端打包方案
用户体验优化
- 添加下载进度提示
- 提供下载失败的文件列表
- 支持取消下载功能
通过以上封装和使用方法,你可以在Vue项目中快速集成批量下载功能,并根据实际需求进行定制扩展。