feat: v3.1.0 OSS直连优化与代码质量提升

- 🚀 OSS 直连上传下载(用户直连OSS,不经过后端)
-  新增 Presigned URL 签名接口
-  支持自定义 OSS endpoint 配置
- 🐛 修复 buildS3Config 不支持自定义 endpoint 的问题
- 🐛 清理残留的 basic-ftp 依赖
- ♻️ 更新 package.json 项目描述和版本号
- 📝 完善 README.md 更新日志和 CORS 配置说明
- 🔒 安全性增强:签名 URL 15分钟/1小时有效期

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Opus
2026-01-18 17:14:16 +08:00
parent 71c2c0465e
commit 0b0e5b9d7c
18 changed files with 3864 additions and 1644 deletions

View File

@@ -48,15 +48,17 @@ createApp({
showCaptcha: false,
captchaUrl: '',
// SFTP配置表单
ftpConfigForm: {
ftp_host: '',
ftp_port: 22,
ftp_user: '',
ftp_password: '',
http_download_base_url: ''
// OSS配置表单
ossConfigForm: {
oss_provider: 'aliyun',
oss_region: '',
oss_access_key_id: '',
oss_access_key_secret: '',
oss_bucket: '',
oss_endpoint: ''
},
showFtpConfigModal: false,
showOssConfigModal: false,
ossConfigSaving: false, // OSS 配置保存中状态
// 修改密码表单
changePasswordForm: {
@@ -211,8 +213,8 @@ createApp({
verifyMessage: '',
// 存储相关
storageType: 'sftp', // 当前使用的存储类型
storagePermission: 'sftp_only', // 存储权限
storageType: 'oss', // 当前使用的存储类型
storagePermission: 'oss_only', // 存储权限
localQuota: 0, // 本地存储配额(字节)
localUsed: 0, // 本地存储已使用(字节)
@@ -222,7 +224,7 @@ createApp({
contextMenuX: 0,
contextMenuY: 0,
contextMenuFile: null,
// 长按检测
longPressTimer: null,
longPressStartX: 0,
@@ -242,7 +244,7 @@ createApp({
editStorageForm: {
userId: null,
username: '',
storage_permission: 'sftp_only',
storage_permission: 'oss_only',
local_storage_quota_value: 1, // 配额数值
quota_unit: 'GB' // 配额单位MB 或 GB
},
@@ -271,14 +273,14 @@ createApp({
suppressStorageToast: false,
profileInitialized: false,
// SFTP配置引导弹窗
showSftpGuideModal: false,
showSftpConfigModal: false,
// OSS配置引导弹窗
showOssGuideModal: false,
showOssConfigModal: false,
// SFTP空间使用统计
sftpUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
sftpUsageLoading: false,
sftpUsageError: null,
// OSS空间使用统计
ossUsage: null, // { totalSize, totalSizeFormatted, fileCount, dirCount }
ossUsageLoading: false,
ossUsageError: null,
// 主题设置
currentTheme: 'dark', // 当前生效的主题: 'dark' 或 'light'
@@ -309,7 +311,7 @@ createApp({
// 存储类型显示文本
storageTypeText() {
return this.storageType === 'local' ? '本地存储' : 'SFTP存储';
return this.storageType === 'local' ? '本地存储' : 'OSS存储';
},
// 分享筛选+排序后的列表
@@ -613,17 +615,17 @@ handleDragLeave(e) {
this.startTokenRefresh(expiresIn);
// 直接从登录响应中获取存储信息
this.storagePermission = this.user.storage_permission || 'sftp_only';
this.storageType = this.user.current_storage_type || 'sftp';
this.storagePermission = this.user.storage_permission || 'oss_only';
this.storageType = this.user.current_storage_type || 'oss';
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);
console.log('[登录] 存储权限:', this.storagePermission, '存储类型:', this.storageType, 'OSS配置:', this.user.has_oss_config);
// 智能存储类型修正:如果当前是SFTP但未配置,且用户有本地存储权限,自动切换到本地
if (this.storageType === 'sftp' && !this.user.has_ftp_config) {
// 智能存储类型修正:如果当前是OSS但未配置,且用户有本地存储权限,自动切换到本地
if (this.storageType === 'oss' && !this.user.has_oss_config) {
if (this.storagePermission === 'local_only' || this.storagePermission === 'user_choice') {
console.log('[登录] SFTP未配置但用户有本地存储权限,自动切换到本地存储');
console.log('[登录] OSS未配置但用户有本地存储权限,自动切换到本地存储');
this.storageType = 'local';
// 异步更新到后端(不等待,避免阻塞登录流程)
axios.post(`${this.apiBase}/api/user/switch-storage`, { storage_type: 'local' })
@@ -646,15 +648,15 @@ handleDragLeave(e) {
this.currentView = 'files';
this.loadFiles('/');
}
// 如果仅SFTP模式,需要检查是否配置了SFTP
else if (this.storagePermission === 'sftp_only') {
if (this.user.has_ftp_config) {
// 如果仅OSS模式,需要检查是否配置了OSS
else if (this.storagePermission === 'oss_only') {
if (this.user.has_oss_config) {
this.currentView = 'files';
this.loadFiles('/');
} else {
this.currentView = 'settings';
alert('欢迎!请先配置您的SFTP服务');
this.openSftpConfigModal();
alert('欢迎!请先配置您的OSS服务');
this.openOssConfigModal();
}
} else {
// 默认行为:跳转到文件页面
@@ -808,44 +810,56 @@ handleDragLeave(e) {
}
},
async updateFtpConfig() {
async updateOssConfig() {
// 防止重复提交
if (this.ossConfigSaving) {
return;
}
this.ossConfigSaving = true;
try {
const response = await axios.post(
`${this.apiBase}/api/user/update-ftp`,
this.ftpConfigForm,
`${this.apiBase}/api/user/update-oss`,
this.ossConfigForm,
);
if (response.data.success) {
alert('SFTP配置已保存');
// 更新用户信息
this.user.has_ftp_config = 1;
// 如果用户有 user_choice 权限,自动切换到 SFTP 存储
if (this.storagePermission === 'user_choice' || this.storagePermission === 'sftp_only') {
this.user.has_oss_config = 1;
// 如果用户有 user_choice 权限,自动切换到 OSS 存储
if (this.storagePermission === 'user_choice' || this.storagePermission === 'oss_only') {
try {
const switchResponse = await axios.post(
`${this.apiBase}/api/user/switch-storage`,
{ storage_type: 'sftp' },
);
{ storage_type: 'oss' },
);
if (switchResponse.data.success) {
this.storageType = 'sftp';
console.log('[SFTP配置] 已自动切换到SFTP存储模式');
this.storageType = 'oss';
console.log('[OSS配置] 已自动切换到OSS存储模式');
}
} catch (err) {
console.error('[SFTP配置] 自动切换存储模式失败:', err);
console.error('[OSS配置] 自动切换存储模式失败:', err);
}
}
// 关闭配置弹窗
this.showSftpConfigModal = false;
this.showOssConfigModal = false;
// 刷新到文件页面
this.currentView = 'files';
this.loadFiles('/');
// 显示成功提示
this.showToast('success', '配置成功', 'OSS存储配置已保存');
}
} catch (error) {
alert('配置失败: ' + (error.response?.data?.message || error.message));
console.error('OSS配置保存失败:', error);
this.showToast('error', '配置失败', error.response?.data?.message || error.message || '请检查配置信息后重试');
} finally {
this.ossConfigSaving = false;
}
},
@@ -905,7 +919,7 @@ handleDragLeave(e) {
}
},
async loadFtpConfig() {
async loadOssConfig() {
try {
const response = await axios.get(
`${this.apiBase}/api/user/profile`,
@@ -913,105 +927,20 @@ handleDragLeave(e) {
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 || '';
// 填充OSS配置表单(密不回显)
this.ossConfigForm.oss_provider = user.oss_provider || 'aliyun';
this.ossConfigForm.oss_region = user.oss_region || '';
this.ossConfigForm.oss_access_key_id = user.oss_access_key_id || '';
this.ossConfigForm.oss_access_key_secret = ''; // 密不回显
this.ossConfigForm.oss_bucket = user.oss_bucket || '';
this.ossConfigForm.oss_endpoint = user.oss_endpoint || '';
}
} catch (error) {
console.error('加载SFTP配置失败:', error);
console.error('加载OSS配置失败:', 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;
},
// 上传工具配置引导已移除OSS 不需要配置文件导入)
async updateUsername() {
if (!this.usernameForm.newUsername || this.usernameForm.newUsername.length < 3) {
@@ -1107,8 +1036,8 @@ handleDragLeave(e) {
localStorage.setItem('user', JSON.stringify(this.user));
// 从最新的用户信息初始化存储相关字段
this.storagePermission = this.user.storage_permission || 'sftp_only';
this.storageType = this.user.current_storage_type || 'sftp';
this.storagePermission = this.user.storage_permission || 'oss_only';
this.storageType = this.user.current_storage_type || 'oss';
this.localQuota = this.user.local_storage_quota || 0;
this.localUsed = this.user.local_storage_used || 0;
@@ -1129,7 +1058,7 @@ handleDragLeave(e) {
targetView = savedView;
} else if (this.user.is_admin) {
targetView = 'admin';
} else if (this.storagePermission === 'sftp_only' && !this.user.has_ftp_config) {
} else if (this.storagePermission === 'oss_only' && !this.user.has_oss_config) {
targetView = 'settings';
} else {
targetView = 'files';
@@ -1311,17 +1240,42 @@ handleDragLeave(e) {
downloadFile(file) {
console.log("[DEBUG] 下载文件:", file);
// SFTP存储且有HTTP直链新窗口打开直接下载避免Mixed Content问题
if (file.httpDownloadUrl) {
window.open(file.httpDownloadUrl, '_blank');
return;
}
// 构建文件路径
const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;
// 本地存储,使用隐藏链接触发下载
const url = `${this.apiBase}/api/files/download?path=${encodeURIComponent(this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`)}`;
// OSS 模式:使用签名 URL 直连下载(不经过后端)
if (this.storageType === 'oss' && this.user?.has_oss_config) {
this.downloadFromOSS(filePath);
} else {
// 本地存储模式:通过后端下载
this.downloadFromLocal(filePath);
}
},
// OSS 直连下载
async downloadFromOSS(filePath) {
try {
// 获取签名 URL
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
params: { path: filePath }
});
if (data.success) {
// 直连 OSS 下载
window.open(data.downloadUrl, '_blank');
}
} catch (error) {
console.error('获取下载链接失败:', error);
this.showToast('error', '错误', '获取下载链接失败');
}
},
// 本地存储下载
downloadFromLocal(filePath) {
const url = `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', file.name);
link.setAttribute('download', '');
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
@@ -1385,6 +1339,7 @@ handleDragLeave(e) {
this.showCreateFolderModal = false;
this.createFolderForm.folderName = '';
await this.loadFiles(this.currentPath); // 刷新文件列表
await this.refreshStorageUsage(); // 刷新空间统计OSS会增加空对象
}
} catch (error) {
console.error('[创建文件夹失败]', error);
@@ -1540,18 +1495,26 @@ handleDragLeave(e) {
// ===== 媒体预览功能 =====
// 获取媒体文件URL
getMediaUrl(file) {
// 获取媒体文件URLOSS直连或后端代理
async getMediaUrl(file) {
const filePath = this.currentPath === '/'
? `/${file.name}`
: `${this.currentPath}/${file.name}`;
// SFTP存储且配置了HTTP下载URL使用HTTP直接访问否则使用API下载
if (file.httpDownloadUrl) {
return file.httpDownloadUrl;
// OSS 模式:返回签名 URL用于媒体预览
if (this.storageType === 'oss' && this.user?.has_oss_config) {
try {
const { data } = await axios.get(`${this.apiBase}/api/files/download-url`, {
params: { path: filePath }
});
return data.success ? data.downloadUrl : null;
} catch (error) {
console.error('获取媒体URL失败:', error);
return null;
}
}
// 本地存储或未配置HTTP URL使用API下载同域 Cookie 验证)
// 本地存储模式:通过后端 API
return `${this.apiBase}/api/files/download?path=${encodeURIComponent(filePath)}`;
},
@@ -1634,7 +1597,9 @@ handleDragLeave(e) {
if (response.data.success) {
this.showToast('success', '成功', '文件已删除');
this.loadFiles(this.currentPath);
// 刷新文件列表和空间统计
await this.loadFiles(this.currentPath);
await this.refreshStorageUsage();
}
} catch (error) {
console.error('删除失败:', error);
@@ -1764,7 +1729,7 @@ handleDragLeave(e) {
},
async uploadFile(file) {
// 文件大小限制预检查(在上传前检查,避免用户等待上传完才发现超限)
// 文件大小限制预检查
if (file.size > this.maxUploadSize) {
const fileSizeMB = Math.round(file.size / (1024 * 1024));
const maxSizeMB = Math.round(this.maxUploadSize / (1024 * 1024));
@@ -1776,99 +1741,129 @@ handleDragLeave(e) {
return;
}
// 本地存储配额预检查
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);
// 设置上传状态
this.uploadingFileName = file.name;
this.uploadProgress = 0;
this.uploadedBytes = 0;
this.totalBytes = file.size;
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: {
'Content-Type': 'multipart/form-data'
},
timeout: 30 * 60 * 1000, // 30分钟超时支持大文件上传
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);
if (this.storageType === 'oss' && this.user?.has_oss_config) {
// ===== OSS 直连上传(不经过后端) =====
await this.uploadToOSSDirect(file);
} else {
// ===== 本地存储上传(经过后端) =====
await this.uploadToLocal(file);
}
} catch (error) {
console.error('上传失败:', error);
// 重置上传进度
this.uploadProgress = 0;
this.uploadedBytes = 0;
this.totalBytes = 0;
this.uploadedBytes = 0;
this.totalBytes = 0;
this.uploadingFileName = '';
// 处理文件大小超限错误
if (error.response?.status === 413) {
const errorData = error.response.data;
this.showToast('error', '上传失败', error.message || '上传失败,请重试');
}
},
// 判断响应是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 超过系统限制,请联系管理员`
);
// OSS 直连上传
async uploadToOSSDirect(file) {
try {
// 1. 获取签名 URL
const { data: signData } = await axios.get(`${this.apiBase}/api/files/upload-signature`, {
params: {
filename: file.name,
contentType: file.type || 'application/octet-stream'
}
} else {
this.showToast('error', '上传失败', error.response?.data?.message || error.message);
});
if (!signData.success) {
throw new Error(signData.message || '获取上传签名失败');
}
// 2. 直连 OSS 上传(不经过后端!)
await axios.put(signData.uploadUrl, file, {
headers: {
'Content-Type': file.type || 'application/octet-stream'
},
onUploadProgress: (progressEvent) => {
this.uploadProgress = Math.round((progressEvent.loaded * 100) / progressEvent.total);
this.uploadedBytes = progressEvent.loaded;
this.totalBytes = progressEvent.total;
},
timeout: 30 * 60 * 1000 // 30分钟超时
});
// 3. 通知后端上传完成
await axios.post(`${this.apiBase}/api/files/upload-complete`, {
objectKey: signData.objectKey,
size: file.size,
path: this.currentPath
});
// 4. 显示成功提示
this.showToast('success', '上传成功', `文件 ${file.name} 已上传到 OSS`);
// 5. 重置上传进度
this.uploadProgress = 0;
this.uploadedBytes = 0;
this.totalBytes = 0;
this.uploadingFileName = '';
// 6. 刷新文件列表和空间统计
await this.loadFiles(this.currentPath);
await this.refreshStorageUsage();
} catch (error) {
// 处理 CORS 错误
if (error.message?.includes('CORS') || error.message?.includes('Cross-Origin')) {
throw new Error('OSS 跨域配置错误,请联系管理员检查 Bucket CORS 设置');
}
throw error;
}
},
// 本地存储上传(经过后端)
async uploadToLocal(file) {
// 本地存储配额预检查
const estimatedUsage = this.localUsed + file.size;
if (estimatedUsage > this.localQuota) {
this.showToast(
'error',
'配额不足',
`文件大小 ${this.formatBytes(file.size)},剩余配额 ${this.formatBytes(this.localQuota - this.localUsed)}`
);
this.uploadProgress = 0;
this.uploadedBytes = 0;
this.totalBytes = 0;
this.uploadingFileName = '';
return;
}
const formData = new FormData();
formData.append('file', file);
formData.append('path', this.currentPath);
const response = await axios.post(`${this.apiBase}/api/upload`, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
timeout: 30 * 60 * 1000,
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);
await this.refreshStorageUsage();
}
},
@@ -2266,14 +2261,14 @@ handleDragLeave(e) {
if (response.data.success && response.data.user) {
const user = response.data.user;
// 同步用户信息(含 has_ftp_config
// 同步用户信息(含 has_oss_config
this.user = { ...(this.user || {}), ...user };
// 检测存储配置是否被管理员更改
const oldStorageType = this.storageType;
const oldStoragePermission = this.storagePermission;
const newStorageType = user.current_storage_type || 'sftp';
const newStoragePermission = user.storage_permission || 'sftp_only';
const newStorageType = user.current_storage_type || 'oss';
const newStoragePermission = user.storage_permission || 'oss_only';
// 更新本地数据
this.localQuota = user.local_storage_quota || 0;
@@ -2293,7 +2288,7 @@ handleDragLeave(e) {
console.log('[存储配置更新] 旧权限:', oldStoragePermission, '新权限:', newStoragePermission);
if (!this.suppressStorageToast) {
this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'SFTP存储'}`);
this.showToast('info', '存储配置已更新', `管理员已将您的存储方式更改为${newStorageType === 'local' ? '本地存储' : 'OSS存储'}`);
} else {
this.suppressStorageToast = false;
}
@@ -2309,30 +2304,41 @@ handleDragLeave(e) {
}
},
// 加载SFTP空间使用统计
async loadSftpUsage() {
// 仅在用户已配置SFTP时才加载
if (!this.user?.has_ftp_config) {
this.sftpUsage = null;
// 加载OSS空间使用统计
async loadOssUsage() {
// 仅在用户已配置OSS时才加载
if (!this.user?.has_oss_config) {
this.ossUsage = null;
return;
}
this.sftpUsageLoading = true;
this.sftpUsageError = null;
this.ossUsageLoading = true;
this.ossUsageError = null;
try {
const response = await axios.get(
`${this.apiBase}/api/user/sftp-usage`,
`${this.apiBase}/api/user/oss-usage`,
);
if (response.data.success) {
this.sftpUsage = response.data.usage;
this.ossUsage = response.data.usage;
}
} catch (error) {
console.error('获取SFTP空间使用情况失败:', error);
this.sftpUsageError = error.response?.data?.message || '获取失败';
console.error('获取OSS空间使用情况失败:', error);
this.ossUsageError = error.response?.data?.message || '获取失败';
} finally {
this.sftpUsageLoading = false;
this.ossUsageLoading = false;
}
},
// 刷新存储空间使用统计(根据当前存储类型)
async refreshStorageUsage() {
if (this.storageType === 'oss' && this.user?.has_oss_config) {
// 刷新 OSS 空间统计
await this.loadOssUsage();
} else if (this.storageType === 'local') {
// 刷新本地存储统计(通过重新获取用户信息)
await this.loadUserProfile();
}
},
@@ -2368,9 +2374,9 @@ handleDragLeave(e) {
return;
}
// 切到SFTP但还未配置,引导弹窗
if (type === 'sftp' && (!this.user?.has_ftp_config)) {
this.showSftpGuideModal = true;
// 切到OSS但还未配置,引导弹窗
if (type === 'oss' && (!this.user?.has_oss_config)) {
this.showOssGuideModal = true;
return;
}
@@ -2387,7 +2393,7 @@ handleDragLeave(e) {
this.storageType = type;
// 用户主动切换后,下一次配置同步不提示管理员修改
this.suppressStorageToast = true;
this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'SFTP存储'}`);
this.showToast('success', '成功', `已切换到${type === 'local' ? '本地存储' : 'OSS存储'}`);
// 重新加载文件列表
if (this.currentView === 'files') {
@@ -2403,33 +2409,33 @@ handleDragLeave(e) {
}
},
ensureSftpConfigSection() {
this.openSftpConfigModal();
ensureOssConfigSection() {
this.openOssConfigModal();
},
openSftpGuideModal() {
this.showSftpGuideModal = true;
openOssGuideModal() {
this.showOssGuideModal = true;
},
closeSftpGuideModal() {
this.showSftpGuideModal = false;
closeOssGuideModal() {
this.showOssGuideModal = false;
},
proceedSftpGuide() {
this.showSftpGuideModal = false;
this.ensureSftpConfigSection();
proceedOssGuide() {
this.showOssGuideModal = false;
this.ensureOssConfigSection();
},
openSftpConfigModal() {
this.showSftpGuideModal = false;
this.showSftpConfigModal = true;
openOssConfigModal() {
this.showOssGuideModal = false;
this.showOssConfigModal = true;
if (this.user && !this.user.is_admin) {
this.loadFtpConfig();
this.loadOssConfig();
}
},
closeSftpConfigModal() {
this.showSftpConfigModal = false;
closeOssConfigModal() {
this.showOssConfigModal = false;
},
// 检查视图权限
@@ -2487,7 +2493,7 @@ handleDragLeave(e) {
openEditStorageModal(user) {
this.editStorageForm.userId = user.id;
this.editStorageForm.username = user.username;
this.editStorageForm.storage_permission = user.storage_permission || 'sftp_only';
this.editStorageForm.storage_permission = user.storage_permission || 'oss_only';
// 智能识别配额单位
const quotaBytes = user.local_storage_quota || 1073741824;
@@ -3082,8 +3088,8 @@ handleDragLeave(e) {
this.loadSystemSettings();
this.loadServerStorageStats();
} else if (newView === 'settings' && this.user && !this.user.is_admin) {
// 普通用户进入设置页面时加载SFTP配置
this.loadFtpConfig();
// 普通用户进入设置页面时加载OSS配置
this.loadOssConfig();
}
// 记住最后停留的视图(需合法且已登录)