From 16d14962833da8033501d18a624375af1e55c187 Mon Sep 17 00:00:00 2001 From: WangLeo <690854599@qq.com> Date: Fri, 5 Jun 2026 16:03:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Popover=20=E5=AE=BD?= =?UTF-8?q?=E5=BA=A6=E4=B8=8D=E7=A8=B3=E5=AE=9A=EF=BC=9A=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=AF=94=E4=BE=8B=E7=BB=84=E4=BB=B6=E7=A1=AC=E7=BC=96=E7=A0=81?= =?UTF-8?q?=E5=AE=BD=E5=BA=A6=EF=BC=8C=E6=94=B9=E7=94=A8=20min-width=20+?= =?UTF-8?q?=20ResizeObserver=20=E8=87=AA=E9=80=82=E5=BA=94=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Popover 新增 ResizeObserver 监听内容尺寸变化,自动重定位保持居中 - popover-content 补充 maxWidth/minWidth 约束,完善 width='auto' 模式 - 所有关闭路径(点击外部、关闭其他弹窗、modelValue watch)统一清理 observer - 移除 painting/video 比例组件的 Popover 硬编码宽度,改用 min-width: 300px - 修复 painting 分辨率选项非弹性布局导致的宽度抖动(white-space: nowrap) - 修复 painting W/H 输入框盒模型和 flex 收缩问题(box-sizing + min-width: 0) --- src/components/Popover/index.vue | 51 +++++++++++++++---- .../dialogBox/proportion/painting.vue | 12 +++-- src/components/dialogBox/proportion/video.vue | 3 +- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/src/components/Popover/index.vue b/src/components/Popover/index.vue index 9b53d34..648b354 100644 --- a/src/components/Popover/index.vue +++ b/src/components/Popover/index.vue @@ -47,20 +47,27 @@ const contentRef = ref(null) const visible = ref(props.modelValue) const position = ref({ top: 0, left: 0 }) const popoverId = ref(Math.random().toString(36).substr(2, 9)) +let resizeObserver = null if (!window.__currentOpenPopoverId__) { window.__currentOpenPopoverId__ = null } -const contentStyle = computed(() => ({ - width: typeof props.width === 'number' ? `${props.width}px` : props.width, - ...position.value -})) +const contentStyle = computed(() => { + const w = typeof props.width === 'number' ? `${props.width}px` : props.width + return { + ...position.value, + width: w, + maxWidth: w === 'auto' ? 'none' : w, + minWidth: w === 'auto' ? '0' : w + } +}) const togglePopover = async () => { if (visible.value) { visible.value = false window.__currentOpenPopoverId__ = null + stopResizeObserver() } else { if (window.__currentOpenPopoverId__ && window.__currentOpenPopoverId__ !== popoverId.value) { window.dispatchEvent(new CustomEvent('close-other-popovers', { detail: { excludeId: popoverId.value } })) @@ -72,20 +79,21 @@ const togglePopover = async () => { window.__currentOpenPopoverId__ = popoverId.value await nextTick() updatePosition() + startResizeObserver() } emit('update:modelValue', visible.value) } const updatePosition = () => { if (!triggerRef.value || !contentRef.value) return - + const triggerRect = triggerRef.value.getBoundingClientRect() const contentRect = contentRef.value.getBoundingClientRect() const gap = 25 - + let top = 0 let left = 0 - + switch (props.placement) { case 'top': top = triggerRect.top - contentRect.height - gap @@ -104,19 +112,35 @@ const updatePosition = () => { left = triggerRect.right + gap break } - + position.value = { top: `${top}px`, left: `${left}px` } } +const startResizeObserver = () => { + if (!contentRef.value) return + stopResizeObserver() + resizeObserver = new ResizeObserver(() => { + updatePosition() + }) + resizeObserver.observe(contentRef.value) +} + +const stopResizeObserver = () => { + if (resizeObserver) { + resizeObserver.disconnect() + resizeObserver = null + } +} + const handleClickOutside = (e) => { if (!visible.value) return - + const triggerEl = popoverRef.value const contentEl = contentRef.value - + if ( triggerEl && !triggerEl.contains(e.target) && @@ -125,6 +149,7 @@ const handleClickOutside = (e) => { ) { visible.value = false window.__currentOpenPopoverId__ = null + stopResizeObserver() emit('update:modelValue', false) } } @@ -132,12 +157,14 @@ const handleClickOutside = (e) => { const handleCloseOtherPopovers = (e) => { if (e.detail.excludeId !== popoverId.value) { visible.value = false + stopResizeObserver() } } const handleCloseOtherSelects = () => { visible.value = false window.__currentOpenPopoverId__ = null + stopResizeObserver() } watch(() => props.modelValue, async (val) => { @@ -145,6 +172,9 @@ watch(() => props.modelValue, async (val) => { if (val) { await nextTick() updatePosition() + startResizeObserver() + } else { + stopResizeObserver() } }) @@ -162,6 +192,7 @@ onBeforeUnmount(() => { window.removeEventListener('scroll', updatePosition, true) window.removeEventListener('close-other-popovers', handleCloseOtherPopovers) window.removeEventListener('close-other-selects', handleCloseOtherSelects) + stopResizeObserver() }) diff --git a/src/components/dialogBox/proportion/painting.vue b/src/components/dialogBox/proportion/painting.vue index bdd597e..5ff3441 100644 --- a/src/components/dialogBox/proportion/painting.vue +++ b/src/components/dialogBox/proportion/painting.vue @@ -1,5 +1,5 @@