const { createApp } = Vue; createApp({ data() { return { // API配置 // API配置 - 通过nginx代理访问 apiBase: window.location.protocol + '//' + window.location.host, // 用户状态 isLoggedIn: false, user: null, token: null, // 视图状态 currentView: 'files', isLogin: true, fileViewMode: 'grid', // 文件显示模式: grid 大图标, list 列表 shareViewMode: 'list', // 分享显示模式: grid 大图标, list 列表 // 表单数据 loginForm: { username: '', password: '' }, registerForm: { username: '', email: '', password: '' }, // SFTP配置表单 ftpConfigForm: { ftp_host: '', ftp_port: 22, ftp_user: '', ftp_password: '', http_download_base_url: '' }, showFtpConfigModal: false, // 修改密码表单 changePasswordForm: { current_password: '', new_password: '' }, // 用户名修改表单 usernameForm: { newUsername: '' }, currentPath: '/', files: [], loading: false, // 分享管理 shares: [], showShareAllModal: false, showShareFileModal: false, shareAllForm: { password: "", expiryType: "never", customDays: 7 }, shareFileForm: { fileName: "", filePath: "", password: "", expiryType: "never", customDays: 7 }, shareResult: null, // 文件重命名 showRenameModal: false, renameForm: { oldName: "", newName: "", path: "" }, // 上传 showUploadModal: false, uploadProgress: 0, uploadedBytes: 0, totalBytes: 0, uploadingFileName: '', isDragging: false, // 上传工具下载 downloadingTool: false, // 管理员 adminUsers: [], showResetPwdModal: false, resetPwdUser: {}, newPassword: '', // 密码重置审核 passwordResetRequests: [], // 文件审查 showFileInspectionModal: false, inspectionUser: null, inspectionFiles: [], inspectionPath: '/', inspectionLoading: false, inspectionViewMode: 'grid', // 文件审查显示模式: grid 大图标, list 列表 // 忘记密码 showForgotPasswordModal: false, forgotPasswordForm: { username: '', new_password: '' }, // 系统设置 systemSettings: { maxUploadSizeMB: 100 }, // Toast通知 toasts: [], toastIdCounter: 0, // 提示信息 errorMessage: '', successMessage: '', // 存储相关 storageType: 'sftp', // 当前使用的存储类型 storagePermission: 'sftp_only', // 存储权限 localQuota: 0, // 本地存储配额(字节) localUsed: 0, // 本地存储已使用(字节) // 右键菜单 showContextMenu: false, contextMenuX: 0, contextMenuY: 0, contextMenuFile: null, // 长按检测 longPressTimer: null, // 媒体预览 showImageViewer: false, showVideoPlayer: false, showAudioPlayer: false, currentMediaUrl: '', currentMediaName: '', currentMediaType: '', // 'image', 'video', 'audio' longPressDuration: 500, // 长按时间(毫秒) // 管理员编辑用户存储权限 showEditStorageModal: false, editStorageForm: { userId: null, username: '', storage_permission: 'sftp_only', local_storage_quota_value: 1, // 配额数值 quota_unit: 'GB' // 配额单位:MB 或 GB }, // 服务器存储统计 serverStorageStats: { totalDisk: 0, usedDisk: 0, availableDisk: 0, totalUserQuotas: 0, totalUserUsed: 0, totalUsers: 0 } }; }, computed: { pathParts() { return this.currentPath.split('/').filter(p => p !== ''); }, // 格式化配额显示 localQuotaFormatted() { return this.formatBytes(this.localQuota); }, localUsedFormatted() { return this.formatBytes(this.localUsed); }, // 配额使用百分比 quotaPercentage() { if (this.localQuota === 0) return 0; return Math.round((this.localUsed / this.localQuota) * 100); }, // 存储类型显示文本 storageTypeText() { return this.storageType === 'local' ? '本地存储' : 'SFTP存储'; } }, methods: { // 格式化文件大小 formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]; }, // 拖拽上传处理 handleDragEnter(e) { e.preventDefault(); e.stopPropagation(); this.isDragging = true; }, handleDragOver(e) { e.preventDefault(); e.stopPropagation(); this.isDragging = true; }, handleDragLeave(e) { e.preventDefault(); e.stopPropagation(); // 使用更可靠的检测:检查鼠标实际位置 const container = e.currentTarget; const rect = container.getBoundingClientRect(); const x = e.clientX; const y = e.clientY; // 如果鼠标位置在容器边界外,隐藏覆盖层 // 添加5px的容差,避免边界问题 const margin = 5; const isOutside = x < rect.left - margin || x > rect.right + margin || y < rect.top - margin || y > rect.bottom + margin; if (isOutside) { this.isDragging = false; return; } // 备用检测:检查 relatedTarget const related = e.relatedTarget; if (!related || !container.contains(related)) { this.isDragging = false; } }, async handleDrop(e) { e.preventDefault(); e.stopPropagation(); this.isDragging = false; const files = e.dataTransfer.files; if (files.length > 0) { const file = files[0]; await this.uploadFile(file); } }, // ===== 认证相关 ===== toggleAuthMode() { this.isLogin = !this.isLogin; this.errorMessage = ''; this.successMessage = ''; }, async handleLogin() { this.errorMessage = ''; try { const response = await axios.post(`${this.apiBase}/api/login`, this.loginForm); if (response.data.success) { this.token = response.data.token; this.user = response.data.user; this.isLoggedIn = true; // 保存token到localStorage localStorage.setItem('token', this.token); localStorage.setItem('user', JSON.stringify(this.user)); // 直接从登录响应中获取存储信息 this.storagePermission = this.user.storage_permission || 'sftp_only'; this.storageType = this.user.current_storage_type || 'sftp'; this.localQuota = this.user.local_storage_quota || 0; this.localUsed = this.user.local_storage_used || 0; console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType); // 管理员直接跳转到管理后台 if (this.user.is_admin) { this.currentView = 'admin'; } // 普通用户:检查存储权限 else { // 如果用户可以使用本地存储,直接进入文件页面 if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') { this.currentView = 'files'; this.loadFiles('/'); } // 如果仅SFTP模式,需要检查是否配置了SFTP else if (this.storagePermission === 'sftp_only') { if (this.user.has_ftp_config) { this.currentView = 'files'; this.loadFiles('/'); } else { this.currentView = 'settings'; alert('欢迎!请先配置您的SFTP服务器'); } } else { // 默认行为:跳转到文件页面 this.currentView = 'files'; this.loadFiles('/'); } } } } catch (error) { this.errorMessage = error.response?.data?.message || '登录失败'; } }, async handleRegister() { this.errorMessage = ''; this.successMessage = ''; try { const response = await axios.post(`${this.apiBase}/api/register`, this.registerForm); if (response.data.success) { this.successMessage = '注册成功!请登录'; this.isLogin = true; // 清空表单 this.registerForm = { username: '', email: '', password: '' }; } } catch (error) { const errorData = error.response?.data; if (errorData?.errors) { this.errorMessage = errorData.errors.map(e => e.msg).join(', '); } else { this.errorMessage = errorData?.message || '注册失败'; } } }, async updateFtpConfig() { try { const response = await axios.post( `${this.apiBase}/api/user/update-ftp`, this.ftpConfigForm, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert('SFTP配置已保存!'); // 更新用户信息 this.user.has_ftp_config = 1; // 刷新到文件页面 this.currentView = 'files'; this.loadFiles('/'); } } catch (error) { alert('配置失败: ' + (error.response?.data?.message || error.message)); } }, async updateAdminProfile() { try { const response = await axios.post( `${this.apiBase}/api/admin/update-profile`, { username: this.adminProfileForm.username }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert('用户名已更新!请重新登录。'); // 更新token和用户信息 if (response.data.token) { this.token = response.data.token; localStorage.setItem('token', response.data.token); } if (response.data.user) { this.user = response.data.user; localStorage.setItem('user', JSON.stringify(response.data.user)); } // 重新登录 this.logout(); } } catch (error) { alert('修改失败: ' + (error.response?.data?.message || error.message)); } }, async changePassword() { if (!this.changePasswordForm.current_password) { alert('请输入当前密码'); return; } if (this.changePasswordForm.new_password.length < 6) { alert('新密码至少6个字符'); return; } try { const response = await axios.post( `${this.apiBase}/api/user/change-password`, { current_password: this.changePasswordForm.current_password, new_password: this.changePasswordForm.new_password }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert('密码修改成功!'); this.changePasswordForm.new_password = ''; this.changePasswordForm.current_password = ''; } } catch (error) { alert('密码修改失败: ' + (error.response?.data?.message || error.message)); } }, async loadFtpConfig() { try { const response = await axios.get( `${this.apiBase}/api/user/profile`, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success && response.data.user) { const user = response.data.user; // 填充SFTP配置表单(密码不回显) this.ftpConfigForm.ftp_host = user.ftp_host || ''; this.ftpConfigForm.ftp_port = user.ftp_port || 22; this.ftpConfigForm.ftp_user = user.ftp_user || ''; this.ftpConfigForm.ftp_password = ''; // 密码不回显 this.ftpConfigForm.http_download_base_url = user.http_download_base_url || ''; } } catch (error) { console.error('加载SFTP配置失败:', error); } }, // 处理配置文件上传 handleConfigFileUpload(event) { const file = event.target.files[0]; if (!file) return; this.processConfigFile(file); // 清空文件选择,允许重复选择同一文件 event.target.value = ''; }, // 处理配置文件拖拽 handleConfigFileDrop(event) { const file = event.dataTransfer.files[0]; if (!file) return; // 检查文件扩展名 if (!file.name.toLowerCase().endsWith('.inf')) { this.showToast('error', '错误', '只支持 .inf 格式的配置文件'); return; } this.processConfigFile(file); // 恢复背景色 event.currentTarget.style.background = '#f8f9ff'; }, // 处理配置文件 async processConfigFile(file) { const reader = new FileReader(); reader.onload = async (e) => { try { const content = e.target.result; const config = this.parseConfigFile(content); if (config) { // 填充表单 this.ftpConfigForm.ftp_host = config.ip || ''; this.ftpConfigForm.ftp_port = config.port || 22; this.ftpConfigForm.ftp_user = config.id || ''; this.ftpConfigForm.ftp_password = config.pw || ''; this.ftpConfigForm.http_download_base_url = config.arr || ''; // 提示用户配置已导入,需要确认后保存 this.showToast('success', '成功', '配置文件已导入!请检查并确认信息后点击"保存配置"按钮'); } else { this.showToast('error', '错误', '配置文件格式不正确,请检查文件内容'); } } catch (error) { console.error('解析配置文件失败:', error); this.showToast('error', '错误', '解析配置文件失败: ' + error.message); } }; reader.readAsText(file); }, // 解析INI格式的配置文件 parseConfigFile(content) { const lines = content.split('\n'); const config = {}; for (let line of lines) { line = line.trim(); // 跳过空行和注释 if (!line || line.startsWith('#') || line.startsWith(';') || line.startsWith('[')) { continue; } // 解析 key=value 格式 const equalsIndex = line.indexOf('='); if (equalsIndex > 0) { const key = line.substring(0, equalsIndex).trim(); const value = line.substring(equalsIndex + 1).trim(); config[key] = value; } } // 验证必需字段 if (config.ip && config.id && config.pw && config.port) { return config; } return null; }, async updateUsername() { if (!this.usernameForm.newUsername || this.usernameForm.newUsername.length < 3) { alert('用户名至少3个字符'); return; } try { const response = await axios.post( `${this.apiBase}/api/user/update-username`, { username: this.usernameForm.newUsername }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert('用户名修改成功!请重新登录'); // 更新本地用户信息 this.user.username = this.usernameForm.newUsername; localStorage.setItem('user', JSON.stringify(this.user)); this.usernameForm.newUsername = ''; } } catch (error) { alert('用户名修改失败: ' + (error.response?.data?.message || error.message)); } }, async updateProfile() { try { const response = await axios.post( `${this.apiBase}/api/user/update-profile`, { email: this.profileForm.email }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert('邮箱已更新!'); // 更新本地用户信息 if (response.data.user) { this.user = response.data.user; localStorage.setItem('user', JSON.stringify(this.user)); } } } catch (error) { alert('更新失败: ' + (error.response?.data?.message || error.message)); } }, logout() { this.isLoggedIn = false; this.user = null; this.token = null; localStorage.removeItem('token'); localStorage.removeItem('user'); }, // 检查本地存储的登录状态 async checkLoginStatus() { const token = localStorage.getItem('token'); const user = localStorage.getItem('user'); if (token && user) { this.token = token; this.user = JSON.parse(user); this.isLoggedIn = true; // 从localStorage中的用户信息初始化存储相关字段 this.storagePermission = this.user.storage_permission || 'sftp_only'; this.storageType = this.user.current_storage_type || 'sftp'; this.localQuota = this.user.local_storage_quota || 0; this.localUsed = this.user.local_storage_used || 0; console.log('[页面加载] 存储权限:', this.storagePermission, '存储类型:', this.storageType); // 加载最新的用户信息(异步更新) this.loadUserProfile(); // 管理员跳转到管理后台 if (this.user.is_admin) { this.currentView = 'admin'; } // 普通用户:根据存储权限决定跳转 else { // 如果用户可以使用本地存储,直接加载文件 if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') { this.loadFiles('/'); } // 如果仅SFTP模式,需要检查是否配置了SFTP else if (this.storagePermission === 'sftp_only') { if (this.user.has_ftp_config) { this.loadFiles('/'); } else { this.currentView = 'settings'; } } else { // 默认加载文件 this.loadFiles('/'); } } } }, // 检查URL参数 checkUrlParams() { const urlParams = new URLSearchParams(window.location.search); const action = urlParams.get('action'); if (action === 'login') { this.isLogin = true; } else if (action === 'register') { this.isLogin = false; } }, // ===== 文件管理 ===== async loadFiles(path) { this.loading = true; this.currentPath = path; try { const response = await axios.get(`${this.apiBase}/api/files`, { params: { path }, headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.files = response.data.items; // 更新存储类型信息 if (response.data.storageType) { this.storageType = response.data.storageType; } if (response.data.storagePermission) { this.storagePermission = response.data.storagePermission; } // 更新用户本地存储信息 await this.loadUserProfile(); } } catch (error) { console.error('加载文件失败:', error); alert('加载文件失败: ' + (error.response?.data?.message || error.message)); if (error.response?.status === 401) { this.logout(); } } finally { this.loading = false; } }, handleFileClick(file) { if (file.isDirectory) { const newPath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`; this.loadFiles(newPath); } else { // 检查文件类型,打开相应的预览 if (file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i)) { this.openImageViewer(file); } else if (file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i)) { this.openVideoPlayer(file); } else if (file.name.match(/\.(mp3|wav|flac|aac|ogg|m4a)$/i)) { this.openAudioPlayer(file); } // 其他文件类型不做任何操作,用户可以通过右键菜单下载 } }, navigateToPath(path) { this.loadFiles(path); }, navigateToIndex(index) { const parts = this.pathParts.slice(0, index + 1); const path = '/' + parts.join('/'); this.loadFiles(path); }, downloadFile(file) { console.log("[DEBUG] 下载文件:", file); if (file.httpDownloadUrl) { // 如果配置了HTTP下载URL,使用HTTP直接下载 console.log("[DEBUG] 使用HTTP下载:", file.httpDownloadUrl); window.open(file.httpDownloadUrl, "_blank"); } else { // 如果没有配置HTTP URL,通过后端SFTP下载 console.log("[DEBUG] 使用SFTP下载"); const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`; // 使用标签下载,通过URL参数传递token const link = document.createElement('a'); link.href = `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}&token=${this.token}`; link.setAttribute('download', file.name); document.body.appendChild(link); link.click(); document.body.removeChild(link); } }, // ===== 文件操作 ===== openRenameModal(file) { this.renameForm.oldName = file.name; this.renameForm.newName = file.name; this.renameForm.path = this.currentPath; this.showRenameModal = true; }, async renameFile() { if (!this.renameForm.newName || this.renameForm.newName === this.renameForm.oldName) { alert('请输入新的文件名'); return; } try { const response = await axios.post( `${this.apiBase}/api/files/rename`, this.renameForm, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.showToast('success', '成功', '文件已重命名'); this.showRenameModal = false; this.loadFiles(this.currentPath); } } catch (error) { console.error('重命名失败:', error); this.showToast('error', '错误', error.response?.data?.message || '重命名失败'); } }, confirmDeleteFile(file) { const fileType = file.isDirectory ? '文件夹' : '文件'; const warning = file.isDirectory ? "\n注意:只能删除空文件夹!" : ""; if (confirm(`确定要删除${fileType} "${file.name}" 吗?此操作无法撤销!${warning}`)) { this.deleteFile(file); } }, // ===== 右键菜单和长按功能 ===== // 显示右键菜单(PC端) showFileContextMenu(file, event) { if (file.isDirectory) return; // 文件夹不显示菜单 event.preventDefault(); this.contextMenuFile = file; this.contextMenuX = event.clientX; this.contextMenuY = event.clientY; this.showContextMenu = true; // 点击其他地方关闭菜单 this.$nextTick(() => { document.addEventListener('click', this.hideContextMenu, { once: true }); }); }, // 隐藏右键菜单 hideContextMenu() { this.showContextMenu = false; this.contextMenuFile = null; }, // 长按开始(移动端) handleLongPressStart(file, event) { if (file.isDirectory) return; // 文件夹不响应长按 // 阻止默认的长按行为(如文本选择) event.preventDefault(); this.longPressTimer = setTimeout(() => { // 触发长按菜单 this.contextMenuFile = file; // 获取触摸点位置 const touch = event.touches[0]; this.contextMenuX = touch.clientX; this.contextMenuY = touch.clientY; this.showContextMenu = true; // 触摸震动反馈(如果支持) if (navigator.vibrate) { navigator.vibrate(50); } // 点击其他地方关闭菜单 this.$nextTick(() => { document.addEventListener('click', this.hideContextMenu, { once: true }); }); }, this.longPressDuration); }, // 长按取消(移动端) handleLongPressEnd() { if (this.longPressTimer) { clearTimeout(this.longPressTimer); this.longPressTimer = null; } }, // 从菜单执行操作 contextMenuAction(action) { if (!this.contextMenuFile) return; switch (action) { case 'preview': // 根据文件类型打开对应的预览 if (this.contextMenuFile.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i)) { this.openImageViewer(this.contextMenuFile); } else if (this.contextMenuFile.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i)) { this.openVideoPlayer(this.contextMenuFile); } else if (this.contextMenuFile.name.match(/\.(mp3|wav|flac|aac|ogg|m4a)$/i)) { this.openAudioPlayer(this.contextMenuFile); } break; case 'download': this.downloadFile(this.contextMenuFile); break; case 'rename': this.openRenameModal(this.contextMenuFile); break; case 'share': this.openShareFileModal(this.contextMenuFile); break; case 'delete': this.confirmDeleteFile(this.contextMenuFile); break; } this.hideContextMenu(); }, // ===== 媒体预览功能 ===== // 获取媒体文件URL getMediaUrl(file) { const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`; // SFTP存储且配置了HTTP下载URL,使用HTTP直接访问 if (file.httpDownloadUrl) { return file.httpDownloadUrl; } // 本地存储或未配置HTTP URL,使用API下载 return `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}&token=${this.token}`; }, // 获取文件缩略图URL getThumbnailUrl(file) { if (!file || file.isDirectory) return null; // 检查是否是图片或视频 const isImage = file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp)$/i); const isVideo = file.name.match(/\.(mp4|avi|mov|wmv|flv|mkv|webm)$/i); if (!isImage && !isVideo) return null; return this.getMediaUrl(file); }, // 打开图片预览 openImageViewer(file) { this.currentMediaUrl = this.getMediaUrl(file); this.currentMediaName = file.name; this.currentMediaType = 'image'; this.showImageViewer = true; }, // 打开视频播放器 openVideoPlayer(file) { this.currentMediaUrl = this.getMediaUrl(file); this.currentMediaName = file.name; this.currentMediaType = 'video'; this.showVideoPlayer = true; }, // 打开音频播放器 openAudioPlayer(file) { this.currentMediaUrl = this.getMediaUrl(file); this.currentMediaName = file.name; this.currentMediaType = 'audio'; this.showAudioPlayer = true; }, // 关闭媒体预览 closeMediaViewer() { this.showImageViewer = false; this.showVideoPlayer = false; this.showAudioPlayer = false; this.currentMediaUrl = ''; this.currentMediaName = ''; this.currentMediaType = ''; }, // 下载当前预览的媒体文件 downloadCurrentMedia() { if (!this.currentMediaUrl) return; // 创建临时a标签触发下载 const link = document.createElement('a'); link.href = this.currentMediaUrl; link.setAttribute('download', this.currentMediaName); document.body.appendChild(link); link.click(); document.body.removeChild(link); }, // 判断文件是否支持预览 isPreviewable(file) { if (!file || file.isDirectory) return false; return file.name.match(/\.(jpg|jpeg|png|gif|bmp|svg|webp|mp4|avi|mov|wmv|flv|mkv|webm|mp3|wav|flac|aac|ogg|m4a)$/i); }, async deleteFile(file) { try { const response = await axios.post( `${this.apiBase}/api/files/delete`, { fileName: file.name, path: this.currentPath, isDirectory: file.isDirectory }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.showToast('success', '成功', '文件已删除'); this.loadFiles(this.currentPath); } } catch (error) { console.error('删除失败:', error); this.showToast('error', '错误', error.response?.data?.message || '删除失败'); } }, downloadUploadTool() { try { this.downloadingTool = true; this.showToast('info', '提示', '正在生成上传工具,下载即将开始...'); // 使用标签下载,通过URL参数传递token,浏览器会显示下载进度 const link = document.createElement('a'); link.href = `${this.apiBase}/api/upload/download-tool?token=${this.token}`; link.setAttribute('download', `玩玩云上传工具_${this.user.username}.zip`); document.body.appendChild(link); link.click(); document.body.removeChild(link); // 延迟重置按钮状态,给下载一些启动时间 setTimeout(() => { this.downloadingTool = false; this.showToast('success', '提示', '下载已开始,请查看浏览器下载进度'); }, 2000); } catch (error) { console.error('下载上传工具失败:', error); this.showToast('error', '错误', '下载失败'); this.downloadingTool = false; } }, // ===== 分享功能 ===== openShareFileModal(file) { this.shareFileForm.fileName = file.name; this.shareFileForm.filePath = this.currentPath === '/' ? file.name : `${this.currentPath}/${file.name}`; this.shareFileForm.password = ''; this.shareFileForm.expiryType = 'never'; this.shareFileForm.customDays = 7; this.shareResult = null; // 清空上次的分享结果 this.showShareFileModal = true; }, async createShareAll() { try { const expiryDays = this.shareAllForm.expiryType === 'never' ? null : this.shareAllForm.expiryType === 'custom' ? this.shareAllForm.customDays : parseInt(this.shareAllForm.expiryType); const response = await axios.post( `${this.apiBase}/api/share/create`, { share_type: 'all', password: this.shareAllForm.password || null, expiry_days: expiryDays }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.shareResult = response.data; this.showToast('success', '成功', '分享链接已创建'); this.loadShares(); } } catch (error) { console.error('创建分享失败:', error); this.showToast('error', '错误', error.response?.data?.message || '创建分享失败'); } }, async createShareFile() { try { const expiryDays = this.shareFileForm.expiryType === 'never' ? null : this.shareFileForm.expiryType === 'custom' ? this.shareFileForm.customDays : parseInt(this.shareFileForm.expiryType); const response = await axios.post( `${this.apiBase}/api/share/create`, { share_type: 'file', file_path: this.shareFileForm.filePath, file_name: this.shareFileForm.fileName, password: this.shareFileForm.password || null, expiry_days: expiryDays }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.shareResult = response.data; this.showToast('success', '成功', '文件分享链接已创建'); this.loadShares(); } } catch (error) { console.error('创建分享失败:', error); this.showToast('error', '错误', error.response?.data?.message || '创建分享失败'); } }, // ===== 文件上传 ===== handleFileSelect(event) { const files = event.target.files; if (files && files.length > 0) { // 支持多文件上传 Array.from(files).forEach(file => { this.uploadFile(file); }); // 清空input,允许重复上传相同文件 event.target.value = ''; } }, handleFileDrop(event) { this.isDragging = false; const file = event.dataTransfer.files[0]; if (file) { this.uploadFile(file); } }, async uploadFile(file) { // 本地存储配额预检查 if (this.storageType === 'local') { const estimatedUsage = this.localUsed + file.size; if (estimatedUsage > this.localQuota) { this.showToast( 'error', '配额不足', `文件大小 ${this.formatBytes(file.size)},剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)},无法上传` ); return; } // 如果使用率将超过90%,给出警告 const willExceed90 = (estimatedUsage / this.localQuota) > 0.9; if (willExceed90) { const confirmed = confirm( `警告:上传此文件后将使用 ${Math.round((estimatedUsage / this.localQuota) * 100)}% 的配额。是否继续?` ); if (!confirmed) return; } } const formData = new FormData(); formData.append('file', file); formData.append('path', this.currentPath); try { // 设置上传文件名和进度 this.uploadingFileName = file.name; this.uploadProgress = 0; this.uploadedBytes = 0; this.totalBytes = 0; const response = await axios.post(`${this.apiBase}/api/upload`, formData, { headers: { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'multipart/form-data' }, onUploadProgress: (progressEvent) => { this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total); this.uploadedBytes = progressEvent.loaded; this.totalBytes = progressEvent.total; } }); if (response.data.success) { // 显示成功提示 this.showToast('success', '上传成功', `文件 ${file.name} 已上传`); // 重置上传进度 this.uploadProgress = 0; this.uploadedBytes = 0; this.totalBytes = 0; this.uploadingFileName = ''; // 自动刷新文件列表 await this.loadFiles(this.currentPath); } } catch (error) { console.error('上传失败:', error); // 重置上传进度 this.uploadProgress = 0; this.uploadedBytes = 0; this.totalBytes = 0; this.uploadingFileName = ''; // 处理文件大小超限错误 if (error.response?.status === 413) { const errorData = error.response.data; // 判断响应是JSON还是HTML(Nginx返回HTML,Backend返回JSON) if (typeof errorData === 'object' && errorData.maxSize && errorData.fileSize) { // Backend返回的JSON响应 const maxSizeMB = Math.round(errorData.maxSize / (1024 * 1024)); const fileSizeMB = Math.round(errorData.fileSize / (1024 * 1024)); this.showToast( 'error', '文件超过上传限制', `文件大小 ${fileSizeMB}MB 超过限制 ${maxSizeMB}MB` ); } else { // Nginx返回的HTML响应,显示通用消息 const fileSizeMB = Math.round(file.size / (1024 * 1024)); this.showToast( 'error', '文件超过上传限制', `文件大小 ${fileSizeMB}MB 超过系统限制,请联系管理员` ); } } else { this.showToast('error', '上传失败', error.response?.data?.message || error.message); } } }, // ===== 分享管理 ===== async loadShares() { try { const response = await axios.get(`${this.apiBase}/api/share/my`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.shares = response.data.shares; } } catch (error) { console.error('加载分享列表失败:', error); alert('加载分享列表失败: ' + (error.response?.data?.message || error.message)); } }, async createShare() { this.shareForm.path = this.currentPath; try { const response = await axios.post(`${this.apiBase}/api/share/create`, this.shareForm, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.shareResult = response.data; this.loadShares(); } } catch (error) { console.error('创建分享失败:', error); alert('创建分享失败: ' + (error.response?.data?.message || error.message)); } }, async deleteShare(id) { if (!confirm('确定要删除这个分享吗?')) return; try { const response = await axios.delete(`${this.apiBase}/api/share/${id}`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { alert('分享已删除'); this.loadShares(); } } catch (error) { console.error('删除分享失败:', error); alert('删除分享失败: ' + (error.response?.data?.message || error.message)); } }, copyShareLink(url) { // 复制分享链接到剪贴板 if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard.writeText(url).then(() => { this.showToast('success', '成功', '分享链接已复制到剪贴板'); }).catch(() => { this.fallbackCopyToClipboard(url); }); } else { this.fallbackCopyToClipboard(url); } }, fallbackCopyToClipboard(text) { // 备用复制方法 const textArea = document.createElement('textarea'); textArea.value = text; textArea.style.position = 'fixed'; textArea.style.left = '-999999px'; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); this.showToast('success', '成功', '分享链接已复制到剪贴板'); } catch (err) { this.showToast('error', '错误', '复制失败,请手动复制'); } document.body.removeChild(textArea); }, // ===== 管理员功能 ===== async loadUsers() { try { const response = await axios.get(`${this.apiBase}/api/admin/users`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.adminUsers = response.data.users; } } catch (error) { console.error('加载用户列表失败:', error); alert('加载用户列表失败: ' + (error.response?.data?.message || error.message)); } }, async banUser(userId, banned) { const action = banned ? '封禁' : '解封'; if (!confirm(`确定要${action}这个用户吗?`)) return; try { const response = await axios.post( `${this.apiBase}/api/admin/users/${userId}/ban`, { banned }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { alert(response.data.message); this.loadUsers(); } } catch (error) { console.error('操作失败:', error); alert('操作失败: ' + (error.response?.data?.message || error.message)); } }, async deleteUser(userId) { if (!confirm('确定要删除这个用户吗?此操作不可恢复!')) return; try { const response = await axios.delete(`${this.apiBase}/api/admin/users/${userId}`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { alert('用户已删除'); this.loadUsers(); } } catch (error) { console.error('删除用户失败:', error); alert('删除用户失败: ' + (error.response?.data?.message || error.message)); } }, // ===== 忘记密码功能 ===== async requestPasswordReset() { if (!this.forgotPasswordForm.username) { this.showToast('error', '错误', '请输入用户名'); return; } if (!this.forgotPasswordForm.new_password || this.forgotPasswordForm.new_password.length < 6) { this.showToast('error', '错误', '新密码至少6个字符'); return; } try { const response = await axios.post( `${this.apiBase}/api/password-reset/request`, this.forgotPasswordForm ); if (response.data.success) { this.showToast('success', '成功', '密码重置请求已提交,请等待管理员审核'); this.showForgotPasswordModal = false; this.forgotPasswordForm = { username: '', new_password: '' }; } } catch (error) { console.error('提交密码重置请求失败:', error); this.showToast('error', '错误', error.response?.data?.message || '提交失败'); } }, // ===== 管理员:密码重置审核 ===== async loadPasswordResetRequests() { try { const response = await axios.get(`${this.apiBase}/api/admin/password-reset/pending`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.passwordResetRequests = response.data.requests; } } catch (error) { console.error('加载密码重置请求失败:', error); this.showToast('error', '错误', '加载密码重置请求失败'); } }, async reviewPasswordReset(requestId, approved) { const action = approved ? '批准' : '拒绝'; if (!confirm(`确定要${action}这个密码重置请求吗?`)) return; try { const response = await axios.post( `${this.apiBase}/api/admin/password-reset/${requestId}/review`, { approved }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.showToast('success', '成功', response.data.message); this.loadPasswordResetRequests(); } } catch (error) { console.error('审核失败:', error); this.showToast('error', '错误', error.response?.data?.message || '审核失败'); } }, // ===== 管理员:文件审查功能 ===== async openFileInspection(user) { this.inspectionUser = user; this.inspectionPath = '/'; this.showFileInspectionModal = true; await this.loadUserFiles('/'); }, async loadUserFiles(path) { this.inspectionLoading = true; this.inspectionPath = path; try { const response = await axios.get( `${this.apiBase}/api/admin/users/${this.inspectionUser.id}/files`, { params: { path }, headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.inspectionFiles = response.data.items; } } catch (error) { console.error('加载用户文件失败:', error); this.showToast('error', '错误', error.response?.data?.message || '加载文件失败'); } finally { this.inspectionLoading = false; } }, handleInspectionFileClick(file) { if (file.isDirectory) { const newPath = this.inspectionPath === '/' ? `/${file.name}` : `${this.inspectionPath}/${file.name}`; this.loadUserFiles(newPath); } }, navigateInspectionToRoot() { this.loadUserFiles('/'); }, navigateInspectionUp() { if (this.inspectionPath === '/') return; const lastSlash = this.inspectionPath.lastIndexOf('/'); const parentPath = lastSlash > 0 ? this.inspectionPath.substring(0, lastSlash) : '/'; this.loadUserFiles(parentPath); }, // ===== 存储管理 ===== // 加载用户个人资料(包含存储信息) async loadUserProfile() { try { const response = await axios.get( `${this.apiBase}/api/user/profile`, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success && response.data.user) { const user = response.data.user; this.localQuota = user.local_storage_quota || 0; this.localUsed = user.local_storage_used || 0; this.storagePermission = user.storage_permission || 'sftp_only'; this.storageType = user.current_storage_type || 'sftp'; } } catch (error) { console.error('加载用户资料失败:', error); } }, // 用户切换存储方式 async switchStorage(type) { if (!confirm(`确定要切换到${type === 'local' ? '本地存储' : 'SFTP存储'}吗?`)) { return; } try { const response = await axios.post( `${this.apiBase}/api/user/switch-storage`, { storage_type: type }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.storageType = type; this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'SFTP存储'}`); // 重新加载文件列表 if (this.currentView === 'files') { this.loadFiles(this.currentPath); } } } catch (error) { console.error('切换存储失败:', error); this.showToast('error', '错误', error.response?.data?.message || '切换存储失败'); } }, // 管理员:打开编辑用户存储权限模态框 openEditStorageModal(user) { this.editStorageForm.userId = user.id; this.editStorageForm.username = user.username; this.editStorageForm.storage_permission = user.storage_permission || 'sftp_only'; // 智能识别配额单位 const quotaBytes = user.local_storage_quota || 1073741824; const quotaMB = quotaBytes / 1024 / 1024; const quotaGB = quotaMB / 1024; // 如果配额能被1024整除且大于等于1GB,使用GB单位,否则使用MB if (quotaMB >= 1024 && quotaMB % 1024 === 0) { this.editStorageForm.local_storage_quota_value = quotaGB; this.editStorageForm.quota_unit = 'GB'; } else { this.editStorageForm.local_storage_quota_value = Math.round(quotaMB); this.editStorageForm.quota_unit = 'MB'; } this.showEditStorageModal = true; }, // 管理员:更新用户存储权限 async updateUserStorage() { try { // 根据单位计算字节数 let quotaBytes; if (this.editStorageForm.quota_unit === 'GB') { quotaBytes = this.editStorageForm.local_storage_quota_value * 1024 * 1024 * 1024; } else { quotaBytes = this.editStorageForm.local_storage_quota_value * 1024 * 1024; } const response = await axios.post( `${this.apiBase}/api/admin/users/${this.editStorageForm.userId}/storage-permission`, { storage_permission: this.editStorageForm.storage_permission, local_storage_quota: quotaBytes }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.showToast('success', '成功', '存储权限已更新'); this.showEditStorageModal = false; this.loadUsers(); } } catch (error) { console.error('更新存储权限失败:', error); this.showToast('error', '错误', error.response?.data?.message || '更新失败'); } }, // ===== 工具函数 ===== formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; }, formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); return `${year}-${month}-${day} ${hours}:${minutes}`; }, // ===== Toast通知 ===== showToast(type, title, message) { const toast = { id: ++this.toastIdCounter, type, title, message, icon: type === 'error' ? 'fas fa-circle-exclamation' : type === 'success' ? 'fas fa-circle-check' : 'fas fa-circle-info', hiding: false }; this.toasts.push(toast); // 4.5秒后开始淡出动画 setTimeout(() => { const index = this.toasts.findIndex(t => t.id === toast.id); if (index !== -1) { this.toasts[index].hiding = true; // 0.5秒后移除(动画时长) setTimeout(() => { const removeIndex = this.toasts.findIndex(t => t.id === toast.id); if (removeIndex !== -1) { this.toasts.splice(removeIndex, 1); } }, 500); } }, 4500); }, // ===== 系统设置管理 ===== async loadSystemSettings() { try { const response = await axios.get(`${this.apiBase}/api/admin/settings`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { const settings = response.data.settings; this.systemSettings.maxUploadSizeMB = Math.round(settings.max_upload_size / (1024 * 1024)); } } catch (error) { console.error('加载系统设置失败:', error); this.showToast('error', '错误', '加载系统设置失败'); } }, async loadServerStorageStats() { try { const response = await axios.get(`${this.apiBase}/api/admin/storage-stats`, { headers: { Authorization: `Bearer ${this.token}` } }); if (response.data.success) { this.serverStorageStats = response.data.stats; } } catch (error) { console.error('加载服务器存储统计失败:', error); this.showToast('error', '错误', '加载服务器存储统计失败'); } }, async updateSystemSettings() { try { const maxUploadSize = parseInt(this.systemSettings.maxUploadSizeMB) * 1024 * 1024; const response = await axios.post( `${this.apiBase}/api/admin/settings`, { max_upload_size: maxUploadSize }, { headers: { Authorization: `Bearer ${this.token}` } } ); if (response.data.success) { this.showToast('success', '成功', '系统设置已更新'); } } catch (error) { console.error('更新系统设置失败:', error); this.showToast('error', '错误', '更新系统设置失败'); } } }, mounted() { // 阻止全局拖拽默认行为(防止拖到区域外打开新页面) window.addEventListener("dragover", (e) => { e.preventDefault(); }); window.addEventListener("drop", (e) => { e.preventDefault(); }); // 添加全局 dragend 监听(拖拽结束时总是隐藏覆盖层) window.addEventListener("dragend", () => { this.isDragging = false; }); // 添加 ESC 键监听(按 ESC 关闭拖拽覆盖层) window.addEventListener("keydown", (e) => { if (e.key === "Escape" && this.isDragging) { this.isDragging = false; } }); // 检查URL参数 this.checkUrlParams(); // 检查登录状态 this.checkLoginStatus(); }, watch: { currentView(newView) { if (newView === 'shares') { this.loadShares(); } else if (newView === 'admin' && this.user?.is_admin) { this.loadUsers(); this.loadSystemSettings(); this.loadPasswordResetRequests(); this.loadServerStorageStats(); } else if (newView === 'settings' && this.user && !this.user.is_admin) { // 普通用户进入设置页面时加载SFTP配置 this.loadFtpConfig(); } } } }).mount('#app');