方法一:使用JSZip和FileSaver(推荐)
1. 安装依赖
npm install jszip file-saver # 或使用CDN2. 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)
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:
<!-- 在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>// 替换之前的下载函数 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后端:
// 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