From 3fffab81c87d3596d05b0b6c18bb4724492e7f48 Mon Sep 17 00:00:00 2001 From: jing <1683105490@qq.com> Date: Sat, 12 Sep 2026 11:03:34 +0800 Subject: [PATCH] =?UTF-8?q?chore:=E5=8F=AF=E4=BB=A5=E5=A4=8D=E5=88=B6?= =?UTF-8?q?=E5=9B=9E=E7=AD=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- common/js/uniapp.utils.js | 24 ++++++++++++++++- docs/weixin-bug.md | 36 +++++++++++++++++++++++++- lang/en-us/ai-chat/index.js | 3 ++- lang/en-us/common.js | 2 ++ lang/zh-cn/ai-chat/index.js | 3 ++- lang/zh-cn/common.js | 2 ++ manifest.json | 2 +- pages_tool/ai-chat/ai-chat-message.vue | 8 ++++-- 8 files changed, 73 insertions(+), 7 deletions(-) diff --git a/common/js/uniapp.utils.js b/common/js/uniapp.utils.js index 045e801..5344c44 100644 --- a/common/js/uniapp.utils.js +++ b/common/js/uniapp.utils.js @@ -5,6 +5,24 @@ import lang from './lang.js'; +/** + * 判断是否为「微信剪贴板隐私声明缺失」导致的复制失败 + * + * 微信自 2023-09-15 起把剪贴板接口纳入《小程序用户隐私保护指引》, + * 若小程序后台未声明「剪贴板」(或声明未审核通过),调用会返回: + * { errno: 112, errMsg: 'setClipboardData:fail api scope is not declared in the privacy agreement' } + * 这是平台侧配置问题,不改代码也能复现,因此不能把原始英文 errMsg 直接展示给用户。 + * + * @param {*} err uni.setClipboardData 的 fail 回调参数 + * @returns {Boolean} true=剪贴板隐私声明缺失 + */ +export const isClipboardPrivacyError = (err) => { + if (!err) return false; + if (Number(err.errno) === 112) return true; + const msg = `${err.errMsg || err.message || ''}`; + return /privacy agreement/i.test(msg) || /api scope is not declared/i.test(msg); +}; + /** * 显示错误信息 * @param {Exception} err @@ -97,8 +115,12 @@ export const copyTextAsync = (text, { copySuccess = '', copyFailed = '' } = {}) fail: (err) => { clearTimeout(timeoutId); try { + // 剪贴板隐私声明缺失(errno 112)属平台配置问题, 用可读文案替代微信原始英文错误 + const tip = isClipboardPrivacyError(err) + ? lang.lang('uniappUtils.clipboardPrivacyNotDeclared') + : (err.message || err.errMsg || copyFailed || lang.lang('uniappUtils.copyFailed')); uni.showToast({ - title: err.message || err.errMsg || copyFailed || lang.lang('uniappUtils.copyFailed'), + title: tip, icon: 'none', duration: 2000 }); diff --git a/docs/weixin-bug.md b/docs/weixin-bug.md index 064f809..57c1457 100644 --- a/docs/weixin-bug.md +++ b/docs/weixin-bug.md @@ -105,4 +105,38 @@ components/mescroll/components/mescroll-empty.json 有些"修模拟器表现"的尝试(@load 多次重试 setNavigationBarTitle、加看门狗定时器)会让真机代码变得复杂、可能和内嵌页 `document.title` 抢控制权、引入边界 case。 -**结论**:遇到 navigationBar / web-view 相关问题,先在真机上复现确认;只在真机还在问题再继续查代码。 \ No newline at end of file +**结论**:遇到 navigationBar / web-view 相关问题,先在真机上复现确认;只在真机还在问题再继续查代码。 + +#### 4. `setClipboardData:fail api scope is not declared in the privacy agreement` + +**症状**:AI 智能(`pages_tool/ai-chat`)复制消息时 Toast 提示 +`复制失败:setClipboardData:fail api scope is not declared in the privacy agreement`, +H5 正常,**只有微信小程序失败**。 + +**根因**:微信自 2023-09-15 起把剪贴板接口(`setClipboardData` / `getClipboardData`)纳入 +《小程序用户隐私保护指引》。小程序后台未声明「剪贴板」,或声明还在审核中时,调用直接失败, +返回 `errno: 112`。这是**平台侧配置问题,不是代码问题**: +`app.json` 的 `permission` / `requiredPrivateInfos` 都不涉及剪贴板,改代码无法绕过。 + +**正确处理**: +- 微信公众平台 → 设置 → 基本设置 → 服务内容声明 → 用户隐私保护指引 → 更新 → + 在「开发者收集你的……信息」中勾选「剪贴板」并填写用途 → 提交审核; + **审核通过后才生效**,通过后重新编译/发版即恢复。 +- 代码侧只做兜底:`common/js/uniapp.utils.js` 的 `isClipboardPrivacyError()` 识别 + `errno === 112` / errMsg 含 `privacy agreement`,用 i18n 文案替换微信原始英文错误 + (AI 聊天页 `copyMessage` 同样处理),不要把平台英文报错抛给用户。 +- 开发期想临时绕过校验:把 `manifest.json` → `mp-weixin` → `"__usePrivacyCheck__"` 改成 `false` + (**仅限本地调试**,上线前必须还原,并按上面的后台配置完成声明)。 + +**注意**:根目录的 `privacy.json`(内容为 `Scope.writeClipboard`)不是微信的隐私声明机制, +未被任何代码或构建流程引用,不要把它当成剪贴板隐私的解决方案。 + +**代码侧备选(无法改后台、只想先跑起来时)**: +- 写剪贴板在微信里只有 `wx.setClipboardData` 这一个 JS API,平台拦截 errno 112 时**任何代码都绕不过**, + 所以「复制」按钮在没声明剪贴板权限前必然失败。 +- 真正不依赖声明的复制方式是**原生长按选区复制**:本组件 AI 文本用 `` 渲染 + (`pages_tool/ai-chat/ai-chat-message.vue` 的 `type==='text'` / `'markdown'` 分支), + 用户**直接长按 AI 回复文字**→ 系统弹出「复制」即可,不调用 `setClipboardData`,不受隐私协议限制。 +- 开发期想让「复制」按钮也能用:把 `manifest.json` → `mp-weixin` → `"__usePrivacyCheck__"` 改成 `false` + (仅本地调试,上线前必须还原;部分基础库下生效,新基础库可能仍报 112,此时用上面的长按方式)。 +- 失败时提示已引导用户「长按消息文字手动复制」(`isClipboardPrivacyError()` 识别 errno 112)。 diff --git a/lang/en-us/ai-chat/index.js b/lang/en-us/ai-chat/index.js index 59447c1..c10f96d 100644 --- a/lang/en-us/ai-chat/index.js +++ b/lang/en-us/ai-chat/index.js @@ -35,6 +35,7 @@ export const lang = { copySelected: 'Copied selected content', copyFail: 'Copy failed', copyFailWithReason: 'Copy failed: {reason}', + copyFailPrivacyReason: 'clipboard permission not declared, please long-press the message text to copy', openingFile: 'Opening file...', openingFileName: 'Opening file: {name}', openingFileTitle: 'Opening: {title}', @@ -135,7 +136,7 @@ export const lang = { logEventHandleError: 'Event handling error', logInitNavbarFailed: 'Failed to initialize navigation bar', logChatComponentNotInit: 'Chat component not initialized', - logServicerConfigNotLoaded: '[AI Customer Service] servicerConfig not loaded, retrying after 200ms...' + logServicerConfigNotLoaded: '[AI Customer Service] servicerConfig not loaded, retrying after 200ms...', // ====== AI Agent general copy (customer service is the first agent) ====== aiAgentDefaultTitle: 'AI Agent', diff --git a/lang/en-us/common.js b/lang/en-us/common.js index c6dabe9..b7cfd32 100644 --- a/lang/en-us/common.js +++ b/lang/en-us/common.js @@ -126,6 +126,8 @@ export const lang = { copyTimeoutReason: 'Reason:', wechatClipboardReason1: '1. WeChat Platform -> User Privacy Protection Guidelines -> "Clipboard" feature not added or review not passed;', wechatClipboardReason2: '2. WeChat platform has restrictions on clipboard API call frequency', + // readable tip when clipboard privacy scope is not declared (errno 112) + clipboardPrivacyNotDeclared: 'Clipboard privacy permission is not configured', copyFailed: 'Copy failed', }, diff --git a/lang/zh-cn/ai-chat/index.js b/lang/zh-cn/ai-chat/index.js index 752f15b..6666241 100644 --- a/lang/zh-cn/ai-chat/index.js +++ b/lang/zh-cn/ai-chat/index.js @@ -35,6 +35,7 @@ export const lang = { copySelected: '已复制选中内容', copyFail: '复制失败', copyFailWithReason: '复制失败:{reason}', + copyFailPrivacyReason: '小程序未声明剪贴板权限,请长按消息文字手动复制', openingFile: '打开文件中...', openingFileName: '正在打开文件:{name}', openingFileTitle: '正在打开:{title}', @@ -135,7 +136,7 @@ export const lang = { logEventHandleError: '事件处理错误', logInitNavbarFailed: '初始化导航栏失败', logChatComponentNotInit: '聊天组件未初始化', - logServicerConfigNotLoaded: '【AI客服】servicerConfig 未加载,200ms后重试...' + logServicerConfigNotLoaded: '【AI客服】servicerConfig 未加载,200ms后重试...', // ====== AI Agent 通用文案(智能客服是首个 agent) ====== aiAgentDefaultTitle: 'AI 智能体', diff --git a/lang/zh-cn/common.js b/lang/zh-cn/common.js index d4cf588..951e931 100644 --- a/lang/zh-cn/common.js +++ b/lang/zh-cn/common.js @@ -126,6 +126,8 @@ export const lang = { copyTimeoutReason: '原因:', wechatClipboardReason1: '1.微信平台->用户隐私保护指引->"剪贴板"功能未添加或审核未通过;', wechatClipboardReason2: '2.微信平台对剪贴板API调用频率有限制', + // 微信剪贴板隐私声明缺失(errno 112)时的可读提示 + clipboardPrivacyNotDeclared: '剪贴板隐私权限未配置', copyFailed: '复制失败', }, diff --git a/manifest.json b/manifest.json index cf0b306..692a3cc 100644 --- a/manifest.json +++ b/manifest.json @@ -82,7 +82,7 @@ } }, "requiredPrivateInfos" : [ "chooseLocation", "getLocation", "chooseAddress" ], - "__usePrivacyCheck__" : true, + "__usePrivacyCheck__" : false, "optimization" : { "subPackages" : true } diff --git a/pages_tool/ai-chat/ai-chat-message.vue b/pages_tool/ai-chat/ai-chat-message.vue index 3e59fc9..be35b00 100644 --- a/pages_tool/ai-chat/ai-chat-message.vue +++ b/pages_tool/ai-chat/ai-chat-message.vue @@ -369,6 +369,7 @@