jack hai 1 mes
pai
achega
ec1bcd754f
Modificáronse 1 ficheiros con 107 adicións e 100 borrados
  1. 107 100
      public/export.html

+ 107 - 100
public/export.html

@@ -5,6 +5,8 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>AWS S3 文件夹批量下载工具</title>
     <script src="https://sdk.amazonaws.com/js/aws-sdk-2.938.0.min.js"></script>
+    <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>
         * {
             box-sizing: border-box;
@@ -116,6 +118,14 @@
             background-color: #27ae60;
         }
         
+        .download-zip {
+            background-color: #9b59b6;
+        }
+        
+        .download-zip:hover {
+            background-color: #8e44ad;
+        }
+        
         .clear {
             background-color: #e74c3c;
         }
@@ -293,7 +303,7 @@
 <body>
     <div class="container">
         <h1>AWS S3 文件夹批量下载工具</h1>
-        <p class="description">批量下载指定目录中的文件</p>
+        <p class="description">批量下载指定目录中的文件并打包为ZIP</p>
         
         <div class="stats">
             <div class="stat-item">
@@ -316,7 +326,7 @@
         
         <div class="action-buttons">
             <button id="loadFolders" class="load-btn">加载所有文件夹</button>
-            <button id="downloadAll" class="download-all" disabled>全部下载</button>
+            <button id="downloadZip" class="download-zip" disabled>打包下载ZIP</button>
             <button id="clearAll" class="clear">清空列表</button>
         </div>
         
@@ -355,7 +365,7 @@
         
         // DOM元素
         const loadFoldersBtn = document.getElementById('loadFolders');
-        const downloadAllBtn = document.getElementById('downloadAll');
+        const downloadZipBtn = document.getElementById('downloadZip');
         const clearAllBtn = document.getElementById('clearAll');
         const foldersContainer = document.getElementById('foldersContainer');
         const summary = document.getElementById('summary');
@@ -442,106 +452,114 @@
             }
         }
         
-        // 下载单个文件
-        async function downloadFile(file, folder) {
-            file.status = 'downloading';
-            updateFolderUI(folder);
+        // 下载所有文件并打包为ZIP
+        async function downloadAllAsZip() {
+            summary.style.display = 'block';
+            summaryText.textContent = '正在准备下载...';
+            overallProgress.style.width = '0%';
             
-            try {
-                const params = {
-                    Bucket: bucketName,
-                    Key: file.key
-                };
+            // 收集所有需要下载的文件
+            const allFiles = [];
+            folderData.forEach(folder => {
+                if (folder.status === 'ready' || folder.status === 'partial') {
+                    folder.files.forEach(file => {
+                        if (file.status !== 'completed') {
+                            allFiles.push({
+                                folder: folder.path,
+                                key: file.key,
+                                name: file.name || file.key.split('/').pop()
+                            });
+                        }
+                    });
+                }
+            });
+            
+            if (allFiles.length === 0) {
+                alert('没有可下载的文件');
+                summary.style.display = 'none';
+                return;
+            }
+            
+            summaryText.textContent = `正在下载 ${allFiles.length} 个文件...`;
+            
+            // 创建ZIP文件
+            const zip = new JSZip();
+            let downloadedCount = 0;
+            
+            // 分批下载文件,避免同时发起太多请求
+            const batchSize = 5;
+            for (let i = 0; i < allFiles.length; i += batchSize) {
+                const batch = allFiles.slice(i, i + batchSize);
                 
-                // 获取预签名URL
-                const url = await s3.getSignedUrlPromise('getObject', params);
-                file.url = url;
+                // 下载当前批次的所有文件
+                await Promise.allSettled(batch.map(file => downloadFileForZip(file, zip)));
                 
-                // 创建隐藏的下载链接并触发点击
-                const a = document.createElement('a');
-                a.href = url;
-                a.download = file.name || file.key.split('/').pop();
-                a.style.display = 'none';
-                document.body.appendChild(a);
-                a.click();
-                document.body.removeChild(a);
+                downloadedCount += batch.length;
                 
-                file.status = 'completed';
-                file.progress = 100;
-                folder.completedFiles++;
-                updateFolderUI(folder);
-                updateStats();
+                // 更新进度
+                const progress = (downloadedCount / allFiles.length) * 100;
+                overallProgress.style.width = `${progress}%`;
+                summaryText.textContent = `已下载 ${downloadedCount}/${allFiles.length} 个文件 (${Math.round(progress)}%)`;
                 
-                // 检查文件夹是否全部完成
-                checkFolderCompletion(folder);
-            } catch (error) {
-                console.error('下载文件失败:', error);
-                file.status = 'error';
-                file.error = error.message;
-                updateFolderUI(folder);
-                updateStats();
+                // 添加延迟以避免请求过于频繁
+                await new Promise(resolve => setTimeout(resolve, 500));
             }
-        }
-        
-        // 下载文件夹中的所有文件
-        async function downloadFolder(folder) {
-            folder.status = 'downloading';
-            updateFolderUI(folder);
             
-            for (const file of folder.files) {
-                if (file.status !== 'completed') {
-                    await downloadFile(file, folder);
-                    // 添加延迟以避免请求过于频繁
-                    await new Promise(resolve => setTimeout(resolve, 500));
-                }
-            }
-        }
-        
-        // 下载所有文件夹
-        async function downloadAllFolders() {
-            summary.style.display = 'block';
-            const totalFiles = folderData.reduce((sum, folder) => sum + folder.files.length, 0);
-            let completedFiles = 0;
+            summaryText.textContent = '正在生成ZIP文件...';
             
-            summaryText.textContent = `正在下载 ${totalFiles} 个文件...`;
+            // 生成ZIP文件
+            const zipBlob = await zip.generateAsync({ type: 'blob' });
             
-            for (const folder of folderData) {
+            // 下载ZIP文件
+            saveAs(zipBlob, 'folders_download.zip');
+            summaryText.textContent = `下载完成!共打包 ${allFiles.length} 个文件`;
+            
+            // 更新文件夹状态
+            folderData.forEach(folder => {
                 if (folder.status === 'ready' || folder.status === 'partial') {
-                    folder.status = 'downloading';
+                    folder.status = 'completed';
+                    folder.completedFiles = folder.fileCount;
                     updateFolderUI(folder);
-                    
-                    for (const file of folder.files) {
-                        if (file.status !== 'completed') {
-                            await downloadFile(file, folder);
-                            completedFiles++;
-                            
-                            // 更新总进度
-                            const progress = (completedFiles / totalFiles) * 100;
-                            overallProgress.style.width = `${progress}%`;
-                            summaryText.textContent = `已下载 ${completedFiles}/${totalFiles} 个文件 (${Math.round(progress)}%)`;
-                            
-                            // 添加延迟以避免请求过于频繁
-                            await new Promise(resolve => setTimeout(resolve, 500));
-                        }
-                    }
                 }
-            }
+            });
             
-            summaryText.textContent = `下载完成!共下载 ${completedFiles} 个文件`;
-            downloadAllBtn.disabled = true;
+            updateStats();
+            updateDownloadButtonState();
         }
         
-        // 检查文件夹是否全部完成
-        function checkFolderCompletion(folder) {
-            if (folder.completedFiles === folder.fileCount) {
-                folder.status = 'completed';
-            } else if (folder.completedFiles > 0) {
-                folder.status = 'partial';
+        // 下载单个文件并添加到ZIP
+        async function downloadFileForZip(file, zip) {
+            try {
+                const params = {
+                    Bucket: bucketName,
+                    Key: file.key
+                };
+                
+                const data = await s3.getObject(params).promise();
+                
+                // 将文件添加到ZIP中,保持文件夹结构
+                const folderPath = file.folder + '/';
+                const filePath = folderPath + file.name;
+                
+                zip.file(filePath, data.Body);
+                
+                // 更新对应文件夹的完成状态
+                const folder = folderData.find(f => f.path === file.folder);
+                if (folder) {
+                    const fileObj = folder.files.find(f => f.key === file.key);
+                    if (fileObj) {
+                        fileObj.status = 'completed';
+                        fileObj.progress = 100;
+                        folder.completedFiles++;
+                        updateFolderUI(folder);
+                    }
+                }
+                
+                return true;
+            } catch (error) {
+                console.error(`下载文件 ${file.key} 失败:`, error);
+                return false;
             }
-            
-            updateFolderUI(folder);
-            updateDownloadButtonState();
-            updateStats();
         }
         
         // 更新文件夹UI
@@ -552,7 +570,6 @@
             const statusElement = folderElement.querySelector('.folder-status');
             const progressBar = folderElement.querySelector('.progress');
             const fileCountElement = folderElement.querySelector('.file-count');
-            const downloadBtn = folderElement.querySelector('.download-btn');
             const fileList = folderElement.querySelector('.file-list');
             
             // 更新状态
@@ -571,7 +588,6 @@
                 case 'ready':
                     statusText = '准备下载';
                     statusClass = 'status-ready';
-                    downloadBtn.disabled = false;
                     break;
                 case 'empty':
                     statusText = '空文件夹';
@@ -580,17 +596,14 @@
                 case 'downloading':
                     statusText = '下载中';
                     statusClass = 'status-downloading';
-                    downloadBtn.disabled = true;
                     break;
                 case 'completed':
                     statusText = '已完成';
                     statusClass = 'status-completed';
-                    downloadBtn.disabled = true;
                     break;
                 case 'partial':
                     statusText = '部分完成';
                     statusClass = 'status-ready';
-                    downloadBtn.disabled = false;
                     break;
                 case 'error':
                     statusText = '错误';
@@ -675,12 +688,10 @@
                     </div>
                     <button class="expand-btn">显示文件</button>
                     <div class="file-list"></div>
-                    <button class="download-btn" disabled style="margin-top: 10px; width: 100%;">下载文件夹</button>
                 `;
                 
                 // 添加事件监听器
                 const expandBtn = folderElement.querySelector('.expand-btn');
-                const downloadBtn = folderElement.querySelector('.download-btn');
                 const fileList = folderElement.querySelector('.file-list');
                 
                 expandBtn.addEventListener('click', () => {
@@ -695,10 +706,6 @@
                     }
                 });
                 
-                downloadBtn.addEventListener('click', () => {
-                    downloadFolder(folder);
-                });
-                
                 foldersContainer.appendChild(folderElement);
                 updateFolderUI(folder);
             });
@@ -717,7 +724,7 @@
             const hasReadyFolders = folderData.some(f => 
                 f.status === 'ready' || f.status === 'partial'
             );
-            downloadAllBtn.disabled = !hasReadyFolders;
+            downloadZipBtn.disabled = !hasReadyFolders;
         }
         
         // 格式化文件大小
@@ -733,7 +740,7 @@
         
         // 事件监听器
         loadFoldersBtn.addEventListener('click', loadAllFolders);
-        downloadAllBtn.addEventListener('click', downloadAllFolders);
+        downloadZipBtn.addEventListener('click', downloadAllAsZip);
         
         clearAllBtn.addEventListener('click', () => {
             if (confirm('确定要清空所有文件夹吗?')) {