方法一:使用JSZip和FileSaver(推荐)
1. 安装依赖
bash
npm install jszip file-saver
# 或使用CDN
2. HTML结构
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JS文件批量下载</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script>
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.file-list {
margin: 20px 0;
padding: 10px;
border: 1px solid #ddd;
max-height: 300px;
overflow-y: auto;
}
.file-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
border-bottom: 1px solid #eee;
}
.progress-bar {
width: 100%;
height: 20px;
background-color: #f0f0f0;
border-radius: 10px;
margin: 10px 0;
overflow: hidden;
}
.progress {
height: 100%;
background-color: #4CAF50;
width: 0%;
transition: width 0.3s;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>JS文件批量下载器</h1>
<!-- 文件输入区域 -->
<div>
<h3>选择JS文件</h3>
<input type="file" id="fileInput" multiple accept=".js" />
<button onclick="addFiles()">添加文件</button>
</div>
<!-- URL输入区域 -->
<div style="margin-top: 20px;">
<h3>或输入JS文件URL</h3>
<div style="display: flex; gap: 10px;">
<input type="text" id="urlInput" placeholder="输入JS文件URL" style="flex: 1; padding: 8px;" />
<button onclick="addURL()">添加URL</button>
</div>
</div>
<!-- 文件列表 -->
<div class="file-list" id="fileList">
<p>已选择文件:<span id="fileCount">0</span>个</p>
</div>
<!-- 进度条 -->
<div id="progressContainer" style="display: none;">
<div class="progress-bar">
<div class="progress" id="progress"></div>
</div>
<p id="progressText">准备中...</p>
</div>
<!-- 下载按钮 -->
<div style="margin-top: 20px;">
<button onclick="downloadAllAsZip()" id="downloadBtn" disabled>打包下载ZIP</button>
<button onclick="clearFiles()" style="background-color: #f44336; margin-left: 10px;">清空列表</button>
</div>
</div>
<script src="main.js"></script>
</body>
</html>
3. JavaScript主逻辑 (main.js)
javascript
let files = [];
let fileCounter = 1;
// 添加本地文件
function addFiles() {
const fileInput = document.getElementById('fileInput');
const selectedFiles = Array.from(fileInput.files);
selectedFiles.forEach(file => {
if (file.type === 'application/javascript' || file.name.endsWith('.js')) {
files.push({
id: fileCounter++,
name: file.name,
content: file,
type: 'local'
});
}
});
updateFileList();
fileInput.value = '';
}
// 添加URL文件
async function addURL() {
const urlInput = document.getElementById('urlInput');
const url = urlInput.value.trim();
if (!url) {
alert('请输入有效的URL');
return;
}
if (!url.endsWith('.js')) {
alert('请输入JS文件的URL');
return;
}
// 从URL提取文件名
const fileName = url.split('/').pop() || `file_${fileCounter}.js`;
files.push({
id: fileCounter++,
name: fileName,
url: url,
type: 'url'
});
updateFileList();
urlInput.value = '';
}
// 更新文件列表显示
function updateFileList() {
const fileList = document.getElementById('fileList');
const fileCount = document.getElementById('fileCount');
const downloadBtn = document.getElementById('downloadBtn');
fileList.innerHTML = '<p>已选择文件:<span id="fileCount">' + files.length + '</span>个</p>';
files.forEach(file => {
const div = document.createElement('div');
div.className = 'file-item';
div.innerHTML = `
<span>${file.name}</span>
<button onclick="removeFile(${file.id})" style="background-color: #ff4444; padding: 4px 8px; font-size: 12px;">
删除
</button>
`;
fileList.appendChild(div);
});
fileCount.textContent = files.length;
downloadBtn.disabled = files.length === 0;
}
// 移除文件
function removeFile(id) {
files = files.filter(file => file.id !== id);
updateFileList();
}
// 清空所有文件
function clearFiles() {
if (confirm('确定要清空所有文件吗?')) {
files = [];
updateFileList();
}
}
// 主下载函数
async function downloadAllAsZip() {
if (files.length === 0) {
alert('请先添加文件');
return;
}
const progressContainer = document.getElementById('progressContainer');
const progressBar = document.getElementById('progress');
const progressText = document.getElementById('progressText');
const downloadBtn = document.getElementById('downloadBtn');
// 显示进度条
progressContainer.style.display = 'block';
downloadBtn.disabled = true;
try {
const zip = new JSZip();
let processed = 0;
// 处理每个文件
for (const file of files) {
progressText.textContent = `正在处理: ${file.name} (${processed + 1}/${files.length})`;
if (file.type === 'local') {
// 本地文件
const content = await readFileAsText(file.content);
zip.file(file.name, content);
} else if (file.type === 'url') {
// 远程文件
try {
const response = await fetch(file.url);
if (!response.ok) {
throw new Error(`下载失败: ${response.status} ${response.statusText}`);
}
const content = await response.text();
zip.file(file.name, content);
} catch (error) {
console.error(`下载 ${file.url} 失败:`, error);
zip.file(file.name, `// 下载失败: ${error.message}\n// 原始URL: ${file.url}`);
}
}
processed++;
const progressPercent = (processed / files.length) * 100;
progressBar.style.width = `${progressPercent}%`;
}
// 生成ZIP文件
progressText.textContent = '正在生成ZIP文件...';
const zipBlob = await zip.generateAsync({
type: 'blob',
compression: 'DEFLATE',
compressionOptions: {
level: 6
}
}, (metadata) => {
if (metadata.percent) {
progressBar.style.width = `${metadata.percent}%`;
progressText.textContent = `正在压缩: ${Math.round(metadata.percent)}%`;
}
});
// 下载ZIP文件
progressText.textContent = '正在下载...';
saveAs(zipBlob, `js-files-${new Date().toISOString().slice(0, 10)}.zip`);
progressText.textContent = '下载完成!';
setTimeout(() => {
progressContainer.style.display = 'none';
progressBar.style.width = '0%';
downloadBtn.disabled = false;
}, 2000);
} catch (error) {
console.error('打包失败:', error);
alert(`打包失败: ${error.message}`);
progressContainer.style.display = 'none';
downloadBtn.disabled = false;
}
}
// 读取本地文件为文本
function readFileAsText(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = (e) => reject(new Error('文件读取失败'));
reader.readAsText(file);
});
}
方法二:使用StreamSaver处理大文件
如果需要处理非常大的文件,可以使用StreamSaver:
html
<!-- 在head中添加 -->
<script src="https://cdn.jsdelivr.net/npm/web-streams-polyfill@2.0.2/dist/ponyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/streamsaver@2.0.3/StreamSaver.min.js"></script>
javascript
// 替换之前的下载函数
async function downloadLargeFilesAsZip() {
if (files.length === 0) return;
// 创建可写流
const fileStream = streamSaver.createWriteStream('js-files.zip');
const writer = fileStream.getWriter();
// 使用JSZip生成流
const zip = new JSZip();
// 添加文件到zip...
// ... 处理文件逻辑
// 生成并写入流
zip.generateInternalStream({
type: 'blob',
streamFiles: true
}).on('data', (chunk) => {
writer.write(chunk);
}).on('end', () => {
writer.close();
}).resume();
}
方法三:使用Node.js后端打包
如果需要处理大量文件或跨域问题,可以使用Node.js后端:
javascript
// server.js
const express = require('express');
const archiver = require('archiver');
const axios = require('axios');
const app = express();
app.post('/download-js', async (req, res) => {
const { urls } = req.body;
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', 'attachment; filename=js-files.zip');
const archive = archiver('zip', {
zlib: { level: 9 }
});
archive.pipe(res);
for (const url of urls) {
try {
const response = await axios.get(url, { responseType: 'stream' });
const filename = url.split('/').pop();
archive.append(response.data, { name: filename });
} catch (error) {
console.error(`Failed to fetch ${url}:`, error);
}
}
archive.finalize();
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
使用说明
-
本地文件:选择本地的JS文件
-
远程文件:输入JS文件的URL地址
-
打包下载:点击"打包下载ZIP"按钮
-
清空列表:点击"清空列表"按钮移除所有文件
注意事项
-
跨域问题:如果JS文件来自不同源且没有CORS头,可能无法下载
-
文件大小:前端处理大量大文件时可能内存不足
-
浏览器兼容性:现代浏览器支持良好,IE需要polyfill