微信小程序 errno 112 排查与隐私接口全局单例化治理(2026-08-31)

做「梦想城镇」这款 Taro 微信小程序(Taro 4 + Vue 3 + 微信云开发)时,我在详情页点「复制好友码」,控制台突然甩出一句:setClipboardData:fail api scope is not declared in the privacy agreement,errno 112。同样的逻辑在首页点复制是正常的,一进详情页就炸。

这类问题特别适合写成一次完整的工程复盘,因为它背后是一类前端都该有的判断:平台副作用要按正确的作用域去管理。

问题背景与排查路径

setClipboardData 是微信隐私接口,调用前需要通过 wx.onNeedPrivacyAuthorize 注册监听,在回调里引导用户授权。原代码只在 index.vue 里注册了这个监听,并且为了「干净」在 onUnmounted 中调了 wx.offNeedPrivacyAuthorize() 注销。

从列表页导航到详情页时,index.vue 被卸载,监听随之被清除。详情页的复制动作没有了监听去拦截授权,于是直接 fail,报出 errno 112。

我一开始也以为只是后台漏声明了。但首页调用同样接口正常,说明不是声明问题,而是「监听在详情页不存在」。问题定位到生命周期:隐私监听被错误地当成了页面级副作用,而剪贴板、头像这类接口是典型的跨页面共享的全局副作用。

技术方案:全局单例化

改造思路很直接——把隐私监听提升为全局单例,在 App 入口注册一次,永不再注销:

  • 新增 src/privacy.js,用一个基于 Vue reactiveprivacyState 保管授权状态,对外暴露 initGlobalPrivacy / handlePrivacyAgree / handlePrivacyDisagree / openPrivacyContract
  • 新增 src/components/PrivacyModal.vue 作为共享弹窗。同意按钮使用 open-type="agreePrivacyAuthorization",监听 @agreeprivacyauthorization 后调用 resolve({event:'agree'});微信收到授权后会自动重发最初被拦截的那个隐私调用,业务代码无需感知。
  • src/app.jssetup() 中调用一次 initGlobalPrivacy(),并且刻意不注销
  • 原来 index.vue / detail.vue 里那套本地隐私代码全部删除,统一改为渲染 <PrivacyModal />。业务层 copyCode 仍直连 Taro.setClipboardData,由全局监听接管授权,无需任何改动。

这里有一个代码侧改不掉的前提:必须去微信公众平台「设置 → 用户隐私保护指引」把「剪贴板(收集你选中的内容)」和「微信头像/昵称」两个接口用途声明上,前端弹窗和后台声明两者都到位才真正生效。我已把它写进代码注释,避免下次再踩。

顺带治理的同类平台坑

做完隐私改造,同一天我把几处 Taro 4.2 / 微信小程序的「组件受控性与布局裁剪」坑也收拾了,它们本质同源:

  • <input> 在 Taro 4.2 中是非受控组件,留言发送成功后 commentText.value='' 已执行,但原生输入框没回写清空。最终用 :key="commentKey",在成功后再 commentKey.value++ 强制重挂载节点解决。
  • 发布面板的描述框由单行 <input> 改为 <textarea>,绑定采用显式 :value + @input 而非 v-model,规避 Taro 虚拟节点回写异常。
  • 首页底部按钮被上方 scroll-view 溢出覆盖。根因是 flex column 中 scroll-viewflex:1; min-height:0 不足以约束高度,改用 .grid-scroll { height: 0 }flex:1 这个经典 hack 强制其真正滚动裁剪,按钮加 flex-shrink: 0
  • 已知 Taro 4.2 的 scroll-view / input / textarea 为 stencil web 组件,小程序端存在 getRootNode is not a function 崩溃风险,触底加载应交给页面级 useReachBottom

方法论沉淀

这次改造留下的不是一个 fix,而是一条可复用的工程准则:跨页面共享的平台能力(隐私监听、剪贴板、头像、全局事件、第三方 SDK 初始化)应在 App 入口以单例统一注册,避免页面级注册 + 卸载注销带来的作用域遗漏。原生组件回显异常时,优先用 :key 强制重挂载,而不是依赖双向绑定的回写。

这套判断比具体改几行代码值钱。如果你也在做微信小程序,遇到 errno 112 或原生组件回显异常,欢迎在评论区聊聊你的处理方式。