// DOM Elements const uploadBtn = document.getElementById('uploadBtn'); const mkdirBtn = document.getElementById('mkdirBtn'); const refreshBtn = document.getElementById('refreshBtn'); const fileInput = document.getElementById('fileInput'); const uploadZone = document.getElementById('uploadZone'); const fileList = document.getElementById('fileList'); // Modals const mkdirModal = document.getElementById('mkdirModal'); const deleteModal = document.getElementById('deleteModal'); const folderNameInput = document.getElementById('folderNameInput'); const deleteFileName = document.getElementById('deleteFileName'); // Progress const uploadProgress = document.getElementById('uploadProgress'); const progressText = document.getElementById('progressText'); const progressFill = document.getElementById('progressFill'); // 👇 新增这两行(对应你 index.html 里新增的 span) const progressPercent = document.getElementById('progressPercent'); const uploadSpeed = document.getElementById('uploadSpeed'); // Toast const toast = document.getElementById('toast'); const toastIcon = document.getElementById('toastIcon'); const toastMessage = document.getElementById('toastMessage'); // State let deleteTarget = null; let deleteType = null; // Toast function function showToast(message, type = 'success') { const toastEl = document.getElementById('toast'); const msgEl = document.getElementById('toastMessage'); const iconEl = document.getElementById('toastIcon'); // 容错处理:如果找不到 Toast 元素,直接在控制台打印,避免阻塞后续代码 if (!toastEl || !msgEl || !iconEl) { console.warn('Toast elements not found. Message:', message); return; } msgEl.textContent = message; iconEl.className = 'toast-icon fas '; if (type === 'success') { iconEl.classList.add('fa-check-circle', 'success'); } else if (type === 'error') { iconEl.classList.add('fa-exclamation-circle', 'error'); } else { iconEl.classList.add('fa-info-circle', 'info'); } toastEl.classList.add('show'); setTimeout(() => { toastEl.classList.remove('show'); }, 3000); } // Upload functions (修改版:支持百分比和网速) function uploadFiles(files) { if (files.length === 0) return; uploadProgress.classList.add('show'); let uploaded = 0; let failed = 0; const uploadNext = (index) => { if (index >= files.length) { setTimeout(() => { uploadProgress.classList.remove('show'); if (failed > 0) { showToast(`${uploaded} 个文件上传成功,${failed} 个失败`, 'error'); } else { showToast(`${uploaded} 个文件上传成功!`, 'success'); } updateStorageInfo(); // 上传完成后更新容量 refreshFileList(); }, 500); return; } const file = files[index]; progressText.textContent = `正在上传: ${file.name} (${index + 1}/${files.length})`; progressFill.style.width = '0%'; progressPercent.textContent = '0%'; uploadSpeed.textContent = '0 KB/s'; const formData = new FormData(); formData.append('file', file); formData.append('path', currentPath); const xhr = new XMLHttpRequest(); let lastLoaded = 0; let lastTime = Date.now(); // 监听上传进度 xhr.upload.onprogress = function(event) { if (event.lengthComputable) { const percent = Math.round((event.loaded / event.total) * 100); progressPercent.textContent = percent + '%'; progressFill.style.width = percent + '%'; const now = Date.now(); const timeDiff = (now - lastTime) / 1000; if (timeDiff > 0.5) { const byteDiff = event.loaded - lastLoaded; const speedBps = byteDiff / timeDiff; uploadSpeed.textContent = formatSpeed(speedBps); lastLoaded = event.loaded; lastTime = now; } } }; xhr.onload = function() { if (xhr.status === 200) { try { const data = JSON.parse(xhr.responseText); if (data.success) { uploaded++; } else { failed++; console.error('后端上传失败:', data.error); } } catch (e) { failed++; console.error('解析响应失败:', e); } } else { failed++; console.error('上传请求失败, 状态码:', xhr.status); } uploadNext(index + 1); // 无论成功失败,继续下一个 }; xhr.open('POST', '/api/upload', true); xhr.send(formData); }; // 👈 这里闭合 uploadNext 箭头函数 uploadNext(0); } // 👈 这里闭合 uploadFiles 函数 // 网速格式化辅助函数 function formatSpeed(bytesPerSecond) { if (bytesPerSecond < 1024) return bytesPerSecond.toFixed(1) + ' B/s'; if (bytesPerSecond < 1024 * 1024) return (bytesPerSecond / 1024).toFixed(1) + ' KB/s'; return (bytesPerSecond / (1024 * 1024)).toFixed(2) + ' MB/s'; } // Refresh file list function refreshFileList() { fetch(`/api/list?path=${encodeURIComponent(currentPath)}`) .then(response => response.json()) .then(data => { if (data.success) { renderFileList(data.files); } }) .catch(error => { console.error('Refresh error:', error); showToast('刷新失败', 'error'); }); } // Render file list function renderFileList(files) { const container = document.querySelector('.file-list-container'); if (!files || files.length === 0) { container.innerHTML = `

此文件夹为空

上传文件或创建文件夹开始使用

`; return; } // Sort: directories first files.sort((a, b) => { if (a.type === b.type) { return a.name.localeCompare(b.name); } return a.type === 'directory' ? -1 : 1; }); const tableHtml = ` ${files.map(file => ` `).join('')}
名称 大小 修改时间 操作
${file.type === 'directory' ? ` ${file.name}` : ` ${file.name}` } ${formatSize(file.size)} ${file.modified || '-'} ${file.type === 'file' ? ` ` : '' }
`; container.innerHTML = tableHtml; // Re-attach event listeners attachFileEventListeners(); } // Format file size function formatSize(sizeBytes) { if (!sizeBytes || sizeBytes === 0 || sizeBytes === '-') { return '-'; } try { const size = parseInt(sizeBytes); const units = ['B', 'KB', 'MB', 'GB', 'TB']; let i = 0; let s = size; while (s >= 1024 && i < units.length - 1) { s /= 1024; i++; } return s.toFixed(2) + ' ' + units[i]; } catch { return '-'; } } // Attach event listeners to file items function attachFileEventListeners() { // Download buttons document.querySelectorAll('.download-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const filename = btn.dataset.filename; downloadFile(filename); }); }); // 👇 新增:查看按钮监听 document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); viewFile(btn.dataset.filename); }); }); // 👇 新增:移动按钮监听 document.querySelectorAll('.move-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); moveFile(btn.dataset.filename); }); }); // Delete buttons document.querySelectorAll('.delete-btn').forEach(btn => { btn.addEventListener('click', (e) => { e.stopPropagation(); const filename = btn.dataset.filename; const type = btn.dataset.type; showDeleteModal(filename, type); }); }); } // Download file function downloadFile(filename) { const url = `/api/download?filename=${encodeURIComponent(filename)}&path=${encodeURIComponent(currentPath)}`; window.location.href = url; } // 移动文件 function moveFile(filename) { if (!filename || filename === 'undefined') return; const targetDir = prompt(`将 [${filename}] 移动到哪个文件夹?(如:documents,留空表示根目录)`, ""); if (targetDir === null) return; fetch('/api/move', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: filename, source_path: currentPath, target_dir: targetDir }) }) .then(res => { if (!res.ok) throw new Error('接口不存在或请求失败'); return res.json(); }) .then(data => { if (data.success) { showToast('移动成功', 'success'); refreshFileList(); } else { showToast(data.error || '移动失败', 'error'); } }) .catch(error => { console.error('Move error:', error); showToast('移动失败,接口未找到', 'error'); }); } // 查看文本文件 function viewFile(filename) { if (!filename || filename === 'undefined') return; fetch(`/api/view?filename=${encodeURIComponent(filename)}&path=${encodeURIComponent(currentPath)}`) .then(res => { if (!res.ok) throw new Error('接口不存在或请求失败'); return res.json(); }) .then(data => { if (data.success) { // 如果 index.html 中有 view-modal,则显示 const viewModal = document.getElementById('view-modal'); if (viewModal) { document.getElementById('view-filename').textContent = data.filename; document.getElementById('view-content').textContent = data.content; viewModal.style.display = 'block'; } else { // 如果还没有在 HTML 中添加模态框,先用 alert 简单展示 alert(`文件名: ${data.filename}\n\n${data.content}`); } } else { showToast(data.error || '无法查看该文件', 'error'); } }) .catch(error => { console.error('View error:', error); showToast('查看失败,接口未找到', 'error'); }); } // Create directory function createDirectory(dirname) { fetch('/api/mkdir', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dirname: dirname, path: currentPath }) }) .then(response => response.json()) .then(data => { if (data.success) { showToast('文件夹创建成功!', 'success'); updateStorageInfo(); // 👈 加在这里,创建成功后更新容量 closeMkdirModal(); refreshFileList(); } else { showToast(data.error || '创建失败', 'error'); } }) .catch(error => { console.error('Mkdir error:', error); showToast('创建失败', 'error'); }); } // Delete file/directory function deleteItem(filename) { fetch('/api/delete', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: filename, path: currentPath }) }) .then(response => response.json()) .then(data => { if (data.success) { showToast('删除成功!', 'success'); updateStorageInfo(); // 👈 加在这里,删除成功后更新容量 closeDeleteModal(); refreshFileList(); } else { showToast(data.error || '删除失败', 'error'); } }) .catch(error => { console.error('Delete error:', error); showToast('删除失败', 'error'); }); } // Modal functions function showMkdirModal() { mkdirModal.classList.add('show'); folderNameInput.value = ''; setTimeout(() => folderNameInput.focus(), 100); } function closeMkdirModal() { mkdirModal.classList.remove('show'); } function showDeleteModal(filename, type) { deleteTarget = filename; deleteType = type; deleteFileName.textContent = filename; deleteModal.classList.add('show'); } function closeDeleteModal() { deleteModal.classList.remove('show'); deleteTarget = null; deleteType = null; } // Event Listeners // Upload button uploadBtn.addEventListener('click', () => { fileInput.click(); }); fileInput.addEventListener('change', (e) => { if (e.target.files.length > 0) { uploadFiles(e.target.files); fileInput.value = ''; } }); // Upload zone click uploadZone.addEventListener('click', () => { fileInput.click(); }); // Drag and drop uploadZone.addEventListener('dragover', (e) => { e.preventDefault(); uploadZone.classList.add('drag-over'); }); uploadZone.addEventListener('dragleave', (e) => { e.preventDefault(); uploadZone.classList.remove('drag-over'); }); uploadZone.addEventListener('drop', (e) => { e.preventDefault(); uploadZone.classList.remove('drag-over'); if (e.dataTransfer.files.length > 0) { uploadFiles(e.dataTransfer.files); } }); // Mkdir button mkdirBtn.addEventListener('click', showMkdirModal); document.getElementById('closeMkdirModal').addEventListener('click', closeMkdirModal); document.getElementById('cancelMkdir').addEventListener('click', closeMkdirModal); document.getElementById('confirmMkdir').addEventListener('click', () => { const dirname = folderNameInput.value.trim(); if (dirname) { createDirectory(dirname); } else { showToast('请输入文件夹名称', 'error'); } }); folderNameInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { const dirname = folderNameInput.value.trim(); if (dirname) { createDirectory(dirname); } } }); // Delete modal document.getElementById('closeDeleteModal').addEventListener('click', closeDeleteModal); document.getElementById('cancelDelete').addEventListener('click', closeDeleteModal); document.getElementById('confirmDelete').addEventListener('click', () => { if (deleteTarget) { deleteItem(deleteTarget); } }); // Refresh button refreshBtn.addEventListener('click', () => { refreshBtn.querySelector('i').classList.add('fa-spin'); refreshFileList(); setTimeout(() => { refreshBtn.querySelector('i').classList.remove('fa-spin'); }, 500); }); // Close modals on outside click mkdirModal.addEventListener('click', (e) => { if (e.target === mkdirModal) { closeMkdirModal(); } }); deleteModal.addEventListener('click', (e) => { if (e.target === deleteModal) { closeDeleteModal(); } }); // Keyboard shortcuts document.addEventListener('keydown', (e) => { // Escape to close modals if (e.key === 'Escape') { closeMkdirModal(); closeDeleteModal(); } }); // Initialize document.addEventListener('DOMContentLoaded', () => { attachFileEventListeners(); initUserFeatures(); }); // User features function initUserFeatures() { // Logout button const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', logout); } } // Logout function function logout() { fetch('/api/logout', { method: 'POST', headers: { 'Content-Type': 'application/json' } }) .then(response => response.json()) .then(data => { if (data.success) { showToast('已退出登录', 'success'); setTimeout(() => { window.location.href = '/login'; }, 500); } else { showToast(data.error || '退出失败', 'error'); } }) .catch(error => { console.error('Logout error:', error); showToast('退出失败', 'error'); }); } // Update storage info function updateStorageInfo() { fetch('/api/storage-info') .then(response => response.json()) .then(data => { if (data.success) { const storageFill = document.querySelector('.storage-fill'); const storageText = document.querySelector('.storage-text'); if (storageFill) { storageFill.style.width = data.storage_percent + '%'; } if (storageText) { storageText.textContent = `${data.used_storage_formatted} / ${data.max_storage_formatted}`; } } }) .catch(error => { console.error('Storage info error:', error); }); }