JS文件批量下载并打包成ZIP的功能

方法一:使用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');
});

使用说明

  1. 本地文件:选择本地的JS文件

  2. 远程文件:输入JS文件的URL地址

  3. 打包下载:点击"打包下载ZIP"按钮

  4. 清空列表:点击"清空列表"按钮移除所有文件

注意事项

  1. 跨域问题:如果JS文件来自不同源且没有CORS头,可能无法下载

  2. 文件大小:前端处理大量大文件时可能内存不足

  3. 浏览器兼容性:现代浏览器支持良好,IE需要polyfill

相关推荐
xiaoxue..2 小时前
React 新手村通关指南:状态、组件与魔法 UI
前端·javascript·react.js·ui
蓝色汪洋2 小时前
luogu填坑
开发语言·c++·算法
咖啡の猫2 小时前
Python列表推导式
开发语言·python
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于PHP的高校心理测评系统的设计与实现为例,包含答辩的问题和答案
开发语言·php
while(1){yan}2 小时前
网络编程UDP
java·开发语言·网络·网络协议·青少年编程·udp·电脑常识
大猫子的技术日记2 小时前
【工具篇】极简入门 UV Python项目管理工具
开发语言·python·uv
滿2 小时前
Vue3 ElementPlus el-select 焦点事件数据不回显问题
javascript·vue.js·elementui
小年糕是糕手2 小时前
【C++同步练习】类和对象(三)
开发语言·jvm·c++·程序人生·考研·算法·改行学it
代码or搬砖2 小时前
Vue生命周期总结(四个阶段,八个钩子函数)
前端·javascript·vue.js