mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-06-20 15:19:41 +08:00
fix(dashboard): stabilize editable layout controls
This commit is contained in:
@@ -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<Record<string, DashboardGridLayoutItem>>({})
|
||||
|
||||
// 是否刚恢复过默认布局,用于避免退出编辑时立即把默认布局写回本地覆盖。
|
||||
const isDashboardGridLayoutResetPending = ref(false)
|
||||
|
||||
const dashboardGridResizeStartHeights = new Map<string, number | undefined>()
|
||||
const dashboardGridPendingContentResize = new Set<GridItemHTMLElement>()
|
||||
const dashboardGridObservedContentHeights = new Map<string, number>()
|
||||
|
||||
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<DynamicButtonMenuItem[] | undefined>(() => {
|
||||
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"
|
||||
/>
|
||||
<VFab
|
||||
v-if="isLayoutEditing"
|
||||
icon="mdi-restore"
|
||||
color="warning"
|
||||
variant="tonal"
|
||||
appear
|
||||
class="compact-fab compact-fab--secondary"
|
||||
@click="resetDashboardGridLayout"
|
||||
/>
|
||||
<VFab
|
||||
:icon="isLayoutEditing ? 'mdi-check' : 'mdi-view-dashboard-edit'"
|
||||
color="primary"
|
||||
|
||||
Reference in New Issue
Block a user