From 3f525c64572af0403c08663314173195b5a240b3 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Tue, 30 Jun 2026 14:17:04 +0800 Subject: [PATCH] fix: adjust dialog fullscreen breakpoint by width --- src/components/dialog/AppDialog.ts | 89 ++++++++++++++++++++++++++++++ src/plugins/vuetify/index.ts | 2 + 2 files changed, 91 insertions(+) create mode 100644 src/components/dialog/AppDialog.ts diff --git a/src/components/dialog/AppDialog.ts b/src/components/dialog/AppDialog.ts new file mode 100644 index 00000000..5d7dd6fc --- /dev/null +++ b/src/components/dialog/AppDialog.ts @@ -0,0 +1,89 @@ +import { defineComponent, h } from 'vue' +import { useDisplay } from 'vuetify' +import { VDialog as VuetifyDialog } from 'vuetify/components/VDialog' + +const DEFAULT_ROOT_FONT_SIZE = 16 + +/** + * 判断传入的 fullscreen 属性是否已请求全屏展示。 + */ +function isFullscreenRequested(value: unknown) { + return value !== undefined && value !== null && value !== false +} + +/** + * 获取文档根字号,用于把 rem/em 宽度限制换算成像素。 + */ +function getRootFontSize() { + if (typeof window === 'undefined') return DEFAULT_ROOT_FONT_SIZE + + const rootFontSize = Number.parseFloat(window.getComputedStyle(document.documentElement).fontSize) + return Number.isFinite(rootFontSize) ? rootFontSize : DEFAULT_ROOT_FONT_SIZE +} + +/** + * 将弹窗宽度限制换算成像素,无法可靠解析的复杂表达式返回空值。 + */ +function parseDialogWidthLimit(value: unknown, viewportWidth: number) { + if (typeof value === 'number') return Number.isFinite(value) ? value : undefined + if (typeof value !== 'string') return undefined + + const trimmedValue = value.trim() + if (!trimmedValue) return undefined + + const numericValue = Number(trimmedValue) + if (Number.isFinite(numericValue)) return numericValue + + const lengthMatch = trimmedValue.match(/^(-?\d*\.?\d+)(px|rem|em|vw|%)$/i) + if (!lengthMatch) return undefined + + const [, rawAmount, rawUnit] = lengthMatch + const amount = Number(rawAmount) + if (!Number.isFinite(amount)) return undefined + + const unit = rawUnit.toLowerCase() + if (unit === 'px') return amount + if (unit === 'rem' || unit === 'em') return amount * getRootFontSize() + if (unit === 'vw') return (viewportWidth * amount) / 100 + if (unit === '%') return (viewportWidth * amount) / 100 + + return undefined +} + +/** + * 根据 maxWidth 与 width 的有效上限,收窄 fullscreen 的生效宽度。 + */ +function resolveFullscreen( + fullscreen: unknown, + maxWidth: unknown, + width: unknown, + viewportWidth: number, +) { + if (!isFullscreenRequested(fullscreen)) return false + + const widthLimits = [maxWidth, width] + .map(value => parseDialogWidthLimit(value, viewportWidth)) + .filter((value): value is number => value !== undefined) + + if (!widthLimits.length) return true + + return viewportWidth <= Math.min(...widthLimits) +} + +export default defineComponent({ + name: 'AppDialog', + inheritAttrs: false, + /** + * 渲染项目统一弹窗,并在宽度上限小于移动全屏断点时收窄 fullscreen 条件。 + */ + setup(_, { attrs, slots }) { + const display = useDisplay() + + return () => { + const maxWidth = attrs.maxWidth ?? attrs['max-width'] + const resolvedFullscreen = resolveFullscreen(attrs.fullscreen, maxWidth, attrs.width, display.width.value) + + return h(VuetifyDialog, { ...attrs, fullscreen: resolvedFullscreen }, slots) + } + }, +}) diff --git a/src/plugins/vuetify/index.ts b/src/plugins/vuetify/index.ts index a54176a8..0b21c0d3 100644 --- a/src/plugins/vuetify/index.ts +++ b/src/plugins/vuetify/index.ts @@ -2,6 +2,7 @@ import { createVuetify } from 'vuetify' import * as components from 'vuetify/components' import { VBtn } from 'vuetify/components/VBtn' import * as labsComponents from 'vuetify/labs/components' +import AppDialog from '@/components/dialog/AppDialog' import defaults from './defaults' import { icons } from './icons' import theme from './theme' @@ -15,6 +16,7 @@ export default createVuetify({ theme, components: { ...components, + VDialog: AppDialog, ...labsComponents, }, })