From 175f610524f52402957e4ec1448240c57106df13 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Thu, 25 Jun 2026 11:28:36 +0800 Subject: [PATCH] Animate dashboard metrics and standardize card sizing --- src/components/cards/BackdropCard.vue | 52 +++++++++-- src/components/cards/LibraryCard.vue | 52 +++++++++-- src/components/cards/PosterCard.vue | 41 ++++++++- src/composables/useDashboardMotion.ts | 41 +++++++++ src/views/dashboard/AnalyticsCpu.vue | 15 +++- .../dashboard/AnalyticsMediaStatistic.vue | 90 +++++++++++++------ src/views/dashboard/AnalyticsMemory.vue | 18 +++- src/views/dashboard/AnalyticsNetwork.vue | 36 ++++---- src/views/dashboard/AnalyticsScheduler.vue | 1 + src/views/dashboard/AnalyticsSpeed.vue | 82 +++++++++++------ src/views/dashboard/AnalyticsStorage.vue | 46 ++++++++-- .../dashboard/AnalyticsWeeklyOverview.vue | 17 +++- src/views/dashboard/MediaServerLatest.vue | 1 - 13 files changed, 392 insertions(+), 100 deletions(-) create mode 100644 src/composables/useDashboardMotion.ts diff --git a/src/components/cards/BackdropCard.vue b/src/components/cards/BackdropCard.vue index 4b6c92dd..6e4a9fa5 100644 --- a/src/components/cards/BackdropCard.vue +++ b/src/components/cards/BackdropCard.vue @@ -13,6 +13,12 @@ const props = defineProps({ const imageLoaded = ref(false) const imageLoadError = ref(false) +const cardStyle = computed(() => ({ + aspectRatio: props.height ? undefined : '3 / 2', + blockSize: props.height, + inlineSize: props.width || '100%', +})) + // 图片加载完成响应 function imageLoadHandler() { imageLoaded.value = true @@ -49,8 +55,7 @@ const getImgUrl = computed(() => {
diff --git a/src/composables/useDashboardMotion.ts b/src/composables/useDashboardMotion.ts new file mode 100644 index 00000000..24a0094f --- /dev/null +++ b/src/composables/useDashboardMotion.ts @@ -0,0 +1,41 @@ +import { TransitionPresets, usePreferredReducedMotion, useTransition, type UseTransitionOptions } from '@vueuse/core' +import { computed, type MaybeRefOrGetter } from 'vue' + +const fileSizeUnits = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'] as const + +export function useDashboardMotionDisabled() { + const preferredMotion = usePreferredReducedMotion() + + return computed(() => preferredMotion.value === 'reduce') +} + +export function useAnimatedDashboardNumber(source: MaybeRefOrGetter, options: UseTransitionOptions = {}) { + const disabled = useDashboardMotionDisabled() + + return useTransition(source, { + duration: 420, + transition: TransitionPresets.easeOutQuart, + disabled, + ...options, + }) +} + +export function formatDashboardCount(value: number) { + return Math.round(Math.max(Number(value) || 0, 0)).toLocaleString() +} + +export function formatDashboardFileSize(bytes: number, decimals = 2, targetBytes = bytes) { + let size = Math.abs(Number(targetBytes) || Number(bytes) || 0) + let unitIndex = 0 + + while (size >= 1024 && unitIndex < fileSizeUnits.length - 1) { + size /= 1024 + unitIndex++ + } + + const divisor = 1024 ** unitIndex + const value = (Math.abs(Number(bytes) || 0) / divisor).toFixed(decimals) + const prefix = bytes < 0 ? '-' : '' + + return `${prefix}${value} ${fileSizeUnits[unitIndex]}` +} diff --git a/src/views/dashboard/AnalyticsCpu.vue b/src/views/dashboard/AnalyticsCpu.vue index d4e78642..32aa2a23 100644 --- a/src/views/dashboard/AnalyticsCpu.vue +++ b/src/views/dashboard/AnalyticsCpu.vue @@ -2,6 +2,7 @@ import { useTheme } from 'vuetify' import { hexToRgb } from '@layouts/utils' import api from '@/api' +import { useAnimatedDashboardNumber } from '@/composables/useDashboardMotion' import { useI18n } from 'vue-i18n' import { useBackground } from '@/composables/useBackground' import { useKeepAliveRefresh } from '@/composables/useKeepAliveRefresh' @@ -39,6 +40,10 @@ const series = ref([ // 当前值 const current = ref(0) +const animatedCurrent = useAnimatedDashboardNumber(current, { + duration: 520, +}) +const animatedCurrentText = computed(() => Math.round(animatedCurrent.value).toLocaleString()) const chartOptions = controlledComputed( () => vuetifyTheme.name.value, @@ -109,7 +114,7 @@ async function loadCpuData() { if (!props.allowRefresh) return try { // 请求数据 - current.value = (await api.get('dashboard/cpu')) ?? 0 + current.value = Number(await api.get('dashboard/cpu')) || 0 // 使用nextTick确保DOM更新完成后再更新图表数据 await nextTick() // 添加到序列 @@ -141,7 +146,9 @@ useKeepAliveRefresh(refresh)
-

{{ t('dashboard.current') }}:{{ current }}%

+

+ {{ t('dashboard.current') }}:{{ animatedCurrentText }}% +

@@ -159,4 +166,8 @@ useKeepAliveRefresh(refresh) min-block-size: 0; } +.dashboard-chart-value { + font-variant-numeric: tabular-nums; +} + diff --git a/src/views/dashboard/AnalyticsMediaStatistic.vue b/src/views/dashboard/AnalyticsMediaStatistic.vue index 8d7d3a1b..0875b43f 100644 --- a/src/views/dashboard/AnalyticsMediaStatistic.vue +++ b/src/views/dashboard/AnalyticsMediaStatistic.vue @@ -1,44 +1,72 @@