feat: add user download traffic reports and restore OSS direct downloads

This commit is contained in:
2026-02-17 17:36:26 +08:00
parent 7687397954
commit 3a22b88f23
4 changed files with 600 additions and 8 deletions

View File

@@ -311,6 +311,17 @@ createApp({
ossUsageLoading: false,
ossUsageError: null,
// 下载流量报表
downloadTrafficReport: {
days: 30,
loading: false,
error: null,
quota: null,
daily: [],
summary: null,
lastUpdatedAt: null
},
// 主题设置
currentTheme: 'dark', // 当前生效的主题: 'dark' 或 'light'
globalTheme: 'dark', // 全局默认主题(管理员设置)
@@ -374,6 +385,80 @@ createApp({
return Math.min(100, Math.round((this.ossUsedBytes / this.ossQuotaBytes) * 100));
},
downloadTrafficQuotaBytes() {
const reportQuota = Number(this.downloadTrafficReport?.quota?.quota);
if (Number.isFinite(reportQuota) && reportQuota >= 0) {
return reportQuota;
}
const userQuota = Number(this.user?.download_traffic_quota || 0);
return Number.isFinite(userQuota) && userQuota > 0 ? Math.floor(userQuota) : 0;
},
downloadTrafficUsedBytes() {
const reportUsed = Number(this.downloadTrafficReport?.quota?.used);
if (Number.isFinite(reportUsed) && reportUsed >= 0) {
return Math.floor(reportUsed);
}
const userUsed = Number(this.user?.download_traffic_used || 0);
return Number.isFinite(userUsed) && userUsed > 0 ? Math.floor(userUsed) : 0;
},
downloadTrafficIsUnlimited() {
if (this.downloadTrafficReport?.quota?.is_unlimited === true) {
return true;
}
return this.downloadTrafficQuotaBytes <= 0;
},
downloadTrafficRemainingBytes() {
if (this.downloadTrafficIsUnlimited) {
return null;
}
const reportRemaining = Number(this.downloadTrafficReport?.quota?.remaining);
if (Number.isFinite(reportRemaining) && reportRemaining >= 0) {
return Math.floor(reportRemaining);
}
return Math.max(0, this.downloadTrafficQuotaBytes - this.downloadTrafficUsedBytes);
},
downloadTrafficUsagePercentage() {
if (this.downloadTrafficIsUnlimited) {
return 0;
}
const reportPercentage = Number(this.downloadTrafficReport?.quota?.usage_percentage);
if (Number.isFinite(reportPercentage) && reportPercentage >= 0) {
return Math.min(100, Math.round(reportPercentage));
}
if (this.downloadTrafficQuotaBytes <= 0) {
return 0;
}
return Math.min(100, Math.round((this.downloadTrafficUsedBytes / this.downloadTrafficQuotaBytes) * 100));
},
downloadTrafficResetCycle() {
return this.downloadTrafficReport?.quota?.reset_cycle
|| this.user?.download_traffic_reset_cycle
|| 'none';
},
downloadTrafficExpiresAt() {
return this.downloadTrafficReport?.quota?.expires_at
|| this.user?.download_traffic_quota_expires_at
|| null;
},
downloadTrafficDailyRowsDesc() {
const rows = Array.isArray(this.downloadTrafficReport?.daily)
? this.downloadTrafficReport.daily
: [];
return [...rows].reverse();
},
// 存储类型显示文本
storageTypeText() {
return this.storageType === 'local' ? '本地存储' : 'OSS存储';
@@ -1460,17 +1545,50 @@ handleDragLeave(e) {
this.loadFiles(newPath);
},
downloadFile(file) {
async downloadFile(file) {
// 构建文件路径
const filePath = this.currentPath === '/' ? `/${file.name}` : `${this.currentPath}/${file.name}`;
// 统一走后端下载接口,确保下载流量可精确计量
const hasDownloadTrafficLimit = Number(this.user?.download_traffic_quota || 0) > 0;
const canDirectOssDownload = this.storageType === 'oss'
&& this.user?.oss_config_source !== 'none'
&& !hasDownloadTrafficLimit;
// OSS 且未启用下载限流:优先使用 OSS 直连下载(速度更快)
if (canDirectOssDownload) {
const directResult = await this.downloadFromOSS(filePath);
if (directResult) {
return;
}
}
// 其他场景走后端下载接口(支持下载流量计量/权限控制)
this.downloadFromLocal(filePath);
},
// 保留方法名兼容旧调用,内部统一转发到后端下载
downloadFromOSS(filePath) {
this.downloadFromLocal(filePath);
async downloadFromOSS(filePath) {
try {
const response = await axios.get(`${this.apiBase}/api/files/download-url`, {
params: { path: filePath }
});
if (!response.data?.success || !response.data?.downloadUrl) {
return false;
}
const link = document.createElement('a');
link.href = response.data.downloadUrl;
link.setAttribute('download', '');
link.rel = 'noopener noreferrer';
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
return true;
} catch (error) {
console.error('OSS直连下载失败将回退到后端下载:', error);
return false;
}
},
// 本地存储下载
@@ -2731,6 +2849,67 @@ handleDragLeave(e) {
}
},
async loadDownloadTrafficReport(days = this.downloadTrafficReport.days) {
if (!this.isLoggedIn || !this.user || this.user.is_admin) {
return;
}
const allowedDays = [7, 30, 90, 180];
const normalizedDays = allowedDays.includes(Number(days)) ? Number(days) : 30;
this.downloadTrafficReport.days = normalizedDays;
this.downloadTrafficReport.loading = true;
this.downloadTrafficReport.error = null;
try {
const response = await axios.get(
`${this.apiBase}/api/user/download-traffic-report?days=${normalizedDays}`,
);
if (response.data.success) {
const quota = response.data.quota || null;
const report = response.data.report || {};
this.downloadTrafficReport.quota = quota;
this.downloadTrafficReport.daily = Array.isArray(report.daily) ? report.daily : [];
this.downloadTrafficReport.summary = report.summary || null;
this.downloadTrafficReport.lastUpdatedAt = new Date().toISOString();
// 同步到 user 对象,保证文件页/设置页显示一致
if (this.user && quota) {
this.user.download_traffic_quota = Number(quota.quota || 0);
this.user.download_traffic_used = Number(quota.used || 0);
this.user.download_traffic_reset_cycle = quota.reset_cycle || 'none';
this.user.download_traffic_quota_expires_at = quota.expires_at || null;
this.user.download_traffic_last_reset_at = quota.last_reset_at || null;
}
} else {
this.downloadTrafficReport.error = response.data.message || '获取报表失败';
}
} catch (error) {
console.error('获取下载流量报表失败:', error);
this.downloadTrafficReport.error = error.response?.data?.message || '获取报表失败';
} finally {
this.downloadTrafficReport.loading = false;
}
},
setDownloadTrafficReportDays(days) {
const nextDays = Number(days);
if (this.downloadTrafficReport.loading || nextDays === this.downloadTrafficReport.days) {
return;
}
this.loadDownloadTrafficReport(nextDays);
},
formatReportDateLabel(dateKey) {
if (!dateKey) return '-';
const match = String(dateKey).match(/^(\d{4})-(\d{2})-(\d{2})$/);
if (match) {
return `${match[2]}-${match[3]}`;
}
return dateKey;
},
// 刷新存储空间使用统计(根据当前存储类型)
async refreshStorageUsage() {
if (this.storageType === 'oss' && this.user?.oss_config_source !== 'none') {
@@ -2894,7 +3073,9 @@ handleDragLeave(e) {
}
break;
case 'settings':
// 设置页面不需要额外加载数据
if (this.user && !this.user.is_admin) {
this.loadDownloadTrafficReport();
}
break;
}
},
@@ -3612,6 +3793,7 @@ handleDragLeave(e) {
} else if (newView === 'settings' && this.user && !this.user.is_admin) {
// 普通用户进入设置页面时加载OSS配置
this.loadOssConfig();
this.loadDownloadTrafficReport();
}
// 记住最后停留的视图(需合法且已登录)