(function() { 'use strict'; Dropzone.autoDiscover = false; const API_BASE = '/api'; let currentPath = '/'; let selectedFiles = new Set(); let eventSource = null; let uploadDropzone = null; let backgroundManager = null; // 后台上传管理器 class BackgroundUploadManager { constructor() { this.activeUploads = new Map(); this.maxConcurrent = 5; this.dropzoneInstance = null; this.onUpdateCallbacks = []; this.isPanelVisible = false; } init() { this.createBackgroundUI(); this.bindEvents(); } createBackgroundUI() { // 创建后台按钮 const backgroundBtn = document.createElement('button'); backgroundBtn.className = 'btn btn-info background-btn'; backgroundBtn.id = 'btn-background'; backgroundBtn.style.display = 'none'; backgroundBtn.innerHTML = '📋 后台传输 (0)'; // 插入到上传按钮后面 const uploadBtn = document.getElementById('btn-upload'); if (uploadBtn && uploadBtn.parentNode) { uploadBtn.parentNode.insertBefore(backgroundBtn, uploadBtn.nextSibling); } // 创建后台面板 const panel = document.createElement('div'); panel.className = 'background-panel'; panel.id = 'background-panel'; panel.innerHTML = `

后台传输 (0)

暂无后台传输任务
`; document.body.appendChild(panel); // 存储DOM引用 this.btnBackground = backgroundBtn; this.backgroundPanel = panel; this.backgroundList = panel.querySelector('#background-list'); this.activeCount = panel.querySelector('#active-count'); this.uploadCount = backgroundBtn.querySelector('#upload-count'); } bindEvents() { this.btnBackground.addEventListener('click', () => { this.showPanel(); }); this.backgroundPanel.querySelector('#background-close').addEventListener('click', () => { this.hidePanel(); }); // 点击面板外部关闭 this.backgroundPanel.addEventListener('click', (e) => { if (e.target === this.backgroundPanel) { this.hidePanel(); } }); } takeOverUploads(dropzone) { this.dropzoneInstance = dropzone; // 分析当前上传状态 const uploadingFiles = dropzone.getUploadingFiles(); const queuedFiles = dropzone.getQueuedFiles(); [...uploadingFiles, ...queuedFiles].forEach(file => { this.addUploadTask(file); }); // 绑定事件监听 this.bindDropzoneEvents(); // 确保Dropzone继续处理上传队列 setTimeout(() => { if (dropzone.getQueuedFiles().length > 0) { dropzone.processQueue(); } }, 100); } bindDropzoneEvents() { if (!this.dropzoneInstance) return; const dz = this.dropzoneInstance; // 监听成功和错误事件 dz.on('success', (file, response) => { this.completeUpload(file._dzFileId); }); dz.on('error', (file, message) => { this.failUpload(file._dzFileId); }); dz.on('complete', (file) => { if (dz.getUploadingFiles().length === 0 && dz.getQueuedFiles().length === 0) { setTimeout(() => { loadFiles(currentPath); }, 500); } }); dz.on('queuecomplete', () => { setTimeout(() => { loadFiles(currentPath); }, 500); }); } addUploadTask(file) { const task = { id: file._dzFileId, fileName: file.name, totalSize: file.size, progress: isNaN(file._uploadProgress) ? 0 : Math.max(0, Math.min(100, file._uploadProgress || 0)), status: 'uploading', chunks: null, startTime: Date.now() }; if (file.totalChunks > 1) { const currentChunk = (file.upload && file.upload.chunk) || 0; task.chunks = `${currentChunk}/${file.totalChunks}`; } this.activeUploads.set(task.id, task); this.notifyUIUpdate(); } updateProgress(fileId, progress, chunkInfo) { const task = this.activeUploads.get(fileId); if (task) { // 确保progress是有效的数值 task.progress = isNaN(progress) ? 0 : Math.max(0, Math.min(100, progress)); task.chunks = chunkInfo; this.notifyUIUpdate(); } } completeUpload(fileId) { const task = this.activeUploads.get(fileId); if (task) { task.status = 'completed'; task.progress = 100; // 延迟删除,让用户看到完成状态 setTimeout(() => { this.activeUploads.delete(fileId); this.notifyUIUpdate(); }, 2000); } } failUpload(fileId) { const task = this.activeUploads.get(fileId); if (task) { task.status = 'failed'; this.notifyUIUpdate(); } } hasActiveUploads() { return this.activeUploads.size > 0; } showPanel() { this.backgroundPanel.style.display = 'block'; this.isPanelVisible = true; this.updateUI(); } hidePanel() { this.backgroundPanel.style.display = 'none'; this.isPanelVisible = false; } updateUI() { const activeCount = this.activeUploads.size; // 更新后台按钮 if (activeCount > 0) { this.btnBackground.style.display = 'inline-block'; this.uploadCount.textContent = activeCount; } else { this.btnBackground.style.display = 'none'; } // 更新面板标题 this.activeCount.textContent = activeCount; // 更新列表 if (this.isPanelVisible) { this.renderUploadList(); } } renderUploadList() { const list = this.backgroundList; list.innerHTML = ''; if (this.activeUploads.size === 0) { list.innerHTML = '
暂无后台传输任务
'; return; } this.activeUploads.forEach(task => { const item = this.createUploadItem(task); list.appendChild(item); }); } createUploadItem(task) { const item = document.createElement('div'); item.className = 'background-item'; item.dataset.id = task.id; const statusIcon = task.status === 'completed' ? '✓' : task.status === 'failed' ? '✗' : '⏳'; // 确保progress是有效的数值 const safeProgress = isNaN(task.progress) ? 0 : Math.max(0, Math.min(100, task.progress)); item.innerHTML = `
${getFileIcon(task.fileName)}
${escapeHtml(task.fileName)}
${Math.round(safeProgress)}% ${task.chunks ? `(${task.chunks})` : ''}
${statusIcon}
`; return item; } notifyUIUpdate() { this.updateUI(); } onUpdate(callback) { this.onUpdateCallbacks.push(callback); } } 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'), 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'), uploadCloseBtn: document.getElementById('upload-close-btn'), uploadPath: document.getElementById('upload-path'), searchInput: document.getElementById('search-input'), searchBtn: document.getElementById('search-btn'), notification: document.getElementById('notification') }; function init() { loadFiles(currentPath); setupEventListeners(); initDropzone(); initBackgroundManager(); startWatch(); } function initBackgroundManager() { backgroundManager = new BackgroundUploadManager(); backgroundManager.init(); } function handleUploadModalClose() { // 检查是否有正在上传的文件 const uploadingFiles = uploadDropzone ? uploadDropzone.getUploadingFiles() : []; const queuedFiles = uploadDropzone ? uploadDropzone.getQueuedFiles() : []; if (uploadingFiles.length > 0 || queuedFiles.length > 0) { // 转移到后台管理器 backgroundManager.takeOverUploads(uploadDropzone); } // 隐藏模态框 elements.uploadModal.classList.remove('active'); } 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 currentPathStr = '/' + 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 initDropzone() { var dropzoneElement = document.querySelector('#upload-dropzone'); if (dropzoneElement) { var existingDropzone = dropzoneElement.dropzone; if (existingDropzone) { existingDropzone.destroy(); } } if (uploadDropzone) { uploadDropzone.destroy(); uploadDropzone = null; } uploadDropzone = new Dropzone('#upload-dropzone', { url: API_BASE + '/upload', paramName: 'file', maxFilesize: 0, timeout: 0, chunking: true, parallelUploads: 2, parallelUploadsPerFile: 1, addRemoveLinks: false, maxChunkSize: 1024 * 1024, retryChunks: true, retryChunksLimit: 3, previewTemplate: '
拖拽文件到此处或点击上传
', dictDefaultMessage: '拖拽文件到此处或点击上传', dictRemoveFile: '移除', dictCancelUpload: '取消', dictCancelUploadConfirmation: '确定取消上传?', dictFallbackMessage: '您的浏览器不支持拖拽上传', dictFileTooBig: '文件太大 ({{filesize}}MB),最大限制: {{maxFilesize}}MB', dictInvalidFileType: '不支持的文件类型', dictResponseError: '服务器错误', dictUploadCanceled: '上传已取消', acceptedFiles: null, init: function() { var dz = this; var uploadedChunks = {}; function getFileProgress(file) { return { uploaded: file.uploadedChunks || 0, total: file.totalChunks || 1 }; } this.on('addedfile', function(file) { var list = document.getElementById('upload-file-list'); var icon = getFileIcon(file.name); var size = formatFileSize(file.size); var fileId = 'f' + Date.now() + Math.random().toString(36).substr(2, 5); file._dzFileId = fileId; file._uploadProgress = 0; file._resumeSupported = true; var item = document.createElement('div'); item.className = 'upload-file-item'; item.id = 'upload-item-' + fileId; item.innerHTML = '' + icon + '' + '
' + '
' + escapeHtml(file.name) + '
' + '
' + size + '
' + '
' + '
' + '
' + '
' + '
' + '0%' + '
' + '
' + '' + '' + '' + '
' + '' + '
'; list.appendChild(item); var progressEl = document.getElementById('progress-' + fileId); var percentEl = document.getElementById('percent-' + fileId); var chunksEl = document.getElementById('chunks-' + fileId); if (file.totalChunks > 1) { chunksEl.textContent = '0/' + file.totalChunks; } item.querySelector('.dz-remove').addEventListener('click', function() { dz.removeFile(file); var el = document.getElementById('upload-item-' + fileId); if (el) el.remove(); }); }); this.on('uploadprogress', function(file, progress, bytesSent, totalBytesSent, totalBytes) { var fileId = file._dzFileId; var totalProgress = progress; // 默认值 // 更新悬浮窗中的进度 var progressEl = document.getElementById('progress-' + fileId); var percentEl = document.getElementById('percent-' + fileId); var chunksEl = document.getElementById('chunks-' + fileId); if (file.totalChunks > 1) { var currentChunk = file.upload.chunk || 0; var chunkWeight = 100 / file.totalChunks; totalProgress = (currentChunk * chunkWeight) + (progress * chunkWeight / 100); if (progressEl) { progressEl.style.width = Math.min(totalProgress, 100) + '%'; } if (percentEl) { percentEl.textContent = Math.round(Math.min(totalProgress, 100)) + '%'; } if (chunksEl) { chunksEl.textContent = (currentChunk + 1) + '/' + file.totalChunks; } } else { totalProgress = progress; if (progressEl) { progressEl.style.width = progress + '%'; } if (percentEl) { percentEl.textContent = Math.round(progress) + '%'; } } // 同步到后台管理器 if (backgroundManager) { backgroundManager.updateProgress(fileId, totalProgress, file.totalChunks > 1 ? `${(file.upload.chunk || 0) + 1}/${file.totalChunks}` : null); } }); this.on('success', function(file, response) { var fileId = file._dzFileId; var itemEl = document.getElementById('upload-item-' + fileId); if (itemEl) { var progressEl = document.getElementById('progress-' + fileId); var percentEl = document.getElementById('percent-' + fileId); var chunksEl = document.getElementById('chunks-' + fileId); progressEl.style.width = '100%'; percentEl.textContent = '100%'; if (chunksEl && file.totalChunks > 1) { chunksEl.textContent = file.totalChunks + '/' + file.totalChunks; } try { var data = JSON.parse(response); if (data.success) { itemEl.classList.add('dz-success'); } else if (data.merged) { itemEl.classList.add('dz-success'); } else { itemEl.classList.add('dz-error'); itemEl.querySelector('.dz-error-message').textContent = data.message || '上传失败'; } } catch (e) { itemEl.classList.add('dz-success'); } } }); this.on('error', function(file, message) { var fileId = file._dzFileId; var itemEl = document.getElementById('upload-item-' + fileId); if (itemEl) { itemEl.classList.add('dz-error'); itemEl.querySelector('.dz-error-message').textContent = message || '上传失败'; } }); this.on('complete', function(file) { if (dz.getUploadingFiles().length === 0 && dz.getQueuedFiles().length === 0) { setTimeout(function() { loadFiles(currentPath); }, 500); } }); this.on('queuecomplete', function() { setTimeout(function() { loadFiles(currentPath); }, 500); }); } }); } 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() { var list = document.getElementById('upload-file-list'); if (list) { list.innerHTML = ''; } if (uploadDropzone) { uploadDropzone.removeAllFiles(true); } var pathInput = document.getElementById('upload-path'); if (pathInput) { pathInput.value = currentPath; } elements.uploadModal.classList.add('active'); }); 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.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'); if (uploadDropzone) { uploadDropzone.removeAllFiles(true); e.dataTransfer.files.forEach(function(file) { uploadDropzone.addFile(file); }); elements.uploadPath.value = currentPath; elements.uploadModal.classList.add('active'); } }); } 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() { handleUploadModalClose(); }); elements.uploadCloseBtn.addEventListener('click', function() { handleUploadModalClose(); }); 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) handleUploadModalClose(); }); } 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 currentPathStr = '/' + 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); })();