From 3b1a510537f560be8bd096bb7af87c645727f595 Mon Sep 17 00:00:00 2001 From: Lan Date: Wed, 3 Jun 2026 03:39:13 +0800 Subject: [PATCH] feat: improve dashboard refresh state --- src/composables/useDashboardStats.ts | 130 +++++++++++++++++---------- src/i18n/locales/en-US.ts | 3 + src/i18n/locales/zh-CN.ts | 3 + src/types/dashboard.ts | 48 ++++++---- src/views/manage/DashboardView.vue | 107 +++++++++++++++++----- 5 files changed, 204 insertions(+), 87 deletions(-) diff --git a/src/composables/useDashboardStats.ts b/src/composables/useDashboardStats.ts index 452fc92..b28fa75 100644 --- a/src/composables/useDashboardStats.ts +++ b/src/composables/useDashboardStats.ts @@ -1,7 +1,11 @@ -import { reactive } from 'vue' +import { computed, ref, reactive } from 'vue' import { StatsService } from '@/services' import type { DashboardViewData } from '@/types' -import { formatFileSize } from '@/utils/common' +import { formatFileSize, getErrorMessage } from '@/utils/common' + +type UseDashboardStatsOptions = { + loadFailedMessage?: string +} const emptyDashboardData = (): DashboardViewData => ({ hasExtendedStats: false, @@ -50,59 +54,91 @@ const formatDuration = (startTimestamp: number | null) => { return `${days}天${hours}小时` } -export function useDashboardStats() { +const normalizeRecentFiles = (recentFiles: DashboardViewData['recentFiles']) => + recentFiles.map((file) => ({ + ...file, + size: toNumber(file.size), + expiredCount: toNumber(file.expiredCount), + usedCount: toNumber(file.usedCount) + })) + +export function useDashboardStats(options: UseDashboardStatsOptions = {}) { const dashboardData = reactive(emptyDashboardData()) + const isLoading = ref(false) + const errorMessage = ref('') + const lastUpdatedAt = ref(null) + const lastUpdatedText = computed(() => + lastUpdatedAt.value ? lastUpdatedAt.value.toLocaleString() : '-' + ) const fetchDashboardData = async () => { - const response = await StatsService.getDashboard() - if (!response.detail) return + isLoading.value = true + errorMessage.value = '' - const detail = response.detail - dashboardData.totalFiles = toNumber(detail.totalFiles) - dashboardData.storageUsed = toNumber(detail.storageUsed) - dashboardData.yesterdayCount = toNumber(detail.yesterdayCount) - dashboardData.todayCount = toNumber(detail.todayCount) - dashboardData.yesterdaySize = toNumber(detail.yesterdaySize) - dashboardData.todaySize = toNumber(detail.todaySize) - dashboardData.sysUptime = detail.sysUptime - dashboardData.hasExtendedStats = hasOwn(detail, 'activeCount') - dashboardData.activeCount = dashboardData.hasExtendedStats - ? toNumber(detail.activeCount) - : dashboardData.totalFiles - dashboardData.expiredCount = toNumber(detail.expiredCount) - dashboardData.textCount = toNumber(detail.textCount) - dashboardData.fileCount = toNumber(detail.fileCount) - dashboardData.chunkedCount = toNumber(detail.chunkedCount) - dashboardData.usedCount = toNumber(detail.usedCount) - dashboardData.storageBackend = detail.storageBackend || '-' - dashboardData.uploadSizeLimit = toNumber(detail.uploadSizeLimit) - dashboardData.openUpload = toNumber(detail.openUpload) - dashboardData.enableChunk = toNumber(detail.enableChunk) - dashboardData.maxSaveSeconds = toNumber(detail.maxSaveSeconds) - dashboardData.topSuffixes = detail.topSuffixes || [] - dashboardData.recentFiles = detail.recentFiles || [] + try { + const response = await StatsService.getDashboard() + if (!response.detail) { + throw new Error('No dashboard data') + } - dashboardData.storageUsedText = formatFileSize(dashboardData.storageUsed) - dashboardData.yesterdaySizeText = formatFileSize(dashboardData.yesterdaySize) - dashboardData.todaySizeText = formatFileSize(dashboardData.todaySize) - dashboardData.uploadSizeLimitText = formatFileSize(dashboardData.uploadSizeLimit) - dashboardData.sysUptimeText = formatDuration(dashboardData.sysUptime) - dashboardData.activeRatio = dashboardData.totalFiles - ? clampRatio((dashboardData.activeCount / dashboardData.totalFiles) * 100) - : 0 - dashboardData.textRatio = dashboardData.totalFiles - ? clampRatio((dashboardData.textCount / dashboardData.totalFiles) * 100) - : 0 - dashboardData.fileRatio = dashboardData.totalFiles - ? clampRatio((dashboardData.fileCount / dashboardData.totalFiles) * 100) - : 0 - dashboardData.todaySizeRatio = dashboardData.uploadSizeLimit - ? clampRatio((dashboardData.todaySize / dashboardData.uploadSizeLimit) * 100) - : 0 + const detail = response.detail + dashboardData.totalFiles = toNumber(detail.totalFiles) + dashboardData.storageUsed = toNumber(detail.storageUsed) + dashboardData.yesterdayCount = toNumber(detail.yesterdayCount) + dashboardData.todayCount = toNumber(detail.todayCount) + dashboardData.yesterdaySize = toNumber(detail.yesterdaySize) + dashboardData.todaySize = toNumber(detail.todaySize) + dashboardData.sysUptime = detail.sysUptime + dashboardData.hasExtendedStats = hasOwn(detail, 'activeCount') + dashboardData.activeCount = dashboardData.hasExtendedStats + ? toNumber(detail.activeCount) + : dashboardData.totalFiles + dashboardData.expiredCount = toNumber(detail.expiredCount) + dashboardData.textCount = toNumber(detail.textCount) + dashboardData.fileCount = toNumber(detail.fileCount) + dashboardData.chunkedCount = toNumber(detail.chunkedCount) + dashboardData.usedCount = toNumber(detail.usedCount) + dashboardData.storageBackend = detail.storageBackend || '-' + dashboardData.uploadSizeLimit = toNumber(detail.uploadSizeLimit) + dashboardData.openUpload = toNumber(detail.openUpload) + dashboardData.enableChunk = toNumber(detail.enableChunk) + dashboardData.maxSaveSeconds = toNumber(detail.maxSaveSeconds) + dashboardData.topSuffixes = detail.topSuffixes || [] + dashboardData.recentFiles = normalizeRecentFiles(detail.recentFiles || []) + + dashboardData.storageUsedText = formatFileSize(dashboardData.storageUsed) + dashboardData.yesterdaySizeText = formatFileSize(dashboardData.yesterdaySize) + dashboardData.todaySizeText = formatFileSize(dashboardData.todaySize) + dashboardData.uploadSizeLimitText = formatFileSize(dashboardData.uploadSizeLimit) + dashboardData.sysUptimeText = formatDuration(dashboardData.sysUptime) + dashboardData.activeRatio = dashboardData.totalFiles + ? clampRatio((dashboardData.activeCount / dashboardData.totalFiles) * 100) + : 0 + dashboardData.textRatio = dashboardData.totalFiles + ? clampRatio((dashboardData.textCount / dashboardData.totalFiles) * 100) + : 0 + dashboardData.fileRatio = dashboardData.totalFiles + ? clampRatio((dashboardData.fileCount / dashboardData.totalFiles) * 100) + : 0 + dashboardData.todaySizeRatio = dashboardData.uploadSizeLimit + ? clampRatio((dashboardData.todaySize / dashboardData.uploadSizeLimit) * 100) + : 0 + lastUpdatedAt.value = new Date() + } catch (error) { + errorMessage.value = getErrorMessage( + error, + options.loadFailedMessage || 'Failed to load dashboard data' + ) + } finally { + isLoading.value = false + } } return { dashboardData, - fetchDashboardData + errorMessage, + fetchDashboardData, + isLoading, + lastUpdatedText } } diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 91d328f..3edb985 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -65,6 +65,9 @@ export default { yesterdayShares: 'Yesterday: {count}', serverUptime: 'Uptime', refresh: 'Refresh', + refreshing: 'Refreshing', + lastUpdated: 'Last updated: {time}', + loadFailed: 'Failed to load dashboard data', fileHealth: 'File Health', fileHealthDesc: 'Real file records grouped by availability, expiry, and type.', activeFileRatio: 'Active Ratio', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 51d6610..2e59ff2 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -65,6 +65,9 @@ export default { yesterdayShares: '昨日分享:{count}', serverUptime: '运行时间', refresh: '刷新数据', + refreshing: '刷新中', + lastUpdated: '最近更新:{time}', + loadFailed: '仪表盘数据加载失败', fileHealth: '文件健康', fileHealthDesc: '基于真实文件记录统计有效、过期和类型分布。', activeFileRatio: '有效占比', diff --git a/src/types/dashboard.ts b/src/types/dashboard.ts index 5d402d3..a200857 100644 --- a/src/types/dashboard.ts +++ b/src/types/dashboard.ts @@ -1,24 +1,24 @@ export interface DashboardData { totalFiles: number - storageUsed: number + storageUsed: number | string yesterdayCount: number todayCount: number - yesterdaySize: number - todaySize: number + yesterdaySize: number | string + todaySize: number | string sysUptime: number | null - activeCount: number - expiredCount: number - textCount: number - fileCount: number - chunkedCount: number - usedCount: number - storageBackend: string - uploadSizeLimit: number - openUpload: number - enableChunk: number - maxSaveSeconds: number - topSuffixes: DashboardSuffixStat[] - recentFiles: DashboardRecentFile[] + activeCount?: number + expiredCount?: number + textCount?: number + fileCount?: number + chunkedCount?: number + usedCount?: number + storageBackend?: string + uploadSizeLimit?: number + openUpload?: number + enableChunk?: number + maxSaveSeconds?: number + topSuffixes?: DashboardSuffixStat[] + recentFiles?: DashboardRecentFile[] } export interface DashboardSuffixStat { @@ -42,6 +42,22 @@ export interface DashboardRecentFile { export interface DashboardViewData extends DashboardData { hasExtendedStats: boolean + activeCount: number + expiredCount: number + textCount: number + fileCount: number + chunkedCount: number + usedCount: number + storageBackend: string + uploadSizeLimit: number + openUpload: number + enableChunk: number + maxSaveSeconds: number + topSuffixes: DashboardSuffixStat[] + recentFiles: DashboardRecentFile[] + storageUsed: number + yesterdaySize: number + todaySize: number storageUsedText: string yesterdaySizeText: string todaySizeText: string diff --git a/src/views/manage/DashboardView.vue b/src/views/manage/DashboardView.vue index e5cf014..b00bfde 100644 --- a/src/views/manage/DashboardView.vue +++ b/src/views/manage/DashboardView.vue @@ -6,22 +6,38 @@

{{ t('admin.dashboard.title') }}

+

+ {{ t('admin.dashboard.lastUpdated', { time: lastUpdatedText }) }} +

+
+ {{ errorMessage }} +
+
- +
@@ -157,10 +176,7 @@ :label="t('admin.dashboard.guestUpload')" :value="dashboardData.openUpload ? t('common.enabled') : t('common.disabled')" /> - +
@@ -168,7 +184,10 @@ {{ t('admin.dashboard.todayCapacityReference') }} {{ dashboardData.todaySizeRatio }}%
-
+
-
+
{{ t('common.noData') }}
- {{ item.suffix || t('admin.dashboard.textType') }} + {{ + item.suffix || t('admin.dashboard.textType') + }} {{ item.count }}
-
+
-
- +
+
- - - - @@ -241,7 +287,10 @@
+ {{ t('admin.dashboard.table.file') }} + {{ t('admin.dashboard.table.size') }} + {{ t('admin.dashboard.table.usage') }} + {{ t('admin.dashboard.table.status') }}
-
+
@@ -306,7 +355,10 @@ import { formatFileSize, formatTimestamp } from '@/utils/common' const isDarkMode = useInjectedDarkMode() const { t } = useI18n() -const { dashboardData, fetchDashboardData } = useDashboardStats() +const { dashboardData, errorMessage, fetchDashboardData, isLoading, lastUpdatedText } = + useDashboardStats({ + loadFailedMessage: t('admin.dashboard.loadFailed') + }) const primaryTextClass = computed(() => (isDarkMode.value ? 'text-white' : 'text-gray-900')) const mutedTextClass = computed(() => (isDarkMode.value ? 'text-gray-400' : 'text-gray-500')) @@ -381,10 +433,17 @@ const PolicyRow = defineComponent({ }, setup(props) { return () => - h('div', { class: 'flex items-center justify-between gap-4 border-b border-gray-200/60 pb-3 last:border-b-0 dark:border-gray-700' }, [ - h('span', { class: 'text-sm text-gray-500 dark:text-gray-400' }, props.label), - h('span', { class: 'text-sm font-medium text-gray-900 dark:text-white' }, props.value) - ]) + h( + 'div', + { + class: + 'flex items-center justify-between gap-4 border-b border-gray-200/60 pb-3 last:border-b-0 dark:border-gray-700' + }, + [ + h('span', { class: 'text-sm text-gray-500 dark:text-gray-400' }, props.label), + h('span', { class: 'text-sm font-medium text-gray-900 dark:text-white' }, props.value) + ] + ) } })