window.FileManager = { currentFolderId: null, selectedFileId: null, pendingDeleteId: null, pendingDeleteIsFolder: false, pendingBatchDeleteFiles: null, selectedItemId: null, // 分页相关变量 currentPage: 1, totalPages: 1, pageSize: 10, allFiles: [], // 存储当前文件夹中的所有文件 // 排序相关变量 currentSortField: 'name', currentSortOrder: 'asc', // 文件夹大小缓存 folderSizeCache: {}, // Modal 变量 newFolderModal: null, moveModal: null, batchMoveModal: null, confirmDeleteModal: null, renameModal: null, // 添加进行中标记,避免重复请求 pendingFolderSizeRequests: {} }; // 初始化 Modal function initializeModals() { try { // 确保 Bootstrap 已加载 if (typeof bootstrap === 'undefined') { console.error('Bootstrap 未加载'); return; } // 等待DOM完全加载 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => { initializeModalsAfterLoad(); }); } else { initializeModalsAfterLoad(); } } catch (error) { console.error('Modal 初始化错误:', error); } } function initializeModalsAfterLoad() { try { // 获取 Modal 元素 const newFolderModalEl = document.getElementById('newFolderModal'); const moveModalEl = document.getElementById('moveModal'); const batchMoveModalEl = document.getElementById('batchMoveModal'); const confirmDeleteModalEl = document.getElementById('confirmDeleteModal'); const renameModalEl = document.getElementById('renameModal'); // 检查元素是否存在并初始化 if (newFolderModalEl && !FileManager.newFolderModal) { FileManager.newFolderModal = new bootstrap.Modal(newFolderModalEl, { backdrop: 'static', keyboard: false }); } if (moveModalEl && !FileManager.moveModal) { FileManager.moveModal = new bootstrap.Modal(moveModalEl, { backdrop: 'static', keyboard: false }); } if (batchMoveModalEl && !FileManager.batchMoveModal) { FileManager.batchMoveModal = new bootstrap.Modal(batchMoveModalEl, { backdrop: 'static', keyboard: false }); } if (confirmDeleteModalEl && !FileManager.confirmDeleteModal) { FileManager.confirmDeleteModal = new bootstrap.Modal(confirmDeleteModalEl, { backdrop: 'static', keyboard: false }); // 绑定确认删除按钮的点击事件 const confirmDeleteBtn = document.getElementById('confirmDeleteBtn'); if (confirmDeleteBtn && !confirmDeleteBtn.onclick) { confirmDeleteBtn.onclick = performDelete; console.log('已绑定确认删除按钮点击事件'); } } if (renameModalEl && !FileManager.renameModal) { FileManager.renameModal = new bootstrap.Modal(renameModalEl, { backdrop: 'static', keyboard: false }); } } catch (error) { console.error('Modal 初始化错误:', error); } } // 页面加载时初始化 Modal document.addEventListener('DOMContentLoaded', initializeModals); // 显示提示信息 function showToast(message, type = 'info') { const options = { text: message, duration: 3000, gravity: "top", position: "right", style: { background: type === 'error' ? '#dc3545' : type === 'success' ? '#28a745' : '#17a2b8', color: 'white', borderRadius: '4px', padding: '10px 15px', fontSize: '14px', boxShadow: '0 3px 6px rgba(0,0,0,0.16)' } }; Toastify(options).showToast(); } // 格式化文件大小 function formatSize(bytes) { if (bytes === null || bytes === undefined) return '-'; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes === 0) return '0 Byte'; const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; } // 获取文件夹路径 async function getFolderPath(folderId) { if (!folderId) return []; try { // 获取所有文件 const response = await fetch('/api/files?all=true'); const data = await response.json(); // 处理API返回的不同格式 let folders = []; if (data.files && Array.isArray(data.files)) { folders = data.files.filter(f => f.is_folder); } else if (Array.isArray(data)) { folders = data.filter(f => f.is_folder); } else { console.error('API返回的数据格式不正确:', data); return []; } // 递归函数,用于构建路径 function buildPath(id) { const folder = folders.find(f => f.id.toString() === id.toString()); if (!folder) return []; if (folder.parent_id !== null) { const parentPath = buildPath(folder.parent_id); // 确保folder对象包含name属性 if (!folder.name && folder.filename) { folder.name = folder.filename; } return [...parentPath, folder]; } return [folder]; } return buildPath(folderId); } catch (error) { console.error('Error getting folder path:', error); return []; } } // 计算文件夹大小 async function calculateFolderSize(folderId) { // 检查缓存中是否已有数据 if (FileManager.folderSizeCache[folderId] !== undefined) { return FileManager.folderSizeCache[folderId]; } // 添加进行中标记,避免重复请求 if (FileManager.pendingFolderSizeRequests[folderId]) { return await FileManager.pendingFolderSizeRequests[folderId]; } try { // 创建一个Promise并保存到请求中 const sizePromise = new Promise(async (resolve) => { try { console.log(`请求文件夹 ${folderId} 大小`); const response = await fetch(`/api/folders/${folderId}/size`, { credentials: 'include', headers: { 'Accept': 'application/json' } }); if (!response.ok) { console.warn(`文件夹 ${folderId} 大小获取失败:`, response.status); // 如果API不可用,使用本地计算备选方案 const fallbackSize = calculateFolderSizeLocally(folderId); FileManager.folderSizeCache[folderId] = fallbackSize; resolve(fallbackSize); return; } const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { console.warn(`文件夹 ${folderId} 返回非JSON格式:`, contentType); const fallbackSize = calculateFolderSizeLocally(folderId); FileManager.folderSizeCache[folderId] = fallbackSize; resolve(fallbackSize); return; } const data = await response.json(); console.log(`文件夹 ${folderId} 大小:`, data); // 存入缓存 FileManager.folderSizeCache[folderId] = data.size || 0; resolve(data.size || 0); } catch (error) { console.error(`计算文件夹 ${folderId} 大小时出错:`, error); const fallbackSize = calculateFolderSizeLocally(folderId); FileManager.folderSizeCache[folderId] = fallbackSize; resolve(fallbackSize); } }); // 保存请求Promise FileManager.pendingFolderSizeRequests[folderId] = sizePromise; return await sizePromise; } catch (error) { console.error('计算文件夹大小出错:', error); return 0; } } // 本地计算文件夹大小(作为备选方案) function calculateFolderSizeLocally(folderId) { try { // 使用深度优先搜索递归计算 let totalSize = 0; function dfs(folder_id) { // 获取当前文件夹的直接子文件和子文件夹 const children = FileManager.allFiles.filter(file => file.parent_id == folder_id); for (const child of children) { if (child.is_folder) { // 递归计算子文件夹大小 dfs(child.id); } else { // 累加文件大小 totalSize += parseInt(child.file_size || 0, 10); } } } dfs(folderId); return totalSize; } catch (error) { console.error('本地计算文件夹大小出错:', error); return 0; } } // 加载文件列表 async function loadFiles() { try { console.log('Loading files for folder:', FileManager.currentFolderId); const response = await fetch(`/api/files?parent_id=${FileManager.currentFolderId || ''}`, { credentials: 'include' // 确保包含Cookie }); console.log('Files API response status:', response.status); if (response.status === 401 || response.status === 403) { // 未登录,重定向到登录页 console.log('用户未登录,重定向到登录页'); window.location.href = '/login.html'; return; } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const contentType = response.headers.get('content-type'); if (!contentType || !contentType.includes('application/json')) { console.error('Invalid content type:', contentType); throw new Error('服务器返回了非JSON格式的数据'); } const data = await response.json(); console.log('文件列表响应:', data); // 检查是否需要重定向 if (data.redirect) { console.log('服务器要求重定向:', data.redirect); window.location.href = data.redirect; return; } // 处理文件列表数据 - 增强版 console.log('处理文件数据,数据类型:', typeof data, Array.isArray(data) ? '是数组' : '不是数组'); if (data && typeof data === 'object') { if (data.files && Array.isArray(data.files)) { console.log('直接使用data.files数组'); FileManager.allFiles = data.files; } else if (Array.isArray(data)) { console.log('直接使用data数组'); FileManager.allFiles = data; } else { console.error('API返回的数据格式不正确, data:', data); FileManager.allFiles = []; } } else { console.error('API返回的数据不是对象:', data); FileManager.allFiles = []; } console.log('Loaded files:', FileManager.allFiles); // 更新面包屑 const folderPath = await getFolderPath(FileManager.currentFolderId); console.log('Current folder path:', folderPath); updateBreadcrumb(folderPath); // 应用分页 renderFileList(); } catch (error) { console.error('Error loading files:', error); showToast(`加载文件列表失败: ${error.message}`, 'error'); } } // 排序文件 function sortFiles(files, field = 'name', order = 'asc') { return [...files].sort((a, b) => { if (field === 'name') { // 名称排序逻辑 const splitName = (name) => { // 拆分文件名与扩展名 const lastDotIndex = name.lastIndexOf('.'); if (lastDotIndex === -1) return { name, ext: '' }; return { name: name.slice(0, lastDotIndex), ext: name.slice(lastDotIndex + 1) }; }; // 文件夹始终在文件前面 if (a.is_folder !== b.is_folder) { return a.is_folder ? -1 : 1; } // 如果都是文件夹或都是文件,按名称排序 const aNameParts = splitName(a.filename || a.name || ''); const bNameParts = splitName(b.filename || b.name || ''); // 如果是文件且扩展名不同,可以选择按扩展名排序 if (!a.is_folder && !b.is_folder && aNameParts.ext !== bNameParts.ext) { return order === 'asc' ? aNameParts.ext.localeCompare(bNameParts.ext) : bNameParts.ext.localeCompare(aNameParts.ext); } // 按名称排序 return order === 'asc' ? (a.filename || a.name || '').localeCompare(b.filename || b.name || '') : (b.filename || b.name || '').localeCompare(a.filename || a.name || ''); } else if (field === 'size') { // 文件大小排序 let aSize = a.size || 0; let bSize = b.size || 0; // 如果是文件夹,使用缓存的大小 if (a.is_folder && FileManager.folderSizeCache[a.id] !== undefined) { aSize = FileManager.folderSizeCache[a.id]; } if (b.is_folder && FileManager.folderSizeCache[b.id] !== undefined) { bSize = FileManager.folderSizeCache[b.id]; } // 如果文件夹大小未缓存,尝试将文件夹排在最后或最前 if (a.is_folder && FileManager.folderSizeCache[a.id] === undefined) { return order === 'asc' ? -1 : 1; } if (b.is_folder && FileManager.folderSizeCache[b.id] === undefined) { return order === 'asc' ? 1 : -1; } // 正常排序 return order === 'asc' ? aSize - bSize : bSize - aSize; } else if (field === 'created_at') { const aDate = new Date(a.created_at); const bDate = new Date(b.created_at); return order === 'asc' ? aDate - bDate : bDate - aDate; } return 0; }); } // 处理排序点击事件 function handleSort(field) { if (FileManager.currentSortField === field) { // 如果点击的是当前排序字段,切换排序顺序 FileManager.currentSortOrder = FileManager.currentSortOrder === 'asc' ? 'desc' : 'asc'; } else { // 如果点击的是新字段,设置为升序 FileManager.currentSortField = field; FileManager.currentSortOrder = 'asc'; } // 更新排序图标 updateSortIcon(); // 重新渲染文件列表 renderFileList(); } // 更新排序图标 function updateSortIcon() { // 移除所有列的排序图标 document.querySelectorAll('th .sort-icon').forEach(icon => icon.remove()); // 添加当前排序列的图标 const th = document.querySelector(`th[data-sort="${FileManager.currentSortField}"]`); if (th) { const icon = document.createElement('span'); icon.className = 'sort-icon ms-1'; icon.innerHTML = FileManager.currentSortOrder === 'asc' ? '↑' : '↓'; th.appendChild(icon); } } // 渲染文件列表(带分页) async function renderFileList() { // 防御性代码,确保allFiles是数组 if (FileManager.allFiles && !Array.isArray(FileManager.allFiles)) { console.warn('allFiles不是数组,尝试修复:', FileManager.allFiles); if (FileManager.allFiles.files && Array.isArray(FileManager.allFiles.files)) { FileManager.allFiles = FileManager.allFiles.files; } else { console.error('无法修复allFiles,重置为空数组'); FileManager.allFiles = []; } } console.log('Rendering file list, total files:', FileManager.allFiles.length); const tbody = document.getElementById('fileList'); tbody.innerHTML = ''; if (FileManager.allFiles.length === 0) { tbody.innerHTML = '当前文件夹为空'; return; } // 优化:限制并发请求数量,只预先计算当前页的文件夹大小 const sortedFiles = sortFiles(FileManager.allFiles, FileManager.currentSortField, FileManager.currentSortOrder); // 计算分页 FileManager.totalPages = Math.ceil(sortedFiles.length / FileManager.pageSize); if (FileManager.currentPage > FileManager.totalPages && FileManager.totalPages > 0) { FileManager.currentPage = FileManager.totalPages; } console.log('Pagination info:', { currentPage: FileManager.currentPage, totalPages: FileManager.totalPages, pageSize: FileManager.pageSize, totalFiles: sortedFiles.length }); // 计算当前页的文件范围 const start = (FileManager.currentPage - 1) * FileManager.pageSize; const end = Math.min(start + FileManager.pageSize, sortedFiles.length); const currentPageFiles = sortedFiles.slice(start, end); // 更新分页控件 updatePagination(sortedFiles.length); // 仅计算当前页文件夹的大小,减少API请求 const folderSizePromises = []; currentPageFiles.forEach(file => { if (file.is_folder && FileManager.folderSizeCache[file.id] === undefined) { const promise = calculateFolderSize(file.id) .then(size => ({ id: file.id, size })); folderSizePromises.push({ id: file.id, promise }); } }); console.log('Current page files:', { start, end, filesCount: currentPageFiles.length, folderPromises: folderSizePromises.length }); // 显示文件列表,先不显示文件夹大小 currentPageFiles.forEach((file, index) => { const actualIndex = start + index + 1; const tr = document.createElement('tr'); let fileSize = parseInt(file.file_size || file.size || 0, 10); // 检查文件是否支持预览 const canPreview = !file.is_folder && fileSize <= 20 * 1024 * 1024; // 小于等于20MB const fileName = file.filename || file.name || ''; const fileExt = fileName.split('.').pop().toLowerCase(); const previewableExts = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'pdf', 'mp4', 'mp3', 'wav', 'webm', 'ogg', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx']; const showPreviewBtn = canPreview && previewableExts.includes(fileExt); tr.innerHTML = ` ${actualIndex} ${file.is_folder ? '📁 ' : '📄 '}${ file.is_folder ? `${file.filename || file.name}` : `${file.filename || file.name}` } ${formatSize(fileSize)} ${moment(file.created_at).format('YYYY-MM-DD HH:mm:ss')}
${file.is_folder ? '' : ` ${showPreviewBtn ? `` : ''} `}
`; // 添加文件夹点击事件 const folderLink = tr.querySelector('.folder-link'); if (folderLink) { folderLink.addEventListener('click', (e) => { e.preventDefault(); FileManager.currentFolderId = folderLink.dataset.id; FileManager.currentPage = 1; // 重置为第一页 loadFiles(); }); } tbody.appendChild(tr); }); // 等待所有文件夹大小计算完成并更新UI if (folderSizePromises.length > 0) { Promise.all(folderSizePromises.map(item => item.promise)) .then(sizes => { folderSizePromises.forEach((item, index) => { const sizeCell = document.querySelector(`.file-size[data-id="${item.id}"]`); if (sizeCell) { sizeCell.textContent = formatSize(sizes[index].size); } }); }) .catch(error => { console.error('Error updating folder sizes:', error); }); } console.log('File list rendering completed'); // 初始化新的 tooltips initPopovers(); // 更新排序图标 updateSortIcon(); } // 更新面包屑 function updateBreadcrumb(folderPath) { const breadcrumb = document.querySelector('#breadcrumb ol'); breadcrumb.innerHTML = ''; // 添加根目录 const rootItem = document.createElement('li'); rootItem.className = 'breadcrumb-item'; rootItem.innerHTML = `根目录`; breadcrumb.appendChild(rootItem); // 添加文件夹路径 if (folderPath && folderPath.length > 0) { folderPath.forEach((folder, index) => { const item = document.createElement('li'); item.className = 'breadcrumb-item'; if (index === folderPath.length - 1) { item.classList.add('active'); item.innerHTML = folder.name; } else { item.innerHTML = `${folder.name}`; } breadcrumb.appendChild(item); }); } // 添加面包屑点击事件 breadcrumb.querySelectorAll('a').forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); FileManager.currentFolderId = link.dataset.id === 'null' ? null : link.dataset.id; FileManager.currentPage = 1; // 重置为第一页 loadFiles(); }); }); } // 显示新建文件夹模态框 function showNewFolderModal() { // 清空输入框 document.getElementById('folderName').value = ''; // 显示模态框 FileManager.newFolderModal.show(); // 显示后自动聚焦到输入框 setTimeout(() => { document.getElementById('folderName').focus(); }, 500); } // 创建文件夹 async function createFolder() { const name = document.getElementById('folderName').value.trim(); if (!name) { showToast('请输入文件夹名称', 'error'); return; } try { const response = await fetch('/api/folders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, parent_id: FileManager.currentFolderId // 使用FileManager.currentFolderId }) }); if (response.ok) { showToast('文件夹创建成功'); FileManager.newFolderModal.hide(); // 使用FileManager.newFolderModal FileManager.currentPage = 1; // 创建新文件夹后回到第一页 loadFiles(); } else { const errorData = await response.json(); showToast(errorData.error || '文件夹创建失败', 'error'); } } catch (error) { console.error('创建文件夹出错:', error); showToast('文件夹创建失败', 'error'); } } // 检查是否是子文件夹 async function isSubfolder(parentId, targetId) { if (!parentId || !targetId) return false; if (parentId.toString() === targetId.toString()) return true; try { const response = await fetch(`/api/files?all=true`); const files = await response.json(); const folders = files.filter(f => f.is_folder); // 递归检查是否是子文件夹 function checkSubfolder(currentId) { const folder = folders.find(f => f.id.toString() === currentId.toString()); if (!folder || folder.parent_id === null) return false; const children = folders.filter(f => f.parent_id?.toString() === folder.id.toString()); return children.some(child => child.id.toString() === targetId.toString() || checkSubfolder(child.id) ); } return checkSubfolder(parentId); } catch (error) { console.error('Error checking subfolder:', error); return false; } } // 同步版本的isSubfolder函数,避免重复API调用 function isSubfolderSync(currentId, targetId, allFiles) { if (!currentId || !targetId) return false; if (currentId.toString() === targetId.toString()) return true; const folders = allFiles.filter(f => f.is_folder); // 检查当前文件夹是否是目标文件夹的子孙文件夹 function isDescendant(folderId) { const folder = folders.find(f => f.id.toString() === folderId.toString()); if (!folder || folder.parent_id === null) return false; // 如果当前文件夹的父文件夹是目标文件夹,或者是目标文件夹的子孙文件夹,则返回true return folder.parent_id.toString() === targetId.toString() || isDescendant(folder.parent_id); } // 从当前文件夹开始,向上检查是否能找到目标文件夹 return isDescendant(currentId); } // 检查是否是父文件夹 function isParentFolder(folderId, fileId, allFiles) { const file = allFiles.find(f => f.id.toString() === fileId.toString()); return file && file.parent_id?.toString() === folderId.toString(); } // 创建单个文件夹项及其子项 async function createFolderItem(folder, container, currentFileIds, level = 0, allFiles = null) { // 如果没有传入allFiles,则获取所有文件 if (!allFiles) { const response = await fetch(`/api/files?all=true`); allFiles = await response.json(); } // 检查是否禁用 const isDisabled = currentFileIds && ( Array.isArray(currentFileIds) ? currentFileIds.map(id => { // 检查每个选中的文件 const currentFile = allFiles.find(f => f.id.toString() === id.toString()); if (!currentFile) return false; return folder.id.toString() === id.toString() || // 当前文件夹本身 isSubfolderSync(folder.id, id, allFiles) || // 目标文件夹是当前文件夹的子孙文件夹 isParentFolder(folder.id, id, allFiles); // 目标文件夹是当前文件夹的父文件夹 }).some(result => result) : folder.id.toString() === currentFileIds.toString() || // 当前文件夹本身 isSubfolderSync(folder.id, currentFileIds, allFiles) || // 目标文件夹是当前文件夹的子孙文件夹 isParentFolder(folder.id, currentFileIds, allFiles) // 目标文件夹是当前文件夹的父文件夹 ); // 创建文件夹项容器 const folderItem = document.createElement('div'); folderItem.className = 'folder-item' + (isDisabled ? ' disabled' : ''); folderItem.dataset.id = folder.id; folderItem.dataset.level = level; // 查找子文件夹(从已有的allFiles中筛选,不再发送请求) const childFolders = allFiles.filter(f => f.is_folder && f.parent_id?.toString() === folder.id.toString()); const hasChildren = childFolders.length > 0; // 添加文件夹内容 folderItem.innerHTML = ` - 📁 ${folder.filename || folder.name} `; // 添加到容器 container.appendChild(folderItem); // 如果有子文件夹,创建子容器并添加切换功能 if (hasChildren) { // 创建子文件夹容器 const childrenContainer = document.createElement('div'); childrenContainer.className = 'folder-children'; childrenContainer.dataset.parentId = folder.id; childrenContainer.style.display = 'block'; // 默认展开 container.appendChild(childrenContainer); // 添加展开/折叠事件 const toggle = folderItem.querySelector('.folder-toggle'); toggle.addEventListener('click', function(e) { e.stopPropagation(); const childrenDiv = this.closest('.folder-item').nextElementSibling; if (childrenDiv && childrenDiv.classList.contains('folder-children')) { const isExpanded = childrenDiv.style.display === 'block'; this.textContent = isExpanded ? '+' : '-'; childrenDiv.style.display = isExpanded ? 'none' : 'block'; } }); // 立即加载所有子文件夹(递归加载) for (const childFolder of childFolders) { await createFolderItem(childFolder, childrenContainer, currentFileIds, level + 1, allFiles); } } // 添加单选框点击事件 const radio = folderItem.querySelector('input[type="radio"]'); radio.addEventListener('click', (e) => { if (isDisabled) { e.preventDefault(); showToast('不能移动到当前文件夹、子文件夹或当前所在的文件夹', 'error'); } }); // 添加文件夹名称点击事件 const folderName = folderItem.querySelector('.folder-name'); folderName.addEventListener('click', (e) => { if (!isDisabled) { const radio = folderItem.querySelector('input[type="radio"]'); radio.checked = true; // 触发一个change事件,以便其他可能依赖此事件的代码能够正常工作 const event = new Event('change', { bubbles: true }); radio.dispatchEvent(event); } else { showToast('不能移动到当前文件夹、子文件夹或当前所在的文件夹', 'error'); } }); return folderItem; } // 加载文件夹树 async function loadFolderTree(parentId, container, currentFileId = null) { try { // 清空容器 container.innerHTML = ''; // 获取所有文件 const response = await fetch('/api/files?all=true'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // 处理API返回的不同格式 let allFiles = []; if (data.files && Array.isArray(data.files)) { allFiles = data.files; } else if (Array.isArray(data)) { allFiles = data; } else { console.error('API返回的数据格式不正确:', data); throw new Error('返回的数据格式不正确'); } // 添加根目录项 const rootItem = document.createElement('div'); rootItem.className = 'folder-item'; // 检查是否需要禁用根目录(仅当当前文件在根目录时) let isRootDisabled = false; if (currentFileId) { const currentFile = Array.isArray(currentFileId) ? allFiles.find(f => currentFileId.includes(f.id.toString())) : allFiles.find(f => f.id.toString() === currentFileId.toString()); isRootDisabled = currentFile && currentFile.parent_id === null; } rootItem.innerHTML = ` 📁 根目录 `; container.appendChild(rootItem); // 添加根目录名称点击事件 const rootFolderName = rootItem.querySelector('.folder-name'); rootFolderName.addEventListener('click', () => { if (!isRootDisabled) { const rootRadio = rootItem.querySelector('input[type="radio"]'); rootRadio.checked = true; // 触发change事件 const event = new Event('change', { bubbles: true }); rootRadio.dispatchEvent(event); } else { showToast('不能移动到当前文件夹', 'error'); } }); // 获取根目录下的文件夹 const folders = allFiles.filter(f => f.is_folder && f.parent_id === null); // 如果有子文件夹,创建根目录的子容器 if (folders.length > 0) { // 更新根目录的折叠图标 const rootToggle = rootItem.querySelector('.folder-toggle'); rootToggle.style.visibility = 'visible'; rootToggle.textContent = '-'; // 默认展开显示"-" // 创建根目录的子容器 const rootChildrenContainer = document.createElement('div'); rootChildrenContainer.className = 'folder-children'; rootChildrenContainer.style.display = 'block'; // 默认展开 container.appendChild(rootChildrenContainer); // 为根目录添加折叠/展开事件 rootToggle.addEventListener('click', function(e) { e.stopPropagation(); const childrenDiv = rootItem.nextElementSibling; if (childrenDiv && childrenDiv.classList.contains('folder-children')) { const isExpanded = childrenDiv.style.display === 'block'; this.textContent = isExpanded ? '+' : '-'; childrenDiv.style.display = isExpanded ? 'none' : 'block'; } }); // 立即加载子文件夹(不使用延迟加载) for (const folder of folders) { await createFolderItem(folder, rootChildrenContainer, currentFileId, 0, allFiles); } } } catch (error) { console.error('Error loading folder tree:', error); showToast('加载文件夹失败', 'error'); } } // 搜索函数 const performSearch = async (isBatch = false) => { const searchInput = document.getElementById('searchInput'); const folderTree = document.getElementById(isBatch ? 'batchFolderTree' : 'folderTree'); if (!searchInput || !folderTree) { console.error('搜索组件未找到'); return; } const searchTerm = searchInput.value.trim().toLowerCase(); if (!searchTerm) { showToast('请输入搜索关键词', 'error'); return; } try { const response = await fetch('/api/files?all=true'); if (!response.ok) { throw new Error('获取文件列表失败'); } const data = await response.json(); let files = []; if (data.files && Array.isArray(data.files)) { files = data.files; } else if (Array.isArray(data)) { files = data; } else { console.error('API返回的数据格式不正确:', data); throw new Error('返回的数据格式不正确'); } const filteredFiles = files.filter(file => (file.filename || file.name || '').toLowerCase().includes(searchTerm) ); // 清空当前目录树 folderTree.innerHTML = ''; // 创建搜索结果容器 const resultsContainer = document.createElement('div'); resultsContainer.className = 'search-results'; resultsContainer.innerHTML = '
搜索结果:
'; folderTree.appendChild(resultsContainer); // 显示搜索结果 if (filteredFiles.length === 0) { resultsContainer.innerHTML += '
未找到匹配的文件夹
'; } else { // 创建搜索结果树 const searchTree = document.createElement('div'); searchTree.className = 'folder-tree'; resultsContainer.appendChild(searchTree); // 获取当前选中的文件 let currentSelectedFiles = []; if (isBatch) { currentSelectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked')).map(cb => cb.value); } // 显示文件夹 for (const folder of filteredFiles) { // 检查是否可以移动到该文件夹 let isDisabled = false; if (isBatch) { // 批量移动逻辑 isDisabled = currentSelectedFiles.includes(folder.id) || currentSelectedFiles.some(id => isSubfolderSync(folder.id, id, files) || // 目标文件夹是当前文件夹的子孙文件夹 isParentFolder(folder.id, id, files) // 目标文件夹是当前文件夹的父文件夹 ); } else { // 单个移动逻辑 isDisabled = FileManager.selectedFileId === folder.id || isSubfolderSync(folder.id, FileManager.selectedFileId, files) || // 目标文件夹是当前文件夹的子孙文件夹 isParentFolder(folder.id, FileManager.selectedFileId, files); // 目标文件夹是当前文件夹的父文件夹 } const folderItem = document.createElement('div'); folderItem.className = 'folder-item' + (isDisabled ? ' disabled' : ''); folderItem.innerHTML = ` 📁 ${folder.filename || folder.name} `; searchTree.appendChild(folderItem); // 添加单选框点击事件 const radio = folderItem.querySelector('input[type="radio"]'); radio.addEventListener('click', (e) => { if (isDisabled) { e.preventDefault(); showToast('不能移动到当前文件夹、子文件夹或父文件夹', 'error'); } }); // 添加文件夹名称点击事件 const folderName = folderItem.querySelector('.folder-name'); folderName.addEventListener('click', (e) => { if (!isDisabled) { const radio = folderItem.querySelector('input[type="radio"]'); radio.checked = true; // 触发一个change事件,以便其他可能依赖此事件的代码能够正常工作 const event = new Event('change', { bubbles: true }); radio.dispatchEvent(event); } else { showToast('不能移动到当前文件夹、子文件夹或父文件夹', 'error'); } }); } } } catch (error) { console.error('Search error:', error); showToast('搜索失败:' + error.message, 'error'); folderTree.innerHTML = '
搜索出错,请重试
'; } }; // 显示移动文件模态框 async function showMoveModal(fileId) { FileManager.selectedFileId = fileId; const folderTree = document.getElementById('folderTree'); // 添加搜索框和按钮 const searchContainer = document.createElement('div'); searchContainer.className = 'search-container mb-3'; searchContainer.innerHTML = `
`; folderTree.parentNode.insertBefore(searchContainer, folderTree); // 使用文件夹树加载函数,默认展开 await loadFolderTree(null, folderTree, fileId); // 确保根目录的子容器展开 const rootChildrenContainer = folderTree.querySelector('.folder-children'); if (rootChildrenContainer) { rootChildrenContainer.style.display = 'block'; } // 添加搜索功能 const searchInput = document.getElementById('searchInput'); const searchButton = document.getElementById('searchButton'); // 搜索按钮点击事件 searchButton.addEventListener('click', () => performSearch(false)); // 输入框回车事件 searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performSearch(false); } }); // 修复辅助功能,在模态框关闭时将焦点移回触发按钮 const triggerButton = document.activeElement; FileManager.moveModal._element.addEventListener('hidden.bs.modal', function () { if (triggerButton && typeof triggerButton.focus === 'function') { setTimeout(() => triggerButton.focus(), 0); } // 清理搜索框 if (searchContainer && searchContainer.parentNode) { searchContainer.parentNode.removeChild(searchContainer); } }, { once: true }); FileManager.moveModal.show(); } // 显示批量移动模态框 async function showBatchMoveModal() { const selectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked')).map(cb => cb.value); if (selectedFiles.length === 0) { showToast('请选择要移动的文件', 'error'); return; } const folderTree = document.getElementById('batchFolderTree'); // 添加搜索框和按钮 const searchContainer = document.createElement('div'); searchContainer.className = 'search-container mb-3'; searchContainer.innerHTML = `
`; folderTree.parentNode.insertBefore(searchContainer, folderTree); // 使用文件夹树加载函数,默认展开 await loadFolderTree(null, folderTree, selectedFiles); // 确保根目录的子容器展开 const rootChildrenContainer = folderTree.querySelector('.folder-children'); if (rootChildrenContainer) { rootChildrenContainer.style.display = 'block'; } // 添加搜索功能 const searchInput = document.getElementById('searchInput'); const searchButton = document.getElementById('searchButton'); // 搜索按钮点击事件 searchButton.addEventListener('click', () => performSearch(true)); // 输入框回车事件 searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { performSearch(true); } }); // 修复辅助功能,在模态框关闭时将焦点移回触发按钮 const triggerButton = document.activeElement; FileManager.batchMoveModal._element.addEventListener('hidden.bs.modal', function () { // 使用FileManager.batchMoveModal if (triggerButton && typeof triggerButton.focus === 'function') { setTimeout(() => triggerButton.focus(), 0); } // 清理搜索框 if (searchContainer && searchContainer.parentNode) { searchContainer.parentNode.removeChild(searchContainer); } }, { once: true }); FileManager.batchMoveModal.show(); // 使用FileManager.batchMoveModal } // 移动文件 async function moveFile() { const selectedFolder = document.querySelector('input[name="target_folder"]:checked'); if (!selectedFolder) { showToast('请选择目标文件夹', 'error'); return; } const targetFolderId = selectedFolder.value === 'null' ? null : selectedFolder.value; console.log('移动文件/文件夹:', { fileId: FileManager.selectedFileId, // 使用FileManager.selectedFileId targetFolderId: targetFolderId }); try { const response = await fetch(`/api/files/${FileManager.selectedFileId}/move`, { // 使用FileManager.selectedFileId method: 'PUT', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' }, credentials: 'include', body: JSON.stringify({ newParentId: targetFolderId }) }); console.log('移动响应状态:', response.status); const responseText = await response.text(); console.log('移动响应内容:', responseText); let responseData; try { responseData = JSON.parse(responseText); } catch (e) { responseData = { message: responseText }; } if (response.ok) { showToast('移动成功'); FileManager.moveModal.hide(); // 使用FileManager.moveModal // 如果当前页将没有内容了,且不是第一页,则回到上一页 if (FileManager.allFiles.length <= FileManager.pageSize && FileManager.currentPage > 1) { FileManager.currentPage--; } loadFiles(); } else { const errorMsg = responseData.error || responseData.message || '未知错误'; console.error('移动失败:', errorMsg); showToast(`移动失败: ${errorMsg}`, 'error'); } } catch (error) { console.error('移动请求错误:', error); showToast(`移动失败: ${error.message}`, 'error'); } } // 删除文件或文件夹 async function deleteFile(id, isFolder) { console.log('准备删除:', { id, isFolder }); // 存储待删除的ID和类型 FileManager.pendingDeleteId = id; FileManager.pendingDeleteIsFolder = isFolder; // 根据类型设置不同的确认消息 const confirmMessage = isFolder ? '此操作将递归删除文件夹下的所有文件,是否继续?' : '确定要删除此文件吗?'; // 设置确认消息 document.getElementById('confirmDeleteMessage').textContent = confirmMessage; // 显示确认对话框 FileManager.confirmDeleteModal.show(); } // 实际执行删除操作 async function performDelete() { console.log('执行删除操作:', { pendingDeleteId: FileManager.pendingDeleteId, pendingDeleteIsFolder: FileManager.pendingDeleteIsFolder, pendingBatchDeleteFiles: FileManager.pendingBatchDeleteFiles }); // 单个文件删除 if (FileManager.pendingDeleteId !== null) { try { console.log(`发送删除请求: /api/files/${FileManager.pendingDeleteId}`); const response = await fetch(`/api/files/${FileManager.pendingDeleteId}`, { method: 'DELETE', credentials: 'include' }); console.log('删除响应状态:', response.status); const responseText = await response.text(); console.log('删除响应内容:', responseText); let responseData; try { responseData = JSON.parse(responseText); } catch (e) { responseData = { message: responseText }; } if (response.ok) { showToast('删除成功'); // 如果当前页没有内容了,且不是第一页,则回到上一页 if (FileManager.allFiles.length <= FileManager.pageSize && FileManager.currentPage > 1) { FileManager.currentPage--; } loadFiles(); } else { showToast(`删除失败: ${responseData.error || responseData.message || '未知错误'}`, 'error'); } } catch (error) { console.error('Delete error:', error); showToast(`删除失败: ${error.message}`, 'error'); } finally { // 关闭确认对话框 if (FileManager.confirmDeleteModal) { FileManager.confirmDeleteModal.hide(); } // 重置待删除项 FileManager.pendingDeleteId = null; FileManager.pendingDeleteIsFolder = false; } } // 批量删除 else if (FileManager.pendingBatchDeleteFiles !== null && FileManager.pendingBatchDeleteFiles.length > 0) { console.log('执行批量删除:', FileManager.pendingBatchDeleteFiles); let success = true; let successCount = 0; let failCount = 0; for (const id of FileManager.pendingBatchDeleteFiles) { try { console.log(`发送批量删除请求: /api/files/${id}`); const response = await fetch(`/api/files/${id}`, { method: 'DELETE', credentials: 'include' }); console.log(`ID ${id} 删除响应状态:`, response.status); if (response.ok) { successCount++; } else { failCount++; success = false; } } catch (error) { console.error(`ID ${id} 删除错误:`, error); failCount++; success = false; } } if (success) { showToast('批量删除成功'); } else if (successCount > 0) { showToast(`部分文件删除成功 (${successCount}/${FileManager.pendingBatchDeleteFiles.length})`, 'warning'); } else { showToast('批量删除失败', 'error'); } // 如果当前页将没有内容了,且不是第一页,则回到上一页 const remainingCount = FileManager.allFiles.length - FileManager.pendingBatchDeleteFiles.length; const currentPageStart = (FileManager.currentPage - 1) * FileManager.pageSize; if (remainingCount <= currentPageStart && FileManager.currentPage > 1) { FileManager.currentPage--; } loadFiles(); // 关闭确认对话框 if (FileManager.confirmDeleteModal) { FileManager.confirmDeleteModal.hide(); } // 重置待删除项 FileManager.pendingBatchDeleteFiles = null; } else { console.warn('没有指定要删除的文件'); if (FileManager.confirmDeleteModal) { FileManager.confirmDeleteModal.hide(); } } } // 全选/取消全选 function toggleSelectAll() { const checkboxes = document.querySelectorAll('.file-checkbox'); const selectAll = document.getElementById('selectAll'); checkboxes.forEach(checkbox => checkbox.checked = selectAll.checked); } // 批量删除 async function deleteSelected() { const selectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked')).map(cb => cb.value); if (selectedFiles.length === 0) { showToast('请选择要删除的文件', 'error'); return; } // 存储待删除的文件列表 FileManager.pendingBatchDeleteFiles = selectedFiles; // 设置确认消息 document.getElementById('confirmDeleteMessage').textContent = `确定要删除选中的 ${selectedFiles.length} 个文件/文件夹吗?`; // 显示确认对话框 FileManager.confirmDeleteModal.show(); } // 批量移动文件 async function batchMoveFiles() { const selectedFolder = document.querySelector('input[name="batch_target_folder"]:checked'); if (!selectedFolder) { showToast('请选择目标文件夹', 'error'); return; } const selectedFiles = Array.from(document.querySelectorAll('.file-checkbox:checked')).map(cb => cb.value); const targetFolderId = selectedFolder.value === 'null' ? null : selectedFolder.value; // 创建进度条容器 const progressContainer = document.createElement('div'); progressContainer.className = 'progress-container mt-3'; progressContainer.innerHTML = `
0%
正在移动文件...
`; // 添加到模态框 const modalBody = FileManager.batchMoveModal._element.querySelector('.modal-body'); modalBody.appendChild(progressContainer); // 禁用确认按钮 const confirmBtn = FileManager.batchMoveModal._element.querySelector('.modal-footer .btn-primary'); confirmBtn.disabled = true; try { // 使用 Promise.all 并行处理移动请求 const totalFiles = selectedFiles.length; let successCount = 0; let failedCount = 0; // 创建所有移动请求 const movePromises = selectedFiles.map(async (fileId, index) => { try { const response = await fetch(`/api/files/${fileId}/move`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ newParentId: targetFolderId }) }); if (response.ok) { successCount++; } else { failedCount++; console.error(`移动文件 ${fileId} 失败:`, await response.text()); } // 更新进度 const progress = Math.round(((index + 1) / totalFiles) * 100); const progressBar = progressContainer.querySelector('.progress-bar'); progressBar.style.width = `${progress}%`; progressBar.textContent = `${progress}%`; } catch (error) { failedCount++; console.error(`移动文件 ${fileId} 时发生错误:`, error); } }); // 等待所有移动操作完成 await Promise.all(movePromises); // 显示结果 if (successCount === totalFiles) { showToast('批量移动成功'); } else if (successCount > 0) { showToast(`部分文件移动成功 (${successCount}/${totalFiles})`, 'warning'); } else { showToast('所有文件移动失败', 'error'); } // 关闭模态框并刷新文件列表 FileManager.batchMoveModal.hide(); // 如果当前页将没有内容了,且不是第一页,则回到上一页 const remainingCount = FileManager.allFiles.length - selectedFiles.length; const currentPageStart = (FileManager.currentPage - 1) * FileManager.pageSize; if (remainingCount <= currentPageStart && FileManager.currentPage > 1) { FileManager.currentPage--; } loadFiles(); } catch (error) { console.error('批量移动出错:', error); showToast('批量移动失败', 'error'); } finally { // 清理进度条 if (progressContainer.parentNode) { progressContainer.parentNode.removeChild(progressContainer); } // 恢复确认按钮 if (confirmBtn) { confirmBtn.disabled = false; } } } // 搜索文件 async function searchFiles() { const searchInput = document.getElementById('fileSearchInput'); const searchTerm = searchInput.value.trim().toLowerCase(); if (!searchTerm) { // 如果搜索框为空,显示所有文件 loadFiles(); return; } try { const response = await fetch('/api/files?all=true'); if (!response.ok) { throw new Error('获取文件列表失败'); } const data = await response.json(); let files = []; if (data.files && Array.isArray(data.files)) { files = data.files; } else if (Array.isArray(data)) { files = data; } else { console.error('API返回的数据格式不正确:', data); throw new Error('返回的数据格式不正确'); } const filteredFiles = files.filter(file => (file.filename || file.name || '').toLowerCase().includes(searchTerm) ); // 更新文件列表显示 const tbody = document.getElementById('fileList'); tbody.innerHTML = ''; if (filteredFiles.length === 0) { tbody.innerHTML = '未找到匹配的文件或文件夹'; return; } // 用于存储加载文件夹大小的Promise const folderSizePromises = []; // 显示搜索结果 filteredFiles.forEach((file, index) => { const tr = document.createElement('tr'); // 如果是文件夹,创建一个大小加载的Promise let folderSizePromise = null; if (file.is_folder) { folderSizePromise = calculateFolderSize(file.id); folderSizePromises.push({ id: file.id, promise: folderSizePromise }); } tr.innerHTML = ` ${index + 1} ${file.is_folder ? '📁 ' : '📄 '}${ file.is_folder ? `${file.filename || file.name}` : `${file.filename || file.name}` } ${file.is_folder ? '计算中...' : formatSize(file.size)} ${moment(file.created_at).format('YYYY-MM-DD HH:mm:ss')}
${file.is_folder ? '' : ` ${showPreviewBtn ? `` : ''} `}
`; // 添加文件夹链接点击事件 const folderLink = tr.querySelector('.folder-link'); if (folderLink) { folderLink.addEventListener('click', (e) => { e.preventDefault(); FileManager.currentFolderId = folderLink.dataset.id; FileManager.currentPage = 1; // 重置为第一页 loadFiles(); }); } tbody.appendChild(tr); }); // 等待所有文件夹大小计算完成并更新UI if (folderSizePromises.length > 0) { Promise.all(folderSizePromises.map(item => item.promise)) .then(sizes => { folderSizePromises.forEach((item, index) => { const sizeCell = document.querySelector(`.file-size[data-id="${item.id}"]`); if (sizeCell) { sizeCell.textContent = formatSize(sizes[index]); } }); }) .catch(error => { console.error('Error updating folder sizes:', error); }); } } catch (error) { console.error('搜索出错:', error); showToast('搜索失败,请重试', 'error'); } } // 初始化 tooltips function initPopovers() { // 销毁所有现有的tooltips document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => { const tooltip = bootstrap.Tooltip.getInstance(el); if (tooltip) { tooltip.dispose(); } }); // 初始化新的tooltips document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => { const tooltip = new bootstrap.Tooltip(el, { html: true, placement: 'right', trigger: 'hover', delay: { show: 0, hide: 0 }, template: '', popperConfig: function(defaultBsPopperConfig) { return { ...defaultBsPopperConfig, modifiers: [ ...(defaultBsPopperConfig.modifiers || []), { name: 'offset', options: { offset: [0, 0], }, } ] }; } }); }); } // 显示重命名模态框 function showRenameModal(id, currentName) { FileManager.selectedItemId = id; const nameInput = document.getElementById('newName'); nameInput.value = currentName; // 在模态框显示后聚焦到输入框并选中文本 FileManager.renameModal._element.addEventListener('shown.bs.modal', () => { nameInput.focus(); nameInput.select(); }, { once: true }); FileManager.renameModal.show(); } // 执行重命名 async function renameItem() { const newName = document.getElementById('newName').value.trim(); if (!newName) { showToast('请输入新名称', 'error'); return; } try { const response = await fetch(`/api/files/${selectedItemId}/rename`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ newName }) }); if (response.ok) { showToast('重命名成功'); renameModal.hide(); loadFiles(); } else { const data = await response.json(); showToast(data.error || '重命名失败', 'error'); } } catch (error) { console.error('Rename error:', error); showToast('重命名失败', 'error'); } } // 检查登录状态 async function checkLoginStatus() { try { const response = await fetch('/api/auth/status', { credentials: 'include' }); if (!response.ok) { throw new Error('会话检查失败'); } const data = await response.json(); if (!data.loggedIn) { // 只有在当前页面不是登录页时才跳转 if (!window.location.pathname.includes('login.html')) { window.location.href = '/login.html'; } return false; } // 更新用户信息显示 const userInfo = document.getElementById('userInfo'); if (userInfo) { userInfo.textContent = `欢迎, ${data.user.username}`; } return true; // 返回登录状态 } catch (error) { console.error('检查登录状态失败:', error); // 只有在当前页面不是登录页时才跳转 if (!window.location.pathname.includes('login.html')) { window.location.href = '/login.html'; } return false; } } // 打开Telegram文件 async function openTelegramFile(fileId) { try { // 获取文件信息 const file = FileManager.allFiles.find(f => String(f.id) === String(fileId)); if (!file) { throw new Error('文件不存在'); } // 获取消息ID const messageId = file.message_id || (file.tg_file_id && file.tg_file_id.includes(':') ? file.tg_file_id.split(':')[1] : null); if (!messageId) { throw new Error('无法获取消息ID'); } // 尝试构建电报链接(t.me或直接获取文件) const chatId = file.file_id && file.file_id.includes(':') ? file.file_id.split(':')[0] : null; if (chatId) { // 构建Telegram链接 const url = `https://t.me/c/${chatId.replace('-100', '')}/${messageId}`; window.open(url, '_blank'); } else { // 回退到直接下载 const encodedFileName = encodeURIComponent(file.filename || file.name || ''); window.open(`/proxy/${fileId}?original_name=${encodedFileName}`, '_blank'); } } catch (error) { console.error('打开Telegram文件失败:', error); showToast('打开Telegram文件失败: ' + error.message, 'error'); } } // 复制文本到剪贴板 function copyToClipboard(elementId) { const element = document.getElementById(elementId); element.select(); element.setSelectionRange(0, 99999); // 对于移动设备 try { document.execCommand('copy'); showToast('已复制到剪贴板'); } catch (err) { navigator.clipboard.writeText(element.value) .then(() => showToast('已复制到剪贴板')) .catch(err => { console.error('复制失败:', err); showToast('复制失败,请手动复制', 'error'); }); } } // 设置每页显示数量 function setPageSize(size) { window.FileManager.pageSize = parseInt(size); window.FileManager.currentPage = 1; // 重置到第一页 loadFiles(); } // 页面初始化函数 async function initPage() { try { console.log('页面初始化开始...'); // 从本地存储中恢复页面大小设置 const savedPageSize = localStorage.getItem('pageSize'); if (savedPageSize) { pageSize = parseInt(savedPageSize, 10); // 更新选择框的值 const pageSizeSelect = document.getElementById('pageSize'); if (pageSizeSelect) { pageSizeSelect.value = pageSize; } } // 为页面大小选择器添加事件监听 const pageSizeSelect = document.getElementById('pageSize'); if (pageSizeSelect) { pageSizeSelect.addEventListener('change', (e) => { setPageSize(e.target.value); }); } // 添加搜索按钮和输入框的事件监听 const searchButton = document.getElementById('fileSearchButton'); const searchInput = document.getElementById('fileSearchInput'); if (searchButton) { searchButton.addEventListener('click', searchFiles); console.log('搜索按钮事件已绑定'); } if (searchInput) { searchInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { searchFiles(); } }); console.log('搜索输入框事件已绑定'); } // 添加修改密码按钮事件监听 const changePasswordBtn = document.getElementById('changePasswordBtn'); if (changePasswordBtn) { changePasswordBtn.addEventListener('click', () => { const changePasswordModal = new bootstrap.Modal(document.getElementById('changePasswordModal')); changePasswordModal.show(); }); console.log('修改密码按钮事件已绑定'); } // 添加退出登录按钮事件监听 const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', logout); console.log('退出登录按钮事件已绑定'); } // 添加保存密码按钮事件监听 const savePasswordBtn = document.getElementById('savePasswordBtn'); if (savePasswordBtn) { savePasswordBtn.addEventListener('click', changePassword); console.log('保存密码按钮事件已绑定'); } // 先检查登录状态 const isLoggedIn = await checkLoginStatus(); if (!isLoggedIn) { console.log('用户未登录,不加载文件列表'); return; } // 在主页时加载文件列表 if (window.location.pathname.includes('index.html') || window.location.pathname === '/' || window.location.pathname === '') { console.log('页面初始化中,加载文件列表...'); await loadFiles(); } } catch (error) { console.error('页面初始化错误:', error); } } // 退出登录 async function logout() { try { console.log('执行退出登录操作'); const response = await fetch('/api/auth/logout', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' } }); // 只显示一次提示 showToast('退出登录中...'); // 清除Cookie document.cookie = 'session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; // 延迟跳转,让用户看到提示 setTimeout(() => { window.location.href = '/login.html'; }, 1000); } catch (error) { console.error('退出登录错误:', error); // 清除Cookie并跳转 document.cookie = 'session=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;'; window.location.href = '/login.html'; } } // 修改密码 async function changePassword() { const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; // 检查密码是否符合要求 if (!currentPassword) { showToast('请输入当前密码', 'error'); return; } if (!newPassword) { showToast('请输入新密码', 'error'); return; } if (newPassword !== confirmPassword) { showToast('两次输入的新密码不一致', 'error'); return; } try { const response = await fetch('/api/auth/change-password', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPassword, newPassword }) }); const data = await response.json(); if (response.ok) { showToast('密码修改成功'); // 隐藏模态框 const changePasswordModal = bootstrap.Modal.getInstance(document.getElementById('changePasswordModal')); changePasswordModal.hide(); // 重置表单 document.getElementById('changePasswordForm').reset(); } else { showToast(data.error || '密码修改失败', 'error'); } } catch (error) { console.error('修改密码错误:', error); showToast('修改密码失败,请重试', 'error'); } } // 更新分页信息 function updatePagination(totalItems) { try { // 计算总页数 FileManager.totalPages = Math.ceil(totalItems / FileManager.pageSize) || 1; // 确保当前页在有效范围内 if (FileManager.currentPage > FileManager.totalPages) { FileManager.currentPage = FileManager.totalPages; } else if (FileManager.currentPage < 1) { FileManager.currentPage = 1; } // 根据是否需要分页来显示或隐藏分页控件 const paginationElement = document.getElementById('pagination'); if (!paginationElement) { console.warn('分页元素不存在'); return; } const pageInfoElement = document.getElementById('pageInfo'); if (totalItems > FileManager.pageSize) { // 显示分页控件 paginationElement.style.cssText = 'display: block !important'; if (pageInfoElement) { pageInfoElement.textContent = `${FileManager.currentPage} / ${FileManager.totalPages}`; } // 更新上一页和下一页按钮状态 const prevButton = document.getElementById('prevPage'); const nextButton = document.getElementById('nextPage'); if (prevButton) { prevButton.parentElement.classList.toggle('disabled', FileManager.currentPage <= 1); } if (nextButton) { nextButton.parentElement.classList.toggle('disabled', FileManager.currentPage >= FileManager.totalPages); } } else { // 隐藏分页控件 paginationElement.style.cssText = 'display: none !important'; } console.log('分页信息已更新:', { currentPage: FileManager.currentPage, totalPages: FileManager.totalPages, pageSize: FileManager.pageSize, totalItems }); } catch (error) { console.error('更新分页信息出错:', error); } } // 页面加载时执行初始化 let hasInitialized = false; document.addEventListener('DOMContentLoaded', () => { if (!hasInitialized) { initPage(); hasInitialized = true; } }); // 定期检查登录状态(每5分钟) setInterval(checkLoginStatus, 5 * 60 * 1000); // 预览文件 async function previewFile(fileId) { try { console.log('预览文件,文件ID:', fileId, '类型:', typeof fileId); console.log('当前文件列表:', FileManager.allFiles); // 确保fileId是字符串类型 const fileIdStr = String(fileId); // 获取文件信息 const fileInfo = FileManager.allFiles.find(file => String(file.id) === fileIdStr); console.log('找到的文件信息:', fileInfo); if (!fileInfo) { // 尝试从服务器获取文件信息 try { console.log('本地未找到文件信息,尝试从服务器获取'); const response = await fetch(`/api/files/${fileIdStr}`); if (response.ok) { const fileData = await response.json(); console.log('从服务器获取的文件信息:', fileData); // 构建预览URL const encodedFileName = encodeURIComponent(fileData.filename || fileData.name || ''); let previewUrl; // 对于图片、音频和视频文件,使用代理 if (fileData.mime_type && ( fileData.mime_type.startsWith('image/') || fileData.mime_type.startsWith('audio/') || fileData.mime_type.startsWith('video/') )) { previewUrl = `/proxy/${fileIdStr}?original_name=${encodedFileName}`; // 对于图片,添加 Content-Disposition: inline if (fileData.mime_type.startsWith('image/')) { previewUrl += '&disposition=inline'; } // 对于音频和视频,使用 HTML5 播放器页面 if (fileData.mime_type.startsWith('audio/') || fileData.mime_type.startsWith('video/')) { // 使用绝对路径,避免相对路径问题 const playerUrl = new URL(window.location.origin); playerUrl.pathname = `/proxy/${fileIdStr}`; playerUrl.search = `?player=1&original_name=${encodedFileName}`; window.open(playerUrl.toString(), '_blank'); return; } } else { // 对于其他类型的文件,直接使用原始路径 previewUrl = `/api/files/${fileIdStr}/download`; } // 打开新窗口预览文件 window.open(previewUrl, '_blank'); return; } } catch (serverError) { console.error('从服务器获取文件信息失败:', serverError); } throw new Error('文件不存在'); } // 构建预览URL const encodedFileName = encodeURIComponent(fileInfo.filename || fileInfo.name || ''); let previewUrl; // 对于图片、音频和视频文件,使用代理 if (fileInfo.mime_type && ( fileInfo.mime_type.startsWith('image/') || fileInfo.mime_type.startsWith('audio/') || fileInfo.mime_type.startsWith('video/') )) { previewUrl = `/proxy/${fileIdStr}?original_name=${encodedFileName}`; // 对于图片,添加 Content-Disposition: inline if (fileInfo.mime_type.startsWith('image/')) { previewUrl += '&disposition=inline'; } // 对于音频和视频,使用 HTML5 播放器页面 if (fileInfo.mime_type.startsWith('audio/') || fileInfo.mime_type.startsWith('video/')) { // 使用绝对路径,避免相对路径问题 const playerUrl = new URL(window.location.origin); playerUrl.pathname = `/proxy/${fileIdStr}`; playerUrl.search = `?player=1&original_name=${encodedFileName}`; window.open(playerUrl.toString(), '_blank'); return; } } else { // 对于其他类型的文件,直接使用原始路径 previewUrl = `/api/files/${fileIdStr}/download`; } // 打开新窗口预览文件 window.open(previewUrl, '_blank'); } catch (error) { console.error('预览文件失败:', error); alert('预览文件失败: ' + error.message); } }