关于uni.createSelectorQuery().select(el).boundingClientRect 自定义组件获取不到值

本文介绍了解决在uni-app自定义子组件中使用uni.createSelectorQuery().in(this)来正确获取元素信息的方法,避免因直接使用uni.createSelectorQuery()导致的数据为空问题。
uni.createSelectorQuery().select("#scrollview").boundingClientRect(data => {
  console.log(data) // null
}).exec()

在自定义的子组件中,data为null,使用this.$nextTick,setTimeout仍然获取不到值

解决方案:
要用 in(this),this传入的是自定义组件的实例,否则获取到的data值为null
uni.createSelectorQuery().in(this).select(“#scrollview”).boundingClientRect

<!-- components/zy-popup-dropdown-menu.vue --> <template> <view> <!-- 触发元素插槽:支持自动绑定 open 方法 --> <slot name="reference" :open="open" :close="close" :toggle="toggle"> <!-- 默认触发器(可删除) --> <view v-if="!$slots.reference" data-reference @tap="open"> 点击选择 ▼ </view> </slot> <!-- 浮动下拉菜单 --> <view v-if="visible" ref="dropdownRef" class="zy-popup-dropdown-menu" :style="{ top: `${top}px`, left: `${left}px`, transform: position }" @touchmove.stop > <view class="dropdown-content"> <view v-for="item in props.data" :key="item[props.valueKey]" class="dropdown-item" :class="{ 'is-selected': props.modelValue === item[props.valueKey] }" @tap="handleSelect(item[props.valueKey])" > <text class="item-label">{{ item[props.labelKey] }}</text> <text v-if="props.modelValue === item[props.valueKey]" class="icon-check"></text> </view> </view> </view> </view> </template> <script setup> import { ref, nextTick, onUnmounted } from 'vue' // ---------------------- // Props // ---------------------- const props = defineProps({ data: { type: Array, required: true }, modelValue: { type: [String, Number, null], default: null }, labelKey: { type: String, default: 'label' }, valueKey: { type: String, default: 'value' }, // 接收外部传入的 ref referenceRef: { type: Object, default: null } }) // ---------------------- // Emits // ---------------------- const emit = defineEmits(['update:modelValue', 'change', 'open', 'close']) // ---------------------- // 内部状态 // ---------------------- const visible = ref(false) const top = ref(0) const left = ref(0) const position = ref('translateY(8px)') const dropdownRef = ref(null) const referenceRect = ref(null) // 缓存触发器位置 let observer = null // ---------------------- // 方法定义 // ---------------------- // 打开下拉框 const open = async () => { if (visible.value) return let rect = null console.log('🚀 ~ open ~ props.referenceRef:', props.referenceRef) console.log('🚀 ~ open ~ props.referenceRef.$el:', props.referenceRef.$el) console.log('🚀 ~ open ~ props.referenceRef.el:', props.referenceRef.el) if (props.referenceRef && props.referenceRef.$el) { // Vue 实例有 $el(H5 平台) rect = await getRectFromElement(props.referenceRef.$el) } else if (props.referenceRef?.el) { // uni-app 中 ref.el 是真实节点(App/小程序) rect = await getRectFromElement(props.referenceRef.el) } else { console.warn('[zy-popup-dropdown-menu] 未提供有效的 referenceRef') return } const res = await new Promise(resolve => { uni.createSelectorQuery().selectViewport().scrollOffset().exec(resolve) }) const scroll = res[0] const windowHeight = scroll.windowHeight || scroll.height const maxHeight = 486 const needUpward = windowHeight - rect.bottom < maxHeight left.value = rect.left top.value = needUpward ? rect.top - maxHeight - 8 : rect.bottom + 8 position.value = needUpward ? 'translateY(-8px)' : 'translateY(8px)' visible.value = true emit('open', props.modelValue) nextTick(() => { bindOutsideClickListener() bindScrollListener() }) } // 获取 DOM 元素 boundingClientRect function getRectFromElement(el) { return new Promise(resolve => { if (!el) return resolve(null) const query = uni.createSelectorQuery() query.select(`#${el.id}`).boundingClientRect() if (!el.id) { // 如果没有 id,尝试用临时 id el.id = 'temp-dropdown-trigger-' + Date.now() query.select(`#${el.id}`).boundingClientRect() setTimeout(() => { el.id = '' }, 100) // 清理 } query.exec(res => { resolve(res[0]) }) }) } // 关闭 const close = () => { if (!visible.value) return visible.value = false emit('close') removeListeners() } // 切换 const toggle = () => { visible.value ? close() : open() } // 选择项 const handleSelect = value => { if (props.modelValue !== value) { emit('update:modelValue', value) emit('change', value) } close() } // 外部点击关闭 const bindOutsideClickListener = () => { const handler = () => close() uni.$on('onPageTap', handler) observer = { ...observer, cleanupTap: () => uni.$off('onPageTap', handler) } } // 滚动关闭 const bindScrollListener = () => { const scrollHandler = () => close() uni.$on('onPageScroll', scrollHandler) observer = { ...observer, cleanupScroll: () => uni.$off('onPageScroll', scrollHandler) } } // 移除监听 const removeListeners = () => { observer?.cleanupTap?.() observer?.cleanupScroll?.() observer = null } // ---------------------- // 兼容性处理:thisContext 模拟 this // 因为 script setup 中没有 this,我们需要一个上下文对象 // 注意:HBuilderX 编译时会将组件实例注入到页面作用域 // 我们可以通过 getCurrentInstance 获取当前实例 uid,用于 in() import { getCurrentInstance } from 'vue' const instance = getCurrentInstance() const thisContext = instance.ctx || instance.proxy // ---------------------- // 暴露方法 // ---------------------- defineExpose({ open, close, toggle }) // ---------------------- // 卸载清理 // ---------------------- onUnmounted(() => { removeListeners() }) </script> <style scoped> /* 注意:给插槽外层加一个标记类名,便于查询 */ /* Vue 不直接支持 slot 元素样式,但我们可以在父级包裹或约定结构 */ /* 这里假设用户使用的元素会被包裹在一个具有特定结构的容器中 */ .zy-popup-dropdown-menu { position: fixed; width: 215px; max-height: 486px; background-color: #ffffff; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); z-index: 9999; overflow: hidden; } .dropdown-content { height: 100%; max-height: 486px; overflow-y: auto; -webkit-overflow-scrolling: touch; } .dropdown-item { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; font-size: 15px; color: #333333; border-bottom: 1px solid #f5f5f5; } .dropdown-item:last-child { border-bottom: none; } .dropdown-item.is-selected { color: #0f56d5; font-weight: 500; } .item-label { flex: 1; word-break: break-word; line-height: 1.4; text-align: left; } .icon-check { font-family: 'erda' !important; font-size: 16px; margin-left: 8px; color: #0f56d5; } </style> open ~ props.referenceRef: null
最新发布
10-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值