Files
vue-driven-cloud-storage/frontend/app.js
WanWanYun a791569b17 修复: 优化存储切换逻辑,解决SFTP配置和权限切换问题
1. 登录时智能修正存储类型:
   - 当用户为SFTP模式但未配置SFTP时,如果有本地存储权限,自动切换到本地存储
   - 避免管理员更改用户权限后,用户登录时出现"加载文件失败"错误

2. SFTP配置后自动切换存储模式:
   - 用户成功配置SFTP后,自动切换到SFTP存储模式
   - 无需用户手动再次切换,提升用户体验

3. 改进存储切换提示信息:
   - 当用户尝试切换到未配置的SFTP时,显示更友好的提示
   - 明确告知用户配置完成后将自动切换到SFTP存储

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-12 22:50:27 +08:00

1935 lines
61 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
},
// 定期检查用户配置更新的定时器
profileCheckInterval: null,
// 上传工具管理
uploadToolStatus: null, // 上传工具状态 { exists, fileInfo: { size, sizeMB, modifiedAt } }
checkingUploadTool: false, // 是否正在检测上传工具
uploadingTool: false // 是否正在上传工具
};
},
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, 'SFTP配置:', this.user.has_ftp_config);
// 智能存储类型修正如果当前是SFTP但未配置且用户有本地存储权限自动切换到本地
if (this.storageType === 'sftp' && !this.user.has_ftp_config) {
if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
console.log('[登录] SFTP未配置但用户有本地存储权限自动切换到本地存储');
this.storageType = 'local';
// 异步更新到后端(不等待,避免阻塞登录流程)
axios.post(
`${this.apiBase}/api/user/switch-storage`,
{ storage_type: 'local' },
{ headers: { Authorization: `Bearer ${this.token}` } }
).catch(err => console.error('[登录] 自动切换存储类型失败:', err));
}
}
// 启动定期检查用户配置
this.startProfileSync();
// 管理员直接跳转到管理后台
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;
// 如果用户有 user_choice 权限,自动切换到 SFTP 存储
if (this.storagePermission === 'user_choice' || this.storagePermission === 'sftp_only') {
try {
const switchResponse = await axios.post(
`${this.apiBase}/api/user/switch-storage`,
{ storage_type: 'sftp' },
{ headers: { Authorization: `Bearer ${this.token}` } }
);
if (switchResponse.data.success) {
this.storageType = 'sftp';
console.log('[SFTP配置] 已自动切换到SFTP存储模式');
}
} catch (err) {
console.error('[SFTP配置] 自动切换存储模式失败:', err);
}
}
// 刷新到文件页面
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');
// 停止定期检查
this.stopProfileSync();
},
// 检查本地存储的登录状态
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();
// 启动定期检查用户配置
this.startProfileSync();
// 管理员跳转到管理后台
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}`;
// 使用<a>标签下载通过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', '提示', '正在生成上传工具,下载即将开始...');
// 使用<a>标签下载通过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还是HTMLNginx返回HTMLBackend返回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;
// 检测存储配置是否被管理员更改
const oldStorageType = this.storageType;
const oldStoragePermission = this.storagePermission;
const newStorageType = user.current_storage_type || 'sftp';
const newStoragePermission = user.storage_permission || 'sftp_only';
// 更新本地数据
this.localQuota = user.local_storage_quota || 0;
this.localUsed = user.local_storage_used || 0;
this.storagePermission = newStoragePermission;
this.storageType = newStorageType;
// 如果存储类型被管理员更改,通知用户并重新加载文件
if (oldStorageType !== newStorageType || oldStoragePermission !== newStoragePermission) {
console.log('[存储配置更新] 旧类型:', oldStorageType, '新类型:', newStorageType);
console.log('[存储配置更新] 旧权限:', oldStoragePermission, '新权限:', newStoragePermission);
this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'SFTP存储'}`);
// 如果当前在文件页面,重新加载文件列表
if (this.currentView === 'files') {
await this.loadFiles(this.currentPath);
}
}
}
} catch (error) {
console.error('加载用户资料失败:', error);
}
},
// 启动定期检查用户配置
startProfileSync() {
// 清除已有的定时器
if (this.profileCheckInterval) {
clearInterval(this.profileCheckInterval);
}
// 每30秒检查一次用户配置是否有更新
this.profileCheckInterval = setInterval(() => {
if (this.isLoggedIn && this.token) {
this.loadUserProfile();
}
}, 30000); // 30秒
console.log('[配置同步] 已启动定期检查30秒间隔');
},
// 停止定期检查
stopProfileSync() {
if (this.profileCheckInterval) {
clearInterval(this.profileCheckInterval);
this.profileCheckInterval = null;
console.log('[配置同步] 已停止定期检查');
}
},
// 用户切换存储方式
async switchStorage(type) {
// 检查是否尝试切换到SFTP但未配置
if (type === 'sftp' && !this.user.has_ftp_config) {
const goToSettings = confirm('您还未配置SFTP服务器。\n\n是否现在前往设置页面进行配置配置完成后将自动切换到SFTP存储。');
if (goToSettings) {
this.currentView = 'settings';
}
return;
}
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 || '切换存储失败');
}
},
// 切换视图并自动刷新数据
switchView(view) {
// 如果已经在当前视图,不重复刷新
if (this.currentView === view) {
return;
}
this.currentView = view;
// 根据视图类型自动加载对应数据
switch (view) {
case 'files':
// 切换到文件视图时,重新加载文件列表
this.loadFiles(this.currentPath);
break;
case 'shares':
// 切换到分享视图时,重新加载分享列表
this.loadShares();
break;
case 'admin':
// 切换到管理后台时,重新加载用户列表
if (this.user && this.user.is_admin) {
this.loadUsers();
this.loadServerStorageStats();
}
break;
case 'settings':
// 设置页面不需要额外加载数据
break;
}
},
// 管理员:打开编辑用户存储权限模态框
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', '错误', '更新系统设置失败');
}
}
,
// ===== 上传工具管理 =====
// 检测上传工具是否存在
async checkUploadTool() {
this.checkingUploadTool = true;
try {
const response = await axios.get(
`${this.apiBase}/api/admin/check-upload-tool`,
{ headers: { Authorization: `Bearer ${this.token}` } }
);
if (response.data.success) {
this.uploadToolStatus = response.data;
if (response.data.exists) {
this.showToast('success', '检测完成', '上传工具文件存在');
} else {
this.showToast('warning', '提示', '上传工具文件不存在,请上传');
}
}
} catch (error) {
console.error('检测上传工具失败:', error);
this.showToast('error', '错误', '检测失败: ' + (error.response?.data?.message || error.message));
} finally {
this.checkingUploadTool = false;
}
},
// 处理上传工具文件
async handleUploadToolFile(event) {
const file = event.target.files[0];
if (!file) return;
// 验证文件类型
if (!file.name.toLowerCase().endsWith('.exe')) {
this.showToast('error', '错误', '只能上传 .exe 文件');
event.target.value = '';
return;
}
// 验证文件大小至少20MB
const minSizeMB = 20;
const fileSizeMB = file.size / (1024 * 1024);
if (fileSizeMB < minSizeMB) {
this.showToast('error', '错误', `文件大小过小(${fileSizeMB.toFixed(2)}MB上传工具通常大于${minSizeMB}MB`);
event.target.value = '';
return;
}
// 确认上传
if (!confirm(`确定要上传 ${file.name} (${fileSizeMB.toFixed(2)}MB) 吗?`)) {
event.target.value = '';
return;
}
this.uploadingTool = true;
try {
const formData = new FormData();
formData.append('file', file);
const response = await axios.post(
`${this.apiBase}/api/admin/upload-tool`,
formData,
{
headers: {
Authorization: `Bearer ${this.token}`,
'Content-Type': 'multipart/form-data'
}
}
);
if (response.data.success) {
this.showToast('success', '成功', '上传工具已上传');
// 重新检测
await this.checkUploadTool();
}
} catch (error) {
console.error('上传工具失败:', error);
this.showToast('error', '错误', error.response?.data?.message || '上传失败');
} finally {
this.uploadingTool = false;
event.target.value = ''; // 清空input允许重复上传
}
} },
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');