feat: enhance dashboard layout with fixed height components and improved styling for media cards

This commit is contained in:
jxxghp
2026-06-28 09:05:31 +08:00
parent 165247b263
commit 1889d5d1e3
12 changed files with 171 additions and 44 deletions
+32 -39
View File
@@ -130,6 +130,18 @@ const enableConfig = ref<{ [key: string]: boolean }>({
latest: true, latest: true,
}) })
// 仅声明了默认行高的内置仪表盘组件使用固定 grid 高度,插件和空媒体组件继续按内容自适应。
const fixedHeightDashboardIds = new Set([
'storage',
'mediaStatistic',
'weeklyOverview',
'speed',
'scheduler',
'cpu',
'memory',
'network',
])
// 仪表板顺序配置 // 仪表板顺序配置
const orderConfig = ref<{ id: string; key: string }[]>([]) const orderConfig = ref<{ id: string; key: string }[]>([])
@@ -141,7 +153,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 4 }, cols: { cols: 12, md: 4 },
rows: 5, rows: 11,
elements: [], elements: [],
}, },
{ {
@@ -150,7 +162,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 8 }, cols: { cols: 12, md: 8 },
rows: 5, rows: 11,
elements: [], elements: [],
}, },
{ {
@@ -159,7 +171,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 4 }, cols: { cols: 12, md: 4 },
rows: 11, rows: 23,
elements: [], elements: [],
}, },
{ {
@@ -168,7 +180,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 4 }, cols: { cols: 12, md: 4 },
rows: 11, rows: 23,
elements: [], elements: [],
}, },
{ {
@@ -177,7 +189,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 4 }, cols: { cols: 12, md: 4 },
rows: 11, rows: 23,
elements: [], elements: [],
}, },
{ {
@@ -186,7 +198,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 6 }, cols: { cols: 12, md: 6 },
rows: 8, rows: 17,
elements: [], elements: [],
}, },
{ {
@@ -195,7 +207,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 6 }, cols: { cols: 12, md: 6 },
rows: 8, rows: 17,
elements: [], elements: [],
}, },
{ {
@@ -204,7 +216,7 @@ const dashboardConfigs = ref<DashboardItem[]>([
key: '', key: '',
attrs: {}, attrs: {},
cols: { cols: 12, md: 6 }, cols: { cols: 12, md: 6 },
rows: 8, rows: 17,
elements: [], elements: [],
}, },
{ {
@@ -396,7 +408,8 @@ function buildRemoteDashboardGridLayout(layout: DashboardGridLayoutConfig) {
// 根据当前视口判断仪表板布局档位,避免手机和桌面共用 Grid 坐标。 // 根据当前视口判断仪表板布局档位,避免手机和桌面共用 Grid 坐标。
function resolveDashboardLayoutProfile(): DashboardLayoutProfile { function resolveDashboardLayoutProfile(): DashboardLayoutProfile {
const width = display.width.value || (typeof window === 'undefined' ? DASHBOARD_GRID_DESKTOP_BREAKPOINT : window.innerWidth) const width =
display.width.value || (typeof window === 'undefined' ? DASHBOARD_GRID_DESKTOP_BREAKPOINT : window.innerWidth)
if (width <= DASHBOARD_GRID_MOBILE_BREAKPOINT) return 'mobile' if (width <= DASHBOARD_GRID_MOBILE_BREAKPOINT) return 'mobile'
if (width <= DASHBOARD_GRID_TABLET_BREAKPOINT) return 'tablet' if (width <= DASHBOARD_GRID_TABLET_BREAKPOINT) return 'tablet'
@@ -1123,7 +1136,10 @@ watch(
dashboardLayoutProfile.value = nextProfile dashboardLayoutProfile.value = nextProfile
dashboardGridLayout.value = (await loadDashboardGridLayoutConfig(nextProfile)) ?? {} dashboardGridLayout.value = (await loadDashboardGridLayoutConfig(nextProfile)) ?? {}
dashboardGrid.value?.column(getDashboardGridColumnsForProfile(nextProfile), getDashboardGridColumnLayout(nextProfile)) dashboardGrid.value?.column(
getDashboardGridColumnsForProfile(nextProfile),
getDashboardGridColumnLayout(nextProfile),
)
dashboardGrid.value?.removeAll(false, false) dashboardGrid.value?.removeAll(false, false)
await syncDashboardGrid() await syncDashboardGrid()
notifyDashboardContentResize() notifyDashboardContentResize()
@@ -1184,7 +1200,10 @@ onBeforeUnmount(() => {
v-for="gridItem in dashboardGridItems" v-for="gridItem in dashboardGridItems"
:key="gridItem.id" :key="gridItem.id"
class="grid-stack-item dashboard-grid-item" class="grid-stack-item dashboard-grid-item"
:class="{ 'is-manual-height': hasManualDashboardGridHeight(gridItem.id) }" :class="{
'is-manual-height': hasManualDashboardGridHeight(gridItem.id),
'is-fixed-height': fixedHeightDashboardIds.has(gridItem.config.id) && !gridItem.config.key,
}"
:gs-id="gridItem.id" :gs-id="gridItem.id"
:gs-x="gridItem.widget.x" :gs-x="gridItem.widget.x"
:gs-y="gridItem.widget.y" :gs-y="gridItem.widget.y"
@@ -1270,6 +1289,8 @@ onBeforeUnmount(() => {
inline-size: 100%; inline-size: 100%;
} }
.dashboard-grid-item.is-fixed-height .dashboard-grid-auto-size,
.dashboard-grid-item.is-fixed-height .dashboard-grid-content-measure,
.dashboard-grid-item.is-manual-height .dashboard-grid-auto-size, .dashboard-grid-item.is-manual-height .dashboard-grid-auto-size,
.dashboard-grid-item.is-manual-height .dashboard-grid-content-measure, .dashboard-grid-item.is-manual-height .dashboard-grid-content-measure,
.dashboard-grid.is-editing .dashboard-grid-auto-size, .dashboard-grid.is-editing .dashboard-grid-auto-size,
@@ -1281,34 +1302,6 @@ onBeforeUnmount(() => {
block-size: 100%; block-size: 100%;
} }
.dashboard-grid :deep(.dashboard-chart-card),
.dashboard-grid :deep(.dashboard-summary-card),
.dashboard-grid :deep(.dashboard-work-card),
.dashboard-grid :deep(.dashboard-media-card) {
display: flex;
flex-direction: column;
min-block-size: 0;
}
.dashboard-grid :deep(.dashboard-chart-card .v-card-text),
.dashboard-grid :deep(.dashboard-work-card .v-card-text),
.dashboard-grid :deep(.dashboard-card-grid-wrap) {
flex: 1 1 auto;
min-block-size: 0;
}
.dashboard-grid:not(.is-editing) .dashboard-grid-item:not(.is-manual-height) :deep(.dashboard-summary-card) {
min-block-size: 160px;
}
.dashboard-grid:not(.is-editing) .dashboard-grid-item:not(.is-manual-height) :deep(.dashboard-chart-card) {
min-block-size: 256px;
}
.dashboard-grid:not(.is-editing) .dashboard-grid-item:not(.is-manual-height) :deep(.dashboard-work-card) {
min-block-size: 352px;
}
.dashboard-grid.is-editing :deep(.v-card-text), .dashboard-grid.is-editing :deep(.v-card-text),
.dashboard-grid-item.is-manual-height :deep(.v-card-text) { .dashboard-grid-item.is-manual-height :deep(.v-card-text) {
overflow: auto; overflow: auto;
+8
View File
@@ -154,11 +154,19 @@ useKeepAliveRefresh(refresh)
</template> </template>
<style scoped> <style scoped>
.dashboard-chart-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-chart-content { .dashboard-chart-content {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.dashboard-chart-plot { .dashboard-chart-plot {
@@ -87,7 +87,7 @@ onActivated(() => {
<VCardTitle>{{ t('dashboard.mediaStatistic') }}</VCardTitle> <VCardTitle>{{ t('dashboard.mediaStatistic') }}</VCardTitle>
</VCardItem> </VCardItem>
<VCardText> <VCardText class="dashboard-summary-content">
<VRow> <VRow>
<VCol v-for="item in statistics" :key="item.title" cols="6" sm="3"> <VCol v-for="item in statistics" :key="item.title" cols="6" sm="3">
<div class="d-flex align-center"> <div class="d-flex align-center">
@@ -111,6 +111,25 @@ onActivated(() => {
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-summary-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-summary-content {
display: flex;
flex: 1 1 auto;
align-items: center;
min-block-size: 0;
}
.dashboard-summary-content :deep(.v-row) {
flex: 1 1 auto;
align-items: center;
}
.dashboard-number { .dashboard-number {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
+8
View File
@@ -160,11 +160,19 @@ useKeepAliveRefresh(refresh)
</template> </template>
<style scoped> <style scoped>
.dashboard-chart-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-chart-content { .dashboard-chart-content {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.dashboard-chart-plot { .dashboard-chart-plot {
+8
View File
@@ -196,11 +196,19 @@ useKeepAliveRefresh(refresh)
</template> </template>
<style scoped> <style scoped>
.dashboard-chart-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-chart-content { .dashboard-chart-content {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.dashboard-chart-plot { .dashboard-chart-plot {
@@ -83,6 +83,13 @@ useDataRefresh(
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-work-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.card-list { .card-list {
--v-card-list-gap: 1.5rem; --v-card-list-gap: 1.5rem;
@@ -96,6 +103,7 @@ useDataRefresh(
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.card-list::-webkit-scrollbar { .card-list::-webkit-scrollbar {
+8
View File
@@ -146,6 +146,13 @@ const { loading } = useDataRefresh(
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.dashboard-work-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.card-list { .card-list {
--v-card-list-gap: 1rem; --v-card-list-gap: 1rem;
@@ -159,6 +166,7 @@ const { loading } = useDataRefresh(
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.dashboard-speed-number { .dashboard-speed-number {
+15 -1
View File
@@ -69,7 +69,7 @@ onActivated(() => {
<VCardItem> <VCardItem>
<VCardTitle>{{ t('dashboard.storage') }}</VCardTitle> <VCardTitle>{{ t('dashboard.storage') }}</VCardTitle>
</VCardItem> </VCardItem>
<VCardText> <VCardText class="dashboard-summary-content">
<h5 class="animated-storage-value text-2xl font-weight-medium text-primary"> <h5 class="animated-storage-value text-2xl font-weight-medium text-primary">
{{ animatedStorageText }} {{ animatedStorageText }}
</h5> </h5>
@@ -106,6 +106,20 @@ onActivated(() => {
inset-inline-end: 2rem; inset-inline-end: 2rem;
} }
.dashboard-summary-card {
position: relative;
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
overflow: hidden;
}
.dashboard-summary-content {
flex: 1 1 auto;
min-block-size: 0;
}
.animated-storage-value { .animated-storage-value {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
@@ -156,11 +156,19 @@ onActivated(() => {
</template> </template>
<style scoped> <style scoped>
.dashboard-work-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-work-content { .dashboard-work-content {
display: flex; display: flex;
flex: 1 1 auto; flex: 1 1 auto;
flex-direction: column; flex-direction: column;
min-block-size: 0; min-block-size: 0;
overflow: hidden;
} }
.dashboard-work-chart { .dashboard-work-chart {
+16 -1
View File
@@ -62,7 +62,7 @@ onActivated(() => {
<VCardTitle>{{ t('dashboard.latest') }} - {{ name }}</VCardTitle> <VCardTitle>{{ t('dashboard.latest') }} - {{ name }}</VCardTitle>
</VCardItem> </VCardItem>
<div class="px-5 pb-3"> <div class="dashboard-media-content px-5 pb-3">
<ProgressiveCardGrid <ProgressiveCardGrid
class="dashboard-media-grid" class="dashboard-media-grid"
:items="data" :items="data"
@@ -91,7 +91,10 @@ onActivated(() => {
} }
.dashboard-media-stack > .dashboard-media-card { .dashboard-media-stack > .dashboard-media-card {
display: flex;
flex-direction: column;
flex: 1 1 auto; flex: 1 1 auto;
block-size: 100%;
min-block-size: 0; min-block-size: 0;
} }
@@ -99,4 +102,16 @@ onActivated(() => {
flex: 1 1 auto; flex: 1 1 auto;
min-block-size: 0; min-block-size: 0;
} }
.dashboard-media-content {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-block-size: 0;
overflow: auto;
}
.dashboard-media-content::-webkit-scrollbar {
display: none;
}
</style> </style>
+20 -1
View File
@@ -65,7 +65,7 @@ onActivated(() => {
<VCardItem> <VCardItem>
<VCardTitle>{{ t('dashboard.library') }}</VCardTitle> <VCardTitle>{{ t('dashboard.library') }}</VCardTitle>
</VCardItem> </VCardItem>
<div class="px-5 pb-3"> <div class="dashboard-media-content px-5 pb-3">
<ProgressiveCardGrid <ProgressiveCardGrid
class="dashboard-media-grid" class="dashboard-media-grid"
:items="libraryList" :items="libraryList"
@@ -89,4 +89,23 @@ onActivated(() => {
flex: 1 1 auto; flex: 1 1 auto;
min-block-size: 0; min-block-size: 0;
} }
.dashboard-media-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-media-content {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-block-size: 0;
overflow: auto;
}
.dashboard-media-content::-webkit-scrollbar {
display: none;
}
</style> </style>
+20 -1
View File
@@ -66,7 +66,7 @@ onActivated(() => {
<VCardTitle>{{ t('dashboard.playing') }}</VCardTitle> <VCardTitle>{{ t('dashboard.playing') }}</VCardTitle>
</VCardItem> </VCardItem>
<div class="px-5 pb-3"> <div class="dashboard-media-content px-5 pb-3">
<ProgressiveCardGrid <ProgressiveCardGrid
class="dashboard-media-grid" class="dashboard-media-grid"
:items="playingList" :items="playingList"
@@ -90,4 +90,23 @@ onActivated(() => {
flex: 1 1 auto; flex: 1 1 auto;
min-block-size: 0; min-block-size: 0;
} }
.dashboard-media-card {
display: flex;
flex-direction: column;
block-size: 100%;
min-block-size: 0;
}
.dashboard-media-content {
display: flex;
flex: 1 1 auto;
flex-direction: column;
min-block-size: 0;
overflow: auto;
}
.dashboard-media-content::-webkit-scrollbar {
display: none;
}
</style> </style>