From 5a3f484b8bfe6f35746f68ad163ed53b9a41af2f Mon Sep 17 00:00:00 2001 From: oneao Date: Mon, 8 Sep 2025 20:51:05 +0800 Subject: [PATCH] u --- .../packages/ao-design/src/popup/index.tsx | 183 ++++++++++-------- code/base-project/base-vue/src/App.vue | 38 ++-- 2 files changed, 124 insertions(+), 97 deletions(-) diff --git a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx index 3187cdd7..602d42c1 100644 --- a/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx +++ b/code/base-project/base-vue/packages/ao-design/src/popup/index.tsx @@ -1,18 +1,20 @@ import type { VNode } from 'vue' import { arrow, offset, useFloating } from '@floating-ui/vue' -import { cloneVNode, defineComponent, onMounted, ref, Teleport ,watch} from 'vue' +import { + cloneVNode, + defineComponent, + nextTick, + onMounted, + ref, + Teleport, + watch, +} from 'vue' import { getPrefixCls } from '../_utils/getPrefixCls' export default defineComponent({ name: 'APopup', props: { - /** - * 触发方式 - * - 'hover': 鼠标悬停显示/隐藏 - * - 'click': 点击切换显示/隐藏 - * - 'focus': 聚焦显示,失焦隐藏 - */ trigger: { type: String as () => 'hover' | 'click' | 'focus', default: 'hover', @@ -20,99 +22,124 @@ export default defineComponent({ }, setup(props, { slots }) { const prefixCls = getPrefixCls('popup') + const triggerRef = ref(null) const floatingRef = ref(null) const arrowRef = ref(null) const visible = ref(false) - // 使用 useFloating 管理浮动层和箭头 - const { floatingStyles, middlewareData } = useFloating( - triggerRef, - floatingRef, - { - placement: 'top', - middleware: [offset(10), arrow({ element: arrowRef })], - }, - ) + // 浮层样式和箭头数据 + const floatingStyles = ref({}) + const middlewareData = ref({ arrow: {} as any }) - // 监听 triggerRef 挂载完成 - onMounted(() => { - console.log('triggerRef 已挂载:', triggerRef.value) // 正确打印 - // 可以在此处调用 useFloating 的更新逻辑(如果有需要) - }) - watch(triggerRef, (newVal) => { - if (newVal !== null) { - console.log('triggerRef 已挂载:', newVal) // 正确打印 DOM 元素 - } - }, { immediate: true }) // 立即执行一次(可选) + let floatingInstance: ReturnType | null = null - // 显示/隐藏/切换浮动层 - const showPopup = () => (visible.value = true) - const hidePopup = () => (visible.value = false) - const togglePopup = () => (visible.value = !visible.value) + // 初始化 FloatingUI,确保触发节点 DOM 已挂载 + const initFloating = async () => { + if (!triggerRef.value || floatingInstance) + return + await nextTick() + const { x, y, strategy, middlewareData: md, update } = useFloating( + triggerRef, + floatingRef, + { + placement: 'top', + middleware: [offset(10), arrow({ element: arrowRef })], + }, + ) + floatingInstance = { x, y, strategy, middlewareData: md, update } + + // watch 可见性变化,更新位置 + watch( + visible, + (v) => { + if (v && floatingInstance?.update) + floatingInstance.update() + }, + ) + + // watch x/y 变化,实时更新样式 + watch([x, y, md], () => { + floatingStyles.value = { + position: strategy.value, + left: `${x.value ?? 0}px`, + top: `${y.value ?? 0}px`, + } + middlewareData.value = md.value + }, { immediate: true }) + } + + // popup 显隐 + const showPopup = () => { visible.value = true; initFloating() } + const hidePopup = () => { visible.value = false } + const togglePopup = () => { visible.value = !visible.value; initFloating() } - // 绑定事件 const eventProps: Record void> = {} if (props.trigger === 'hover') { - eventProps.onMouseenter = () => showPopup() - eventProps.onMouseleave = () => hidePopup() + eventProps.onMouseenter = showPopup + eventProps.onMouseleave = hidePopup } else if (props.trigger === 'click') { - eventProps.onClick = () => togglePopup() + eventProps.onClick = togglePopup } else if (props.trigger === 'focus') { - eventProps.onFocus = () => showPopup() - eventProps.onBlur = () => hidePopup() + eventProps.onFocus = showPopup + eventProps.onBlur = hidePopup } - // 获取 reference 插槽或默认插槽 - const slotContent = slots.reference?.() || slots.default?.() || [] - let triggerSlot: VNode | null = null + return () => { + const slotContent = slots.reference?.() || slots.default?.() || [] + let triggerSlot: VNode | null = null - // 单元素直接绑定事件,多元素套 span - if (slotContent.length === 1) { - triggerSlot = cloneVNode(slotContent[0], { - ref: triggerRef, - ...eventProps, - }) - } - else if (slotContent.length > 1) { - triggerSlot = ( - - {slotContent.map(vnode => cloneVNode(vnode, { ...eventProps }))} - - ) - } + if (slotContent.length === 1) { + const child = slotContent[0] + const isHoisted = child.patchFlag != null && child.patchFlag === -1 - return () => ( - <> - {/* 原位渲染触发元素 */} - {triggerSlot} + if (!isHoisted) { + triggerSlot = cloneVNode(child, { ref: triggerRef, ...eventProps }) + } + else { + triggerSlot = ( + + {slotContent.map(v => cloneVNode(v))} + + ) + } + } + else { + triggerSlot = ( + + {slotContent.map(v => cloneVNode(v))} + + ) + } - {/* 浮动层挂载到 body */} - - {visible.value && ( -
- Popup 内容 -
+ {triggerSlot} + + + {visible.value && ( +
+ Popup 内容 +
-
- )} - - - ) + }} + /> +
+ )} +
+ + ) + } }, }) diff --git a/code/base-project/base-vue/src/App.vue b/code/base-project/base-vue/src/App.vue index d37b46f7..78dd2916 100644 --- a/code/base-project/base-vue/src/App.vue +++ b/code/base-project/base-vue/src/App.vue @@ -1,22 +1,22 @@ - + - + - +