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(() => { - - 无法渲染插件仪表盘部件: 未知渲染模式或配置错误 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" /> +