From 75da7d35b4e5e8143c6bbcafc8e815121ecebbb2 Mon Sep 17 00:00:00 2001
From: jxxghp
Date: Sat, 6 Jun 2026 18:22:36 +0800
Subject: [PATCH] fix(dashboard): stabilize editable layout controls
---
.../dialog/ContentToggleSettingsDialog.vue | 16 -----
src/components/misc/DashboardElement.vue | 40 +++++------
src/pages/dashboard.vue | 72 ++++++++++++++++---
src/views/dashboard/AnalyticsCpu.vue | 26 +++----
.../dashboard/AnalyticsMediaStatistic.vue | 52 +++++++-------
src/views/dashboard/AnalyticsMemory.vue | 26 +++----
src/views/dashboard/AnalyticsNetwork.vue | 44 ++++++------
src/views/dashboard/AnalyticsScheduler.vue | 68 +++++++++---------
src/views/dashboard/AnalyticsSpeed.vue | 60 ++++++++--------
src/views/dashboard/AnalyticsStorage.vue | 40 +++++------
.../dashboard/AnalyticsWeeklyOverview.vue | 38 +++++-----
src/views/dashboard/MediaServerLatest.vue | 40 +++++------
src/views/dashboard/MediaServerLibrary.vue | 40 +++++------
src/views/dashboard/MediaServerPlaying.vue | 40 +++++------
14 files changed, 295 insertions(+), 307 deletions(-)
diff --git a/src/components/dialog/ContentToggleSettingsDialog.vue b/src/components/dialog/ContentToggleSettingsDialog.vue
index 555b953c..dd87e160 100644
--- a/src/components/dialog/ContentToggleSettingsDialog.vue
+++ b/src/components/dialog/ContentToggleSettingsDialog.vue
@@ -16,8 +16,6 @@ const props = withDefaults(
items: UnknownRecord[]
labelGetter?: (item: UnknownRecord) => string
modelValue?: boolean
- resetIcon?: string
- resetText?: string
selectAllText?: string
selectNoneText?: string
showBulkActions?: boolean
@@ -30,8 +28,6 @@ const props = withDefaults(
elevated: false,
labelGetter: undefined,
modelValue: true,
- resetIcon: 'mdi-restore',
- resetText: '',
selectAllText: '',
selectNoneText: '',
showBulkActions: false,
@@ -42,7 +38,6 @@ const props = withDefaults(
const emit = defineEmits<{
(event: 'close'): void
- (event: 'reset'): void
(event: 'save', payload: { elevated: boolean; enabled: Record }): void
(event: 'update:elevated', value: boolean): void
(event: 'update:modelValue', value: boolean): void
@@ -104,11 +99,6 @@ function setAllItems(value: boolean) {
})
}
-// 触发调用方提供的重置动作。
-function triggerResetAction() {
- emit('reset')
-}
-
// 提交通用内容开关设置。
function submitSettings() {
emit('save', {
@@ -157,12 +147,6 @@ function submitSettings() {
-
-
-
-
- {{ props.resetText }}
-
{{ props.selectAllText }}
diff --git a/src/components/misc/DashboardElement.vue b/src/components/misc/DashboardElement.vue
index 66fe1292..a5cd91d0 100644
--- a/src/components/misc/DashboardElement.vue
+++ b/src/components/misc/DashboardElement.vue
@@ -140,30 +140,28 @@ onUnmounted(() => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ props.config?.attrs?.title ?? props.config?.name }}
-
- {{ props.config?.attrs?.subtitle }}
-
-
+
+
+
+
+
-
-
+
+
+
+
+
+ {{ props.config?.attrs?.title ?? props.config?.name }}
+
+ {{ props.config?.attrs?.subtitle }}
+
+
+
+
+
+
无法渲染插件仪表盘部件: 未知渲染模式或配置错误
diff --git a/src/pages/dashboard.vue b/src/pages/dashboard.vue
index 72adff32..42e6afdc 100644
--- a/src/pages/dashboard.vue
+++ b/src/pages/dashboard.vue
@@ -33,6 +33,7 @@ const DASHBOARD_GRID_COLUMNS = 12
const DASHBOARD_GRID_CELL_HEIGHT = 16
const DASHBOARD_GRID_FALLBACK_ROWS = 4
const DASHBOARD_GRID_MARGIN = 8
+const DASHBOARD_GRID_CONTENT_RESIZE_THRESHOLD = 4
const DASHBOARD_GRID_LAYOUT_STORAGE_KEY = 'MP_DASHBOARD_GRID_LAYOUT'
interface DashboardGridLayoutItem {
@@ -66,8 +67,12 @@ const isSyncingDashboardGrid = ref(false)
// 仪表板本地布局覆盖配置
const dashboardGridLayout = ref>({})
+// 是否刚恢复过默认布局,用于避免退出编辑时立即把默认布局写回本地覆盖。
+const isDashboardGridLayoutResetPending = ref(false)
+
const dashboardGridResizeStartHeights = new Map()
const dashboardGridPendingContentResize = new Set()
+const dashboardGridObservedContentHeights = new Map()
let dashboardGridContentObserver: ResizeObserver | null = null
let dashboardGridContentResizeFrame: number | null = null
@@ -324,7 +329,6 @@ function openDashboardSettings() {
hint: t('dashboard.chooseContent'),
items: dashboardConfigs.value,
labelGetter: (item: DashboardItem) => item.attrs?.title ?? item.name,
- resetText: t('dashboard.resetLayout'),
title: t('dashboard.settings'),
valueGetter: (item: DashboardItem) => buildPluginDashboardId(item.id, item.key),
},
@@ -332,7 +336,6 @@ function openDashboardSettings() {
close: () => {
settingsDialogController = null
},
- reset: resetDashboardGridLayout,
save: saveDashboardConfig,
'update:modelValue': (value: boolean) => {
if (!value) settingsDialogController = null
@@ -347,6 +350,7 @@ function resetDashboardGridLayout() {
dashboardGridLayout.value = {}
localStorage.removeItem(DASHBOARD_GRID_LAYOUT_STORAGE_KEY)
dashboardGrid.value?.removeAll(false, false)
+ isDashboardGridLayoutResetPending.value = true
nextTick(syncDashboardGrid)
}
@@ -354,20 +358,32 @@ function resetDashboardGridLayout() {
const dashboardDynamicButtonMenuItems = computed(() => {
if (!appMode.value) return undefined
- return [
+ const items: DynamicButtonMenuItem[] = [
{
title: isLayoutEditing.value ? t('dashboard.exitEditMode') : t('dashboard.editLayout'),
icon: isLayoutEditing.value ? 'mdi-check' : 'mdi-view-dashboard-edit',
color: 'primary',
action: toggleDashboardLayoutEditing,
},
- {
- title: t('dashboard.settings'),
- icon: 'mdi-tune',
- color: 'info',
- action: openDashboardSettings,
- },
]
+
+ if (isLayoutEditing.value) {
+ items.push({
+ title: t('dashboard.resetLayout'),
+ icon: 'mdi-restore',
+ color: 'warning',
+ action: resetDashboardGridLayout,
+ })
+ }
+
+ items.push({
+ title: t('dashboard.settings'),
+ icon: 'mdi-tune',
+ color: 'info',
+ action: openDashboardSettings,
+ })
+
+ return items
})
useDynamicButton({
@@ -379,11 +395,16 @@ useDynamicButton({
// 切换仪表板布局编辑模式,退出编辑时压实并保存当前布局。
function toggleDashboardLayoutEditing() {
if (isLayoutEditing.value) {
- compactAndPersistDashboardGrid()
+ if (isDashboardGridLayoutResetPending.value) {
+ isDashboardGridLayoutResetPending.value = false
+ } else {
+ compactAndPersistDashboardGrid()
+ }
isLayoutEditing.value = false
return
}
+ isDashboardGridLayoutResetPending.value = false
isLayoutEditing.value = true
nextTick(syncDashboardGrid)
}
@@ -676,10 +697,14 @@ function observeDashboardGridContent() {
if (!gridElement || typeof ResizeObserver === 'undefined') return
dashboardGridContentObserver?.disconnect()
+ dashboardGridPendingContentResize.clear()
+ dashboardGridObservedContentHeights.clear()
dashboardGridContentObserver = new ResizeObserver(entries => {
entries.forEach(entry => {
const itemElement = entry.target.closest('.dashboard-grid-item') as GridItemHTMLElement | null
- if (itemElement) scheduleDashboardItemContentResize(itemElement)
+ if (itemElement && shouldScheduleDashboardContentResize(itemElement, entry.contentRect.height)) {
+ scheduleDashboardItemContentResize(itemElement)
+ }
})
})
@@ -688,6 +713,20 @@ function observeDashboardGridContent() {
})
}
+// 判断内容高度变化是否足够触发 GridStack 行高重算,避免 hover 级微小波动造成布局抖动。
+function shouldScheduleDashboardContentResize(element: GridItemHTMLElement, nextHeight: number) {
+ const id = element.getAttribute('gs-id') ?? ''
+ if (!id) return true
+
+ const previousHeight = dashboardGridObservedContentHeights.get(id)
+ dashboardGridObservedContentHeights.set(id, nextHeight)
+
+ return (
+ previousHeight === undefined ||
+ Math.abs(nextHeight - previousHeight) >= DASHBOARD_GRID_CONTENT_RESIZE_THRESHOLD
+ )
+}
+
// 延迟执行单个组件内容测高,合并连续 ResizeObserver 回调。
function scheduleDashboardItemContentResize(element: GridItemHTMLElement) {
dashboardGridPendingContentResize.add(element)
@@ -805,6 +844,7 @@ function getDefaultDashboardGridWidthById(id: string) {
function compactAndPersistDashboardGrid(manualHeightId: string | false = false) {
if (!dashboardGrid.value || isSyncingDashboardGrid.value) return
+ isDashboardGridLayoutResetPending.value = false
dashboardGrid.value.compact('compact')
nextTick(() => persistDashboardGridLayout(manualHeightId))
}
@@ -855,6 +895,7 @@ onBeforeUnmount(() => {
dashboardGridResizeRefreshFrame = null
}
dashboardGridPendingContentResize.clear()
+ dashboardGridObservedContentHeights.clear()
dashboardGridResizeStartHeights.clear()
dashboardGrid.value?.destroy(false)
dashboardGrid.value = null
@@ -905,6 +946,15 @@ onBeforeUnmount(() => {
class="compact-fab compact-fab--secondary"
@click="openDashboardSettings"
/>
+
-
-
-
-
- CPU
-
-
-
-
-
- {{ t('dashboard.current') }}:{{ current }}%
-
-
-
-
+
+
+ CPU
+
+
+
+
+
+ {{ t('dashboard.current') }}:{{ current }}%
+
+