Files
FileCodeBoxFronted2026/src/composables/useDashboardStats.ts
T
orion b60c003ec2 优化 2026 前端无障碍与响应式体验
完善 2026 主题的移动端与桌面端交互体验,修复多项可访问性和信息展示问题。

- 允许浏览器缩放,补充焦点样式、按钮名称、表单标签和对话框键盘焦点管理\n- 支持 reduced-motion,扩大公共页、后台导航、记录列表和分页控件的触控区域\n- 优化系统设置分区导航、密码显示、字段语义和底部粘性保存栏\n- 压缩移动文件列表操作区,改善桌面表格滚动可达性和统计值截断\n- 优化仪表盘指标、趋势摘要、进度条语义及中英文文案
2026-07-21 11:05:23 +08:00

189 lines
6.8 KiB
TypeScript

import { computed, ref, reactive } from 'vue'
import { useI18n } from 'vue-i18n'
import { StatsService } from '@/services'
import type { DashboardData, DashboardHealthSummary, DashboardViewData } from '@/types'
import { formatFileSize, getErrorMessage } from '@/utils/common'
type UseDashboardStatsOptions = {
loadFailedMessage?: string
}
const emptyDashboardData = (): DashboardViewData => ({
hasExtendedStats: false,
totalFiles: 0,
storageUsed: 0,
yesterdayCount: 0,
todayCount: 0,
yesterdaySize: 0,
todaySize: 0,
sysUptime: null,
activeCount: 0,
expiredCount: 0,
textCount: 0,
fileCount: 0,
chunkedCount: 0,
usedCount: 0,
storageBackend: '-',
uploadSizeLimit: 0,
openUpload: 0,
enableChunk: 0,
maxSaveSeconds: 0,
healthAttentionCount: 0,
healthDangerCount: 0,
healthWarningCount: 0,
expiringSoonCount: 0,
storageIssueCount: 0,
neverRetrievedCount: 0,
healthyCount: 0,
permanentCount: 0,
storageUsedText: '0 Bytes',
yesterdaySizeText: '0 Bytes',
todaySizeText: '0 Bytes',
uploadSizeLimitText: '0 Bytes',
sysUptimeText: '-',
activeRatio: 0,
textRatio: 0,
fileRatio: 0,
healthyRatio: 0,
healthAttentionRatio: 0,
todaySizeRatio: 0
})
const toNumber = (value: number | string | null | undefined) => Number(value || 0)
const clampRatio = (value: number) => Math.max(0, Math.min(100, Math.round(value)))
const hasOwn = (target: object, key: string) => Object.prototype.hasOwnProperty.call(target, key)
const formatDuration = (startTimestamp: number | null, dayLabel: string, hourLabel: string) => {
if (!startTimestamp) return '-'
const uptime = Date.now() - startTimestamp
const days = Math.floor(uptime / (24 * 60 * 60 * 1000))
const hours = Math.floor((uptime % (24 * 60 * 60 * 1000)) / (60 * 60 * 1000))
return `${days}${dayLabel} ${hours}${hourLabel}`
}
const healthSummaryKeys: (keyof DashboardHealthSummary)[] = [
'healthAttentionCount',
'healthDangerCount',
'healthWarningCount',
'expiringSoonCount',
'storageIssueCount',
'neverRetrievedCount',
'healthyCount',
'permanentCount'
]
const normalizeHealthSummary = (detail: DashboardData): DashboardHealthSummary => ({
healthAttentionCount: toNumber(
detail.healthSummary?.healthAttentionCount ?? detail.healthAttentionCount
),
healthDangerCount: toNumber(detail.healthSummary?.healthDangerCount ?? detail.healthDangerCount),
healthWarningCount: toNumber(
detail.healthSummary?.healthWarningCount ?? detail.healthWarningCount
),
expiringSoonCount: toNumber(detail.healthSummary?.expiringSoonCount ?? detail.expiringSoonCount),
storageIssueCount: toNumber(detail.healthSummary?.storageIssueCount ?? detail.storageIssueCount),
neverRetrievedCount: toNumber(
detail.healthSummary?.neverRetrievedCount ?? detail.neverRetrievedCount
),
healthyCount: toNumber(detail.healthSummary?.healthyCount ?? detail.healthyCount),
permanentCount: toNumber(detail.healthSummary?.permanentCount ?? detail.permanentCount)
})
export function useDashboardStats(options: UseDashboardStatsOptions = {}) {
const { t, locale } = useI18n()
const dashboardData = reactive<DashboardViewData>(emptyDashboardData())
const isLoading = ref(false)
const errorMessage = ref('')
const lastUpdatedAt = ref<Date | null>(null)
const lastUpdatedText = computed(() =>
lastUpdatedAt.value
? new Intl.DateTimeFormat(locale.value, { dateStyle: 'medium', timeStyle: 'short' }).format(lastUpdatedAt.value)
: '-'
)
const fetchDashboardData = async () => {
isLoading.value = true
errorMessage.value = ''
try {
const response = await StatsService.getDashboard()
if (!response.detail) {
throw new Error('No dashboard data')
}
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)
const healthSummary = normalizeHealthSummary(detail)
healthSummaryKeys.forEach((key) => {
dashboardData[key] = healthSummary[key]
})
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,
t('common.dayShort'),
t('common.hourShort')
)
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.healthyRatio = dashboardData.totalFiles
? clampRatio((dashboardData.healthyCount / dashboardData.totalFiles) * 100)
: 0
dashboardData.healthAttentionRatio = dashboardData.totalFiles
? clampRatio((dashboardData.healthAttentionCount / 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,
errorMessage,
fetchDashboardData,
isLoading,
lastUpdatedText
}
}