fix(dashboard): show media empty and retry states (#589)

This commit is contained in:
InfinityPacer
2026-07-25 12:57:28 +08:00
committed by GitHub
parent 48f8e8fc71
commit 76c524eb9b
11 changed files with 461 additions and 40 deletions
@@ -0,0 +1,78 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const props = defineProps<{
/** 空结果使用的图标。 */
emptyIcon: string
/** 成功空结果对应的业务文案。 */
emptyText: string
/** 当前是否正在等待首次可用结果。 */
loading: boolean
/** 当前是否因请求失败而没有可展示的快照。 */
failed: boolean
/** 卡片标题。 */
title: string
}>()
defineSlots<{
/** 在标题栏右侧显示与当前状态相关的轻量操作。 */
append?: () => unknown
}>()
const { t } = useI18n()
</script>
<template>
<VCard class="dashboard-media-state dashboard-grid-fill">
<VCardItem class="dashboard-media-state-header">
<VCardTitle>{{ props.title }}</VCardTitle>
<template #append>
<slot name="append" />
</template>
</VCardItem>
<VCardText
class="dashboard-media-state-content text-medium-emphasis"
:role="props.failed ? 'alert' : 'status'"
aria-live="polite"
>
<template v-if="props.loading">
<VProgressCircular indeterminate color="primary" size="28" width="2" />
<span>{{ t('common.loading') }}</span>
</template>
<template v-else-if="props.failed">
<VIcon icon="mdi-server-network-off" color="warning" size="30" />
<span>{{ t('dashboard.mediaServerLoadFailed') }}</span>
</template>
<template v-else>
<VIcon :icon="props.emptyIcon" size="30" />
<span>{{ props.emptyText }}</span>
</template>
</VCardText>
</VCard>
</template>
<style scoped>
.dashboard-media-state {
display: flex;
flex-direction: column;
min-block-size: 10rem;
}
.dashboard-media-state-header {
padding-block-end: 0.25rem;
}
.dashboard-media-state-content {
display: flex;
flex: 1 1 auto;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 0.65rem;
min-block-size: 0;
text-align: center;
}
</style>
@@ -0,0 +1,58 @@
<script setup lang="ts">
const props = defineProps<{
/** 有可展示快照时,在支持悬停的桌面设备上延后到卡片交互时显示。 */
deferred?: boolean
/** 描述当前卡片重试目标的可访问文案。 */
label: string
}>()
const emit = defineEmits<{
/** 请求用户主动重新加载当前卡片。 */
retry: []
}>()
</script>
<template>
<VBtn
icon
variant="text"
color="warning"
size="small"
:class="{ 'dashboard-retry-button--deferred': props.deferred }"
:aria-label="props.label"
@click="emit('retry')"
>
<VIcon icon="mdi-cloud-alert-outline" size="20" />
<VTooltip activator="parent" location="top">
{{ props.label }}
</VTooltip>
</VBtn>
</template>
<style scoped>
@media (hover: none), (pointer: coarse) {
.dashboard-retry-button--deferred {
display: none;
}
}
@media (hover: hover) and (pointer: fine) {
.dashboard-retry-button--deferred {
opacity: 0;
pointer-events: none;
transition: opacity 180ms ease;
}
:global(.v-card:hover .dashboard-retry-button--deferred),
:global(.v-card:focus-within .dashboard-retry-button--deferred) {
opacity: 1;
pointer-events: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.dashboard-retry-button--deferred {
transition: none;
}
}
</style>