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}`;
// 使用标签下载,通过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;
// 检测存储配置是否被管理员更改
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.switchView('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');