(function() { 'use strict'; const API_BASE = '/api'; let currentPath = '/'; let selectedFiles = new Set(); let eventSource = null; let isUploading = false; const elements = { fileListBody: document.getElementById('file-list-body'), breadcrumb: document.getElementById('breadcrumb'), pathDisplay: document.getElementById('path-display'), connectionStatus: document.getElementById('connection-status'), btnUpload: document.getElementById('btn-upload'), btnDownload: document.getElementById('btn-download'), btnDelete: document.getElementById('btn-delete'), btnMove: document.getElementById('btn-move'), btnRefresh: document.getElementById('btn-refresh'), btnNewDir: document.getElementById('btn-new-dir'), selectAll: document.getElementById('select-all'), fileInput: document.getElementById('file-input'), dropZone: document.getElementById('drop-zone'), previewModal: document.getElementById('preview-modal'), previewTitle: document.getElementById('preview-title'), previewBody: document.getElementById('preview-body'), previewClose: document.getElementById('preview-close'), moveModal: document.getElementById('move-modal'), moveClose: document.getElementById('move-close'), moveConfirm: document.getElementById('move-confirm'), moveCancel: document.getElementById('move-cancel'), moveFilename: document.getElementById('move-filename'), moveBreadcrumb: document.getElementById('move-breadcrumb'), dirTree: document.getElementById('dir-tree'), moveSource: document.getElementById('move-source'), moveDest: document.getElementById('move-dest'), newDirModal: document.getElementById('new-dir-modal'), newDirClose: document.getElementById('new-dir-close'), newDirConfirm: document.getElementById('new-dir-confirm'), newDirCancel: document.getElementById('new-dir-cancel'), newDirName: document.getElementById('new-dir-name'), renameModal: document.getElementById('rename-modal'), renameClose: document.getElementById('rename-close'), renameConfirm: document.getElementById('rename-confirm'), renameCancel: document.getElementById('rename-cancel'), renameName: document.getElementById('rename-name'), renamePath: document.getElementById('rename-path'), uploadModal: document.getElementById('upload-modal'), uploadClose: document.getElementById('upload-close'), uploadCancel: document.getElementById('upload-cancel'), uploadConfirm: document.getElementById('upload-confirm'), uploadList: document.getElementById('upload-list'), uploadProgressContainer: document.getElementById('upload-progress-container'), uploadProgressBar: document.getElementById('upload-progress-bar'), uploadStats: document.getElementById('upload-stats'), uploadSpeed: document.getElementById('upload-speed'), searchInput: document.getElementById('search-input'), searchBtn: document.getElementById('search-btn'), notification: document.getElementById('notification') }; let uploadXhrs = []; function init() { loadFiles(currentPath); setupEventListeners(); startWatch(); } function loadFiles(path) { elements.fileListBody.innerHTML = '加载中...'; fetch(API_BASE + '/files?path=' + encodeURIComponent(path)) .then(function(response) { return response.json(); }) .then(function(data) { renderFileList(data.files); updateBreadcrumb(path); elements.pathDisplay.textContent = path || '/'; }) .catch(function(error) { showNotification('加载失败: ' + error.message, 'error'); elements.fileListBody.innerHTML = '加载失败'; }); } function renderFileList(files) { var html = ''; if (currentPath !== '/') { html += '' + '' + '↩️返回上级' + '-' + '-' + '' + ''; } if (!files || files.length === 0) { if (currentPath === '/') { elements.fileListBody.innerHTML = '📂此目录为空'; } else { elements.fileListBody.innerHTML = html + '📂此目录为空'; } return; } var dirs = files.filter(function(f) { return f.isDir; }).sort(function(a, b) { return a.name.localeCompare(b.name); }); var regularFiles = files.filter(function(f) { return !f.isDir; }).sort(function(a, b) { return a.name.localeCompare(b.name); }); var sortedFiles = dirs.concat(regularFiles); html += sortedFiles.map(function(file) { return '' + '' + '' + getFileIcon(file.name, file.isDir) + '' + escapeHtml(file.name) + '' + '' + (file.isDir ? '-' : formatFileSize(file.size)) + '' + '' + formatTime(file.modTime) + '' + '
' + (file.isDir ? '' : '') + (file.canPreview ? '' : '') + '' + '' + '
'; }).join(''); elements.fileListBody.innerHTML = html; updateButtonStates(); } function getFileIcon(name, isDir) { if (isDir) return '📁'; var ext = name.split('.').pop().toLowerCase(); var icons = { pdf: '📕', doc: '📘', docx: '📘', xls: '📗', xlsx: '📗', ppt: '📙', pptx: '📙', txt: '📄', md: '📝', html: '🌐', htm: '🌐', css: '🎨', js: '📜', json: '{ }', xml: '📋', png: '🖼️', jpg: '🖼️', jpeg: '🖼️', gif: '🖼️', bmp: '🖼️', webp: '🖼️', mp3: '🎵', wav: '🎵', ogg: '🎵', flac: '🎵', mp4: '🎬', avi: '🎬', mkv: '🎬', mov: '🎬', webm: '🎬', zip: '📦', rar: '📦', '7z': '📦', tar: '📦', gz: '📦', exe: '⚙️', app: '⚙️', sh: '💻', go: '🔷', py: '🐍' }; return icons[ext] || '📄'; } function formatFileSize(bytes) { if (bytes === 0) return '0 B'; var k = 1024; var sizes = ['B', 'KB', 'MB', 'GB']; var i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function formatTime(timeStr) { var date = new Date(timeStr); var now = new Date(); var isToday = date.toDateString() === now.toDateString(); if (isToday) { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } function escapeHtml(text) { var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function updateBreadcrumb(path) { if (!path || path === '/') { elements.breadcrumb.innerHTML = '根目录'; return; } var parts = path.split('/').filter(function(p) { return p; }); var html = '根目录'; parts.forEach(function(part, index) { var currentPath = '/' + parts.slice(0, index + 1).join('/'); html += '' + escapeHtml(part) + ''; }); elements.breadcrumb.innerHTML = html; } function updateButtonStates() { var count = selectedFiles.size; elements.btnDownload.disabled = count === 0; elements.btnDelete.disabled = count === 0; elements.btnMove.disabled = count !== 1; } function setupEventListeners() { elements.breadcrumb.addEventListener('click', function(e) { if (e.target.classList.contains('crumb')) { currentPath = e.target.dataset.path; loadFiles(currentPath); } }); elements.fileListBody.addEventListener('click', function(e) { var checkbox = e.target.closest('.file-checkbox'); var fileName = e.target.closest('.file-name'); var actionBtn = e.target.closest('.action-btn'); if (checkbox) { toggleFileSelection(checkbox.dataset.path, checkbox.checked); } else if (fileName) { var isDir = fileName.dataset.isDir === 'true'; var path = fileName.dataset.path; if (path === '..') { if (currentPath !== '/') { var parts = currentPath.split('/').filter(function(p) { return p; }); parts.pop(); currentPath = '/' + parts.join('/') || '/'; loadFiles(currentPath); } } else if (isDir) { currentPath = path; loadFiles(currentPath); } } else if (actionBtn) { var action = actionBtn.dataset.action; var path = actionBtn.dataset.path; if (action === 'download') downloadFile(path); if (action === 'preview') previewFile(path); if (action === 'rename') showRenameModal(path); if (action === 'delete') deleteFiles([path]); } }); elements.selectAll.addEventListener('change', function(e) { var checkboxes = elements.fileListBody.querySelectorAll('.file-checkbox'); checkboxes.forEach(function(cb) { toggleFileSelection(cb.dataset.path, e.target.checked); }); }); elements.btnUpload.addEventListener('click', function() { elements.fileInput.click(); }); elements.btnDownload.addEventListener('click', downloadSelected); elements.btnDelete.addEventListener('click', function() { if (selectedFiles.size > 0 && confirm('确定要删除选中的 ' + selectedFiles.size + ' 个项目吗?')) { deleteFiles(Array.from(selectedFiles)); } }); elements.btnMove.addEventListener('click', function() { if (selectedFiles.size === 1) { showMoveModal(Array.from(selectedFiles)[0]); } }); elements.btnRefresh.addEventListener('click', function() { loadFiles(currentPath); }); elements.btnNewDir.addEventListener('click', function() { elements.newDirName.value = ''; elements.newDirModal.classList.add('active'); elements.newDirName.focus(); }); elements.fileInput.addEventListener('change', function(e) { uploadFiles(e.target.files); e.target.value = ''; }); elements.searchBtn.addEventListener('click', searchFiles); elements.searchInput.addEventListener('keypress', function(e) { if (e.key === 'Enter') searchFiles(); }); elements.searchInput.addEventListener('input', function() { if (elements.searchInput.value === '') { loadFiles(currentPath); } }); setupDragDrop(); setupModals(); } function toggleFileSelection(path, selected) { if (selected) { selectedFiles.add(path); } else { selectedFiles.delete(path); } var checkbox = elements.fileListBody.querySelector('.file-checkbox[data-path="' + path + '"]'); if (checkbox) { checkbox.checked = selected; } updateRowSelections(); updateButtonStates(); updateSelectAllState(); } function updateRowSelections() { var rows = elements.fileListBody.querySelectorAll('tr'); rows.forEach(function(row) { if (selectedFiles.has(row.dataset.path)) { row.classList.add('selected'); } else { row.classList.remove('selected'); } }); } function updateSelectAllState() { var checkboxes = elements.fileListBody.querySelectorAll('.file-checkbox'); var checked = Array.from(checkboxes).filter(function(cb) { return cb.checked; }); elements.selectAll.checked = checkboxes.length > 0 && checked.length === checkboxes.length; } function setupDragDrop() { document.addEventListener('dragenter', function(e) { e.preventDefault(); elements.dropZone.classList.add('active'); }); elements.dropZone.addEventListener('dragleave', function(e) { if (e.target === elements.dropZone) { elements.dropZone.classList.remove('active'); } }); elements.dropZone.addEventListener('dragover', function(e) { e.preventDefault(); }); elements.dropZone.addEventListener('drop', function(e) { e.preventDefault(); elements.dropZone.classList.remove('active'); uploadFiles(e.dataTransfer.files); }); } function setupModals() { elements.previewClose.addEventListener('click', function() { elements.previewModal.classList.remove('active'); }); elements.moveClose.addEventListener('click', function() { elements.moveModal.classList.remove('active'); }); elements.moveCancel.addEventListener('click', function() { elements.moveModal.classList.remove('active'); }); elements.moveConfirm.addEventListener('click', moveFile); elements.newDirClose.addEventListener('click', function() { elements.newDirModal.classList.remove('active'); }); elements.newDirCancel.addEventListener('click', function() { elements.newDirModal.classList.remove('active'); }); elements.newDirConfirm.addEventListener('click', createDirectory); elements.newDirName.addEventListener('keypress', function(e) { if (e.key === 'Enter') createDirectory(); }); elements.renameClose.addEventListener('click', function() { elements.renameModal.classList.remove('active'); }); elements.renameCancel.addEventListener('click', function() { elements.renameModal.classList.remove('active'); }); elements.renameConfirm.addEventListener('click', renameFile); elements.renameName.addEventListener('keypress', function(e) { if (e.key === 'Enter') renameFile(); }); elements.uploadClose.addEventListener('click', function() { if (!isUploading) { elements.uploadModal.classList.remove('active'); } }); elements.uploadCancel.addEventListener('click', function() { if (isUploading) { uploadXhrs.forEach(function(xhr) { xhr.abort(); }); uploadXhrs = []; isUploading = false; elements.uploadModal.classList.remove('active'); } }); elements.uploadConfirm.addEventListener('click', function() { elements.uploadModal.classList.remove('active'); }); document.addEventListener('click', function(e) { if (e.target === elements.previewModal) elements.previewModal.classList.remove('active'); if (e.target === elements.moveModal) elements.moveModal.classList.remove('active'); if (e.target === elements.newDirModal) elements.newDirModal.classList.remove('active'); if (e.target === elements.renameModal) elements.renameModal.classList.remove('active'); if (e.target === elements.uploadModal && !isUploading) elements.uploadModal.classList.remove('active'); }); } function uploadFiles(files) { if (!files || files.length === 0) return; var fileArray = Array.from(files); elements.uploadList.innerHTML = fileArray.map(function(file, index) { return '
' + '' + getFileIcon(file.name, false) + '' + '
' + '
' + escapeHtml(file.name) + '
' + '
' + '' + formatFileSize(file.size) + '' + '等待中...' + '
' + '
' + '
' + '
'; }).join(''); elements.uploadProgressContainer.classList.add('active'); elements.uploadProgressBar.style.width = '0%'; elements.uploadStats.classList.add('active'); elements.uploadSpeed.classList.add('active'); elements.uploadSpeed.textContent = '准备上传...'; elements.uploadCancel.style.display = 'block'; elements.uploadConfirm.style.display = 'none'; elements.uploadModal.classList.add('active'); var total = fileArray.length; var totalBytes = fileArray.reduce(function(sum, f) { return sum + f.size; }, 0); var uploadedBytes = 0; var completedCount = 0; uploadXhrs = []; var loadedBytesMap = fileArray.map(function() { return 0; }); var finishedMap = fileArray.map(function() { return false; }); var lastTime = Date.now(); var lastLoaded = 0; function updateSpeed() { var now = Date.now(); var timeDiff = (now - lastTime) / 1000; if (timeDiff < 0.5) return; var currentLoaded = loadedBytesMap.reduce(function(sum, bytes, i) { return sum + (finishedMap[i] ? fileArray[i].size : bytes); }, 0); var bytesDiff = currentLoaded - lastLoaded; var speed = bytesDiff / timeDiff; if (speed > 0 && completedCount < total) { var remaining = totalBytes - currentLoaded; var eta = remaining / speed; var etaStr; if (eta < 60) etaStr = Math.ceil(eta) + '秒'; else if (eta < 3600) etaStr = Math.ceil(eta / 60) + '分钟'; else etaStr = Math.ceil(eta / 3600) + '小时'; elements.uploadSpeed.textContent = formatFileSize(speed) + '/s · 剩余' + etaStr; } else if (completedCount < total) { elements.uploadSpeed.textContent = '处理中...'; } lastTime = now; lastLoaded = currentLoaded; } fileArray.forEach(function(file, index) { var formData = new FormData(); formData.append('file', file); var xhr = new XMLHttpRequest(); uploadXhrs.push(xhr); xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) { loadedBytesMap[index] = e.loaded; var percent = Math.min(99, Math.round((e.loaded / e.total) * 100)); document.getElementById('upload-progress-text-' + index).textContent = percent + '%'; document.getElementById('upload-item-progress-' + index).style.width = percent + '%'; var currentLoaded = loadedBytesMap.reduce(function(sum, bytes, i) { return sum + (finishedMap[i] ? fileArray[i].size : bytes); }, 0); var allPercent = Math.min(99, Math.round((currentLoaded / totalBytes) * 100)); elements.uploadProgressBar.style.width = allPercent + '%'; updateSpeed(); } }); xhr.addEventListener('load', function() { finishedMap[index] = true; loadedBytesMap[index] = file.size; uploadedBytes += file.size; completedCount++; document.getElementById('upload-progress-text-' + index).textContent = '完成'; document.getElementById('upload-item-progress-' + index).style.background = '#4caf50'; document.getElementById('upload-item-progress-' + index).style.width = '100%'; var allPercent = Math.round((uploadedBytes / totalBytes) * 100); elements.uploadProgressBar.style.width = allPercent + '%'; if (completedCount === total) { isUploading = false; elements.uploadCancel.style.display = 'none'; elements.uploadConfirm.style.display = 'block'; elements.uploadStats.textContent = '上传完成: 成功 ' + completedCount + ' 个'; elements.uploadSpeed.textContent = ''; loadFiles(currentPath); } else { elements.uploadSpeed.textContent = '处理中... (' + (total - completedCount) + '个文件)'; } }); xhr.addEventListener('error', function() { finishedMap[index] = true; loadedBytesMap[index] = file.size; uploadedBytes += file.size; completedCount++; document.getElementById('upload-progress-text-' + index).textContent = '失败'; document.getElementById('upload-item-progress-' + index).style.background = '#f44336'; var allPercent = Math.round((uploadedBytes / totalBytes) * 100); elements.uploadProgressBar.style.width = allPercent + '%'; if (completedCount === total) { isUploading = false; elements.uploadCancel.style.display = 'none'; elements.uploadConfirm.style.display = 'block'; elements.uploadStats.textContent = '上传完成: 成功 ' + completedCount + ' 个'; elements.uploadSpeed.textContent = ''; loadFiles(currentPath); } }); xhr.open('POST', API_BASE + '/upload?path=' + encodeURIComponent(currentPath)); xhr.send(formData); }); } function downloadFile(path) { var url = API_BASE + '/download?path=' + encodeURIComponent(path); var a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function downloadSelected() { var paths = Array.from(selectedFiles); if (paths.length === 1) { downloadFile(paths[0]); } else { var url = API_BASE + '/download?paths=' + encodeURIComponent(JSON.stringify(paths)); var a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } } function deleteFiles(paths) { fetch(API_BASE + '/files', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(paths) }) .then(function(response) { return response.json(); }) .then(function(data) { if (data.success) { showNotification('已删除 ' + paths.length + ' 个项目', 'success'); selectedFiles.clear(); loadFiles(currentPath); } else { showNotification('删除失败', 'error'); } }) .catch(function(error) { showNotification('删除失败: ' + error.message, 'error'); }); } function showMoveModal(sourcePath) { elements.moveSource.value = sourcePath; elements.moveFilename.textContent = '移动: ' + sourcePath; loadDirectoryTree('/'); elements.moveModal.classList.add('active'); } function loadDirectoryTree(path) { fetch(API_BASE + '/files?path=' + encodeURIComponent(path)) .then(function(response) { return response.json(); }) .then(function(data) { renderDirectoryTree(data.files || [], path); updateMoveBreadcrumb(path); }); } function renderDirectoryTree(files, currentPath) { var dirs = (files || []).filter(function(f) { return f.isDir; }); if (dirs.length === 0) { elements.dirTree.innerHTML = '
📂

此目录为空

'; return; } elements.dirTree.innerHTML = dirs.map(function(dir) { return '
📁' + escapeHtml(dir.name) + '
'; }).join(''); elements.dirTree.querySelectorAll('.dir-item').forEach(function(item) { item.addEventListener('click', function() { loadDirectoryTree(item.dataset.path); }); }); } function updateMoveBreadcrumb(path) { if (!path || path === '/') { elements.moveBreadcrumb.innerHTML = '根目录'; return; } var parts = path.split('/').filter(function(p) { return p; }); var html = '根目录'; parts.forEach(function(part, index) { var currentPath = '/' + parts.slice(0, index + 1).join('/'); html += '' + escapeHtml(part) + ''; }); elements.moveBreadcrumb.innerHTML = html; elements.moveBreadcrumb.querySelectorAll('.crumb').forEach(function(crumb) { crumb.addEventListener('click', function() { loadDirectoryTree(crumb.dataset.path); }); }); } function moveFile() { var sourcePath = elements.moveSource.value; var destPath = elements.moveDest.value; if (!sourcePath || !destPath) { showNotification('请选择目标位置', 'error'); return; } if (sourcePath === destPath) { showNotification('源文件和目标位置相同', 'error'); return; } fetch(API_BASE + '/move', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: sourcePath, dest: destPath }) }) .then(function(response) { return response.json(); }) .then(function(data) { if (data.success) { showNotification('文件已移动', 'success'); elements.moveModal.classList.remove('active'); selectedFiles.delete(sourcePath); selectedFiles.add(destPath); loadFiles(currentPath); } else { showNotification('移动失败: ' + data.message, 'error'); } }) .catch(function(error) { showNotification('移动失败: ' + error.message, 'error'); }); } function showRenameModal(path) { elements.renamePath.value = path; var name = path.split('/').pop(); elements.renameName.value = name; elements.renameModal.classList.add('active'); elements.renameName.focus(); elements.renameName.select(); } function renameFile() { var path = elements.renamePath.value; var newName = elements.renameName.value.trim(); if (!path || !newName) { showNotification('请输入新名称', 'error'); return; } var newPath = path.substring(0, path.lastIndexOf('/') + 1) + newName; fetch(API_BASE + '/rename', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: path, name: newName }) }) .then(function(response) { return response.json(); }) .then(function(data) { if (data.success) { showNotification('重命名成功', 'success'); elements.renameModal.classList.remove('active'); if (selectedFiles.has(path)) { selectedFiles.delete(path); selectedFiles.add(newPath); } loadFiles(currentPath); } else { showNotification('重命名失败: ' + data.message, 'error'); } }) .catch(function(error) { showNotification('重命名失败: ' + error.message, 'error'); }); } function createDirectory() { var name = elements.newDirName.value.trim(); if (!name) { showNotification('请输入文件夹名称', 'error'); return; } var path = currentPath === '/' ? '/' + name : currentPath + '/' + name; fetch(API_BASE + '/dir', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: path }) }) .then(function(response) { return response.json(); }) .then(function(data) { if (data.success) { showNotification('文件夹创建成功', 'success'); elements.newDirModal.classList.remove('active'); loadFiles(currentPath); } else { showNotification('创建失败: ' + data.message, 'error'); } }) .catch(function(error) { showNotification('创建失败: ' + error.message, 'error'); }); } function previewFile(path) { var name = path.split('/').pop(); elements.previewTitle.textContent = name; elements.previewBody.innerHTML = '
📄

加载中...

'; elements.previewModal.classList.add('active'); fetch(API_BASE + '/preview?path=' + encodeURIComponent(path)) .then(function(response) { var contentType = response.headers.get('Content-Type'); if (contentType && contentType.startsWith('image/')) { return response.blob().then(function(blob) { return { type: 'image', data: URL.createObjectURL(blob) }; }); } else if (contentType && (contentType.startsWith('text/') || contentType.includes('json') || contentType.includes('javascript'))) { return response.text().then(function(text) { return { type: 'text', data: text }; }); } else { return response.blob().then(function(blob) { return { type: 'iframe', data: URL.createObjectURL(blob) }; }); } }) .then(function(result) { switch (result.type) { case 'image': elements.previewBody.innerHTML = '' + escapeHtml(name) + ''; break; case 'text': elements.previewBody.innerHTML = '
' + escapeHtml(result.data) + '
'; break; case 'iframe': elements.previewBody.innerHTML = ''; break; default: elements.previewBody.innerHTML = '
📄

此文件类型不支持预览

点击下载查看

'; } }) .catch(function(error) { elements.previewBody.innerHTML = '

预览失败: ' + error.message + '

'; }); } function startWatch() { if (eventSource) { eventSource.close(); eventSource = null; } try { eventSource = new EventSource(API_BASE + '/watch'); eventSource.onopen = function() { elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; }; eventSource.onerror = function() { if (eventSource.readyState === EventSource.CLOSED) return; elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; }; eventSource.addEventListener('message', function(e) { try { var event = JSON.parse(e.data); handleWatchEvent(event); } catch (err) {} }); } catch (err) { elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; } } function handleWatchEvent(event) { var inCurrentDir = currentPath === '/' || event.path.startsWith(currentPath + '/') || event.path === currentPath; if (!inCurrentDir) return; var pathParts = event.path.split('/').filter(function(p) { return p; }); var eventDir = '/' + pathParts.slice(0, -1).join('/') || '/'; if (eventDir !== currentPath) return; switch (event.type) { case 'create': showNotification('新建文件: ' + event.name, 'info'); loadFiles(currentPath); break; case 'delete': showNotification('已删除: ' + event.name, 'info'); selectedFiles.delete(event.path); loadFiles(currentPath); break; case 'rename': if (selectedFiles.has(event.oldPath)) { selectedFiles.delete(event.oldPath); selectedFiles.add(event.path); } showNotification('文件已重命名: ' + event.name, 'info'); loadFiles(currentPath); break; } } function showNotification(message, type) { type = type || 'info'; elements.notification.textContent = message; elements.notification.className = 'notification ' + type; elements.notification.classList.add('show'); setTimeout(function() { elements.notification.classList.remove('show'); }, 3000); } function searchFiles() { var keyword = elements.searchInput.value.trim(); if (!keyword) { loadFiles(currentPath); return; } elements.fileListBody.innerHTML = '搜索中...'; searchAllDirectories('/', keyword.toLowerCase(), []); } function searchAllDirectories(path, keyword, results) { fetch(API_BASE + '/files?path=' + encodeURIComponent(path)) .then(function(response) { return response.json(); }) .then(function(data) { var files = data.files || []; files.forEach(function(file) { if (file.name.toLowerCase().includes(keyword)) { results.push(file); } }); var dirs = files.filter(function(f) { return f.isDir; }); if (dirs.length > 0) { var promises = dirs.map(function(dir) { return searchAllDirectories(dir.path, keyword, results); }); return Promise.all(promises); } }) .then(function() { if (path === '/') { if (results.length > 0) { renderSearchResults(results, keyword); } else { elements.fileListBody.innerHTML = '🔍未找到匹配的文件'; } } }) .catch(function(error) { if (path === '/') { showNotification('搜索失败: ' + error.message, 'error'); elements.fileListBody.innerHTML = '搜索失败'; } }); } function renderSearchResults(files, keyword) { var sortedFiles = files.sort(function(a, b) { return a.name.localeCompare(b.name); }); var html = sortedFiles.map(function(file) { return '' + '' + '' + getFileIcon(file.name, file.isDir) + '' + highlightKeyword(escapeHtml(file.name), keyword) + '' + '' + (file.isDir ? '-' : formatFileSize(file.size)) + '' + '' + formatTime(file.modTime) + '' + '
' + (file.isDir ? '' : '') + (file.canPreview ? '' : '') + '' + '' + '
'; }).join(''); elements.fileListBody.innerHTML = html; updateButtonStates(); } function highlightKeyword(text, keyword) { if (!keyword) return text; var regex = new RegExp('(' + keyword + ')', 'gi'); return text.replace(regex, '$1'); } document.addEventListener('DOMContentLoaded', init); })();