修复 Popover 宽度不稳定:移除比例组件硬编码宽度,改用 min-width + ResizeObserver 自适应内容
- 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)
This commit is contained in:
parent
f0008aedde
commit
16d1496283
@ -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()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Popover placement="top" :width="400">
|
||||
<Popover placement="top">
|
||||
<div class="proportion-container">
|
||||
<div class="section">
|
||||
<h3>选择比例</h3>
|
||||
@ -248,6 +248,7 @@ watch(() => [props.modelValue, props.resolution], () => {
|
||||
|
||||
.proportion-container{
|
||||
padding: 20px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.section{
|
||||
@ -269,7 +270,7 @@ watch(() => [props.modelValue, props.resolution], () => {
|
||||
.proportion-options{
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
margin-bottom: 16px;
|
||||
background-color: #F8F9FA;
|
||||
padding: 5px;
|
||||
@ -325,14 +326,13 @@ watch(() => [props.modelValue, props.resolution], () => {
|
||||
}
|
||||
|
||||
.resolution-item{
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
// background: #f5f5f5;
|
||||
white-space: nowrap;
|
||||
color: #666;
|
||||
|
||||
&:hover{
|
||||
@ -354,6 +354,7 @@ watch(() => [props.modelValue, props.resolution], () => {
|
||||
|
||||
.input-group{
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
|
||||
label{
|
||||
@ -367,6 +368,7 @@ watch(() => [props.modelValue, props.resolution], () => {
|
||||
}
|
||||
|
||||
input{
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 12px 12px 12px 30px;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Popover placement="top" :width="400">
|
||||
<Popover placement="top">
|
||||
<div class="proportion-container">
|
||||
<div class="section">
|
||||
<h3>选择比例</h3>
|
||||
@ -142,6 +142,7 @@ const getProportionStyle = (value) => {
|
||||
|
||||
.proportion-container{
|
||||
padding: 20px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.section{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user