uniapp(vue3)在uni-app钉钉小程序安卓手机上一个页面点击按钮弹出一个popup组件,关闭这个组件,点击查询按钮这个popup组件被弹出来了,偶尔出现

uniapp(vue3)在uni-app钉钉小程序安卓手机上一个页面点击按钮弹出一个popup组件,关闭这个组件,点击查询按钮这个popup组件被弹出来了,偶尔出现

4 回复

知识库无法支撑。请补充HBuilderX版本、相关代码及详细报错信息,以便精准排查钉钉小程序安卓端弹窗异常问题。 欢迎到专业群( uni-app 官方技术交流群 1 ) 咨询,群中有相关专业的管理员和群友。

好的,收到这个 bug 反馈了。作为 DCloud 官方人员,我来对这个反馈进行评审。
首先,评价一下这个 bug 反馈的效果。
这个反馈整体质量不错,尤其是在问题描述部分,提供了非常关键的信息,值得肯定。

问题描述清晰且深入:反馈者没有停留在“弹窗自动弹出”这个表面现象,而是主动进行了排查和分析。他明确指出“钉钉原生没有这个问题”,并且测试了使用 v-if 自己写的弹出层也会复现,更重要的是,他指出了“点击查询之后又重新执行了点击按钮调接口的操作”。这非常关键,它将问题从“popup 组件异常”精准地定位到了“事件穿透/重复触发”这个更核心的机制上。这为问题的定位节省了大量时间。

环境信息提供了关键线索:他提到了“安卓手机”、“版本比较低的安卓系统出现次数多一些”,这为判断这是一个特定平台(Android钉钉小程序)和特定 WebView 内核版本的兼容性问题,提供了很有价值的线索。

其次,分析一下 bug 反馈内容的完整性,有几处需要补充:

缺少代码示例:这是最关键的缺失。反馈中提到了“打印留样标签”按钮和“查询”按钮,以及一个 popup 组件。但完全没有提供任何代码片段。官方人员需要看到:

这两个按钮是如何绑定事件的?
popup 组件是如何使用和关闭的(通过 ref 调用 close() 还是 v-model 控制)?
页面布局是怎样的?按钮和 popup 组件是否有层级或位置上的重叠? 没有代码,就无法判断是否存在事件冒泡、v-if/v-show 使用不当、或者 ref 引用错误等常见问题。

复现步骤不够具体:虽然逻辑清晰,但缺少具体的操作细节。例如,关闭 popup 组件后,是立即点击查询按钮,还是间隔了一段时间?复现是“偶尔出现”,那么大概的操作频率是多少?需要补充一个更精确的操作路径。

分类信息缺失版本号:只写了“HBuilderX版本 正式版”,没有提供具体的版本号,比如 4.87 或 5.07。也缺少钉钉客户端的版本和 Android 系统版本。这些对排查兼容性问题至关重要。

然后,判断这个 bug 是否成立。
这个 bug 是成立的,而且反馈者的分析很可能是对的。
这不是一个 uni-popup 组件本身的显示/隐藏 bug,而是一个事件处理机制的兼容性问题。反馈者描述的“点击查询之后又重新执行了点击按钮调接口的操作”是典型的“事件穿透”现象。
在小程序环境中,弹出层(popup)的关闭往往伴随着动画效果。在 Android 的特定 WebView 内核下,当 popup 的遮罩层和内容在关闭动画期间迅速消失时,用户紧接着点击下方“查询”按钮的触摸事件,可能会被错误地分发到消失前该位置下方的“打印留样标签”按钮上。低版本 Android 系统出现次数更多,也符合这个猜测,因为旧版 WebView 的事件处理机制可能更不完善。
从知识库的对话记录中,也能看到类似的问题模式。有用户反馈过 uni-popup 在微信小程序上,点击遮罩层关闭后重复几次,会导致页面按钮无法点击。这说明 popup 组件在小程序环境下的动画和事件处理,确实存在一些需要打磨的边界情况。
最后,提供一些分析和建议:

根本原因:这个问题的根源在于 UniApp 框架在将 Vue 的弹出层逻辑转换为小程序原生的 animation 和 hidden 等属性时,其动画时序与 Android 钉钉客户端 WebView 的事件分发机制在特定场景下产生了冲突,导致事件穿透。

解决方案建议:

事件防抖/节流:这是最直接有效的临时方案。为“打印留样标签”按钮的点击事件添加防抖处理(例如使用 lodash 的 debounce 或自己写一个简易的),限制它在短时间内(比如 300ms)只能被触发一次。这可以防止 popup 关闭动画期间的事件穿透。
延迟关闭与事件处理:在 popup 的关闭逻辑中,先隐藏内容,延迟一小段时间(比如 200ms)再彻底移除遮罩层,或者延迟执行查询按钮的逻辑。但这种方法可能会影响用户体验,让操作感觉不跟手。
使用 v-if 替代 v-show:如果 popup 是使用 v-show 控制,可以尝试改用 v-if 配合 nextTick。v-if 会销毁和重建 DOM 元素,能更彻底地避免事件穿透,但可能会带来性能开销和动画不流畅的问题。
完善代码示例并提交:建议反馈者按照上面的要求,补充一个最简化的、可运行的 demo 代码,并附上具体的 HBuilderX 版本、钉钉版本和 Android 系统版本,然后提交一个完整的 issue。这样官方人员可以更精确地复现和修复这个底层框架的兼容性问题。

                                                                                    内容为 AI 生成,仅供参考<br>

更多关于uniapp(vue3)在uni-app钉钉小程序安卓手机上一个页面点击按钮弹出一个popup组件,关闭这个组件,点击查询按钮这个popup组件被弹出来了,偶尔出现的实战教程也可以访问 https://www.itying.com/category-93-b0.html


<template> <view> <view class="commonLim-list"> <view class="searchclass"> <view class="left"> <up-search v-model="query.keyWord" placeholder="随工单编号或批次号" clearabled="true" showAction="false" @search="handleSearch" /> <view v-show="isshow1" class="left">
<up-search v-model="query.materialTypeName" placeholder="请选择材质" clearabled="true" showAction="false" inputStyle="{ 'pointer-events': 'none' }" />
<up-search v-model="query.specificationName" placeholder="请选择规格型号" clearabled="true" showAction="false" inputStyle="{ 'pointer-events': 'none' }" />
<MachineAndFurnaceNumber query="query" @updateMachineFurnace="handleSearch" ref="MachineAndFurnaceRef" /> <PatetimePicker query="query" @updateFn="handleSearch" titleName="分卷操作" startTime="cuttingTimeStart" endTime="cuttingTimeEnd" /> </view> </view> <view class="right"> <up-button type="primary" @click="handleSearch" size="small" shape="circle" class="first" >搜索</up-button > <up-button type="primary" @click="handlecancel" size="small" shape="circle" >重置</up-button > </view> <scanCodeList :resultPakage="resultPakage" /> </view> <view class="upCcon"> <up-icon name="iconName" size="50rpx" color="#3c9cff" @click="handleicon" ></up-icon> </view> <scroll-view class="commonLim-listView" scroll-y @scrolltolower="onScrollToBottom" > <view v-show="state.rows.length"> <view v-for="(item, index) in state.rows" key="item.id" class="commonLim-listView-item" > <view class="commonLim-listView-item-title"> <label>{{ item.batchNo }}</label> <text class="mgr40">工序:{{ item.processName }}</text> <text>序号:{{ index + 1 }}</text> </view> <view class="commonLim-listView-item-content"> <view class="half width100"> <label>随工单编号:</label> <text>{{ item.trackingNo }}</text> <label v-if="item.isCancellation == 1" style="color: #f56c6c" >(作废)</label > </view> <view class="half width100"> <label>分卷状态:</label> <text>{{ reelScomtatus(item.reelStatus) }}</text> </view> <view class="half width100"> <label>物料编码:</label> <text>{{ item.materialCode }}</text> </view> <view class="half width100"> <label>材质:</label> <text>{{ item.materialTypeName }}</text> </view> <view class="half width100"> <label>规格型号:</label> <text>{{ item.specificationName }}</text> </view> <view class="half width100"> <label>炉号:</label> <text>{{ item.heatNo }}</text> </view> <view class="half width100"> <label>机台编号:</label> <text>{{ item.machineNo }}</text> </view> <view class="half width100"> <label>炉管号:</label> <text>{{ item.furnaceTubeNo }}</text> </view> <view class="half width100"> <label>分轮编号:</label> <text>{{ item.roundNo }}</text> </view> <view class="half width100"> <label>分轮重量:</label> <text>{{ item.roundWeight }}</text> </view> <view class="half width100"> <label>内部编号:</label> <text>{{ item.sampleNo }}</text> </view> <view class="half width100" style="color: #f56c6c" v-if="item.isCancellation == 1" > <label>作废原因:</label> <text>{{ item.reason }}</text> </view> <view class="half width100"> <label>分卷人:</label> <text>{{ item.cuttingBy }}</text> </view> <view class="half width100"> <label>分卷操作日期:</label> <text>{{ item.cuttingTime }}</text> </view> <view class="half width100"> <label>质检人员:</label> <text>{{ item.inspectionBy }}</text> </view> <view class="half width100"> <label>质检时间:</label> <text>{{ item.inspectionTime }}</text> </view> <view class="half width100"> <label>称重人:</label> <text>{{ item.weighingBy }}</text> </view> <view class="half width100"> <label>称重日期:</label> <text>{{ item.weighingTime }}</text> </view> <view class="half width100"> <label>入库人:</label> <text>{{ item.stockBy }}</text> </view> <view class="half width100"> <label>入库日期:</label> <text>{{ item.stockCompleteTime }}</text> </view> </view> <view class="commonLim-listView-item-button"> <view class="box"> <up-button type="primary" text="打印留样标签" size="small" @click="handleprint2(item)" customStyle="{ padding: '0 30rpx' }" ></up-button> <up-button type="error" text="异常查看" size="small" @click="handleLook(item)" customStyle="{ padding: '0 30rpx' }" ></up-button> </view> </view> </view> </view> <up-empty mode="data" v-show="state.rows.length == 0"> </up-empty> <up-loadmore v-show="state.rows.length" status="loadStatus" icon="true" loadmore-text="轻轻上拉获取更多" loading-text="努力加载中..." nomore-text="没有更多了" @loadmore="handleloadMore" /> </scroll-view> <printComponent imgSrc="imgSrc" showReject="showchenpinReject" @closeFn="showchenpinReject = false" titleName="查看二维码" /> </view> <up-popup show="abnormalShow" mode="bottom" closeOnClickOverlay overlayOpacity="1" [@close](/user/close)="abnormalShow = false" > <view style=" text-align: center; color: #f56c6c; margin-top: 20rpx; font-size: 36rpx; " >异常信息</view > <view class="boxTables" style="padding: 30rpx; border-radius: 30rpx"> <view class="left"> <view class="common">操作人:</view> <view class="common"> {{ abnormalObject.operatorName }} </view> </view> <view class="left"> <view class="common">操作日期:</view> <view class="common"> {{ abnormalObject.operationDate }} </view> </view> <view class="left"> <view class="common">异常原因:</view> <view class="common"> {{ abnormalObject.reason }} </view> </view> <view class="left"> <view class="common">异常审批人:</view> <view class="common"> {{ abnormalObject.approverName }} </view> </view> <view class="left"> <view class="common">异常操作日期:</view> <view class="common" style="border-bottom: 2rpx solid #ccc">{{ abnormalObject.operationDate }}</view> </view> </view> </up-popup> </view> </template> <script setup> import { FinishedProductLabel } from "../../commonApi/index.js"; import { pagedynamicdate } from "./api.js"; import { useMemberStore } from "@/stores/modules/member"; import { baseURL, reelLists } from "@/utils/index.js"; const memberStore = useMemberStore(); const MachineAndFurnaceRef = ref(null); const loadStatus = ref("loadmore"); const iconName = ref("arrow-down"); const showchenpinReject = ref(false); const abnormalShow = ref(false); const abnormalObject = ref({}); const imgSrc = ref([]); const isshow1 = computed(() => { if (iconName.value == "arrow-down") { return false; } else { return true; } }); const reelScomtatus = computed(() => { return (reelStatus) => { return reelLists[reelStatus]; }; }); const handleicon = () => { if (iconName.value == "arrow-down") { iconName.value = "arrow-up"; } else { iconName.value = "arrow-down"; } }; const Initialriqi = new Date().toISOString().split("T")[0]; const query = ref({ page: 0, size: 10, operationDateStart: Initialriqi, operationDateEnd: Initialriqi, cuttingTimeStart: Initialriqi, cuttingTimeEnd: Initialriqi }); const state = reactive({ rows: [], totals: 0 }); const loadData = async () => { const res = await pagedynamicdate(query.value); state.rows = [...state.rows, ...res.content]; state.totals = res.totalElements; if (state.rows.length < state.totals) { loadStatus.value = "loadmore"; // 仍可继续上拉 } else { loadStatus.value = "nomore"; // 已全部加载 } }; const Consistentlogic = async () => { try { uni.showLoading({ title: "加载中" }); state.rows = []; state.totals = 0; await loadData(); } finally { uni.hideLoading(); } }; const handleSearch = () => { query.value.page = 0; Consistentlogic(); }; const handlecancel = () => { query.value = { page: 0, size: 10 }; MachineAndFurnaceRef.value.luguanhaoiList = []; Consistentlogic(); }; const handleloadMore = async () => { // 防止重复请求 if (loadStatus.value === "loading") return; loadStatus.value = "loading"; // 判断是否还有更多数据 if (state.rows.length < state.totals) { loadStatus.value = "loadmore"; // 仍可继续上拉 query.value.page += 1; // 为下一次请求准备页码 try { await loadData(); } catch (error) { console.log("加载数据失败", error); } } else { loadStatus.value = "nomore"; // 已全部加载 } }; function onScrollToBottom() { // 当 scroll-view 触底且状态为 loadmore 时手动调用 if (loadStatus.value === "loadmore") { handleloadMore(); } } //扫码检索需要执行的函数 const resultPakage = (value) => { query.value.keyWord = value; handleSearch(); }; const handleprint2 = async (item) => { try { imgSrc.value = null; uni.showLoading({ title: "加载中" }); const res = await FinishedProductLabel(item.id, 2); showchenpinReject.value = true; uni.hideLoading(); if (res) { imgSrc.value = res.map( (url) => ${baseURL}/zgjmAPI/file/preview?filePath=${encodeURIComponent(url)} ); } } catch (err) { uni.hideLoading(); } }; const handlefocus1 = () => { uni.navigateTo({ url: /pages/searchcaizhi/searchcaizhi?from=caizhi }); }; const handlefocus2 = async () => { if (query.value.materialTypeId) { uni.navigateTo({ url: /pages/searchcaizhi/searchcaizhi?from=guigexinghao&pId=${query.value.materialTypeId} }); } else { uni.showToast({ icon: "none", title: "请选择材质" }); } }; const handleLook = (item) => { abnormalObject.value = item; abnormalShow.value = true; }; onLoad(() => { handleSearch(); }); onShow(() => { if (memberStore.from_Page == "caizhi") { query.value.materialTypeId = memberStore.zemId; query.value.materialTypeName = memberStore.zemName; query.value.specificationId = null; query.value.specificationName = null; handleSearch(); } else if (memberStore.from_Page == "guigexinghao") { query.value.specificationId = memberStore.zemId; query.value.specificationName = memberStore.zemName; handleSearch(); } memberStore.zemId = ""; memberStore.zemName = ""; memberStore.from_Page = ""; }); </script> <style lang="scss" scoped> .boxTables { .left { .common:nth-of-type(1) { background: #f56c6c; } } } </style>

HBuilderX 版本:5.05、钉钉版本:3.9.22.0和 Android 系统版本:7.1.2

这个问题影响很大,一个页面有搜索条件,有查询按钮,有列表,列表中有一些按钮,比如删除,编辑,设置都很正常。生产环境我们当时有一个剪线按钮,客户需要多次点击剪线按钮,每次点击剪线就会调后端接口,安卓手机上点击多次剪线之后,在点击查询按钮调接口获取第一页数据,然后就出现第一条数据的剪线按钮就被点击了,这个很致命,这种页面功能是很普遍,影响太大了,请官方人员解决下这个问题,谢谢

回到顶部