fix(privacy): 修复微信隐私协议导致的chooseImage错误

- 在 app.config.ts 的requiredPrivateInfos中添加chooseImage和chooseMedia权限声明
- 在上传文件接口uploadFile中调用ensurePrivacyAuthorized函数,预先检查用户隐私授权
- 在 app.tsx 的 useLaunch 钩子中注册 onNeedPrivacyAuthorization 回调,自定义弹窗提示用户协议
- ensurePrivacyAuthorized兼容旧版本微信基础库,未提供隐私授权函数时默认放行
- 更新文档说明隐私协议错误码errno:112的原因及修复方案
- 提醒绕过uploadFile调用chooseImage的页面需单独添加隐私授权预检机制
This commit is contained in:
2026-07-14 23:26:49 +08:00
parent 1fcfcdec73
commit a353bc9ea8
5 changed files with 83 additions and 1 deletions

View File

@@ -22,4 +22,14 @@
4. **登录页面**`passport/login.tsx``passport/register.tsx``pages/store/login/index.tsx` 在保存登录态前检查 status被禁用则弹窗拦截 4. **登录页面**`passport/login.tsx``passport/register.tsx``pages/store/login/index.tsx` 在保存登录态前检查 status被禁用则弹窗拦截
- 用户状态约定:`status === 0` 正常,`status === 1` 禁用(通过 `updateUserStatus(userId, status)` 接口控制) - 用户状态约定:`status === 0` 正常,`status === 1` 禁用(通过 `updateUserStatus(userId, status)` 接口控制)
## 微信隐私协议chooseImage errno:112修复
- 报错:`MiniProgramError {errMsg:"chooseImage:fail api scope is not declared in the privacy agreement", errno:112}`(基础库 3.16.1+ 强制)
- 影响范围:所有调用 `Taro.chooseImage` 的地方,本轮修复统一入口 `uploadFile`(商品管理页、订单评价、售后申请、门店订单都走这个)
- 修复三处:
1. **`src/app.config.ts`** — `requiredPrivateInfos` 补齐 `chooseImage``chooseMedia`
2. **`src/api/system/file/index.ts`** — `uploadFile``ensurePrivacyAuthorized()` 预检(`getPrivacySetting` + `requirePrivacyAuthorize`
3. **`src/app.tsx`** — `useLaunch` 里注册 `Taro.onNeedPrivacyAuthorization` 回调,弹自定义 showModal 协议弹窗,同意 resolve({event:'agree'}) 拒绝 resolve({event:'disagree'})
- 旧基础库兼容:`typeof wxAny.requirePrivacyAuthorize !== 'function'` 时直接放行
- 后续若别的页面直接调 `Taro.chooseImage`(如 `evaluate/index.tsx``after-sale/apply/index.tsx``store/orders/index.tsx`),仍要单独加 `ensurePrivacyAuthorized` 预检

View File

@@ -41,6 +41,14 @@
- 场景说明:下单提醒 - 场景说明:下单提醒
- 新订单检测 Hook`src/hooks/useNewOrderDetector.ts`30秒轮询页面隐藏暂停 - 新订单检测 Hook`src/hooks/useNewOrderDetector.ts`30秒轮询页面隐藏暂停
## 微信隐私协议(基础库 3.16.1+ 强制)
- 报错特征:`chooseImage:fail api scope is not declared in the privacy agreement` + `errno:112`
- 三处必改:
1. `src/app.config.ts``requiredPrivateInfos` 必含 `chooseImage``chooseMedia`(已经包含 `getLocation``chooseLocation`
2. 调用 `Taro.chooseImage` 前必须先 `getPrivacySetting``requirePrivacyAuthorize` 预检(封装在 `src/api/system/file/index.ts``ensurePrivacyAuthorized()` 里)
3. `src/app.tsx``useLaunch` 中注册 `Taro.onNeedPrivacyAuthorization` 回调(用 `showModal` 自定义弹窗)
- 直接调 `Taro.chooseImage` 的页面:`pages/order/evaluate/index.tsx``pages/after-sale/apply/index.tsx``pages/store/orders/index.tsx` —— 这些页面绕过了 `uploadFile`,需要单独加 `ensurePrivacyAuthorized` 预检
## 分享 / 朋友圈能力2026-07-13 接入) ## 分享 / 朋友圈能力2026-07-13 接入)
- 统一封装:`src/hooks/useShare.ts``useShare({title, path?, query?, imageUrl?, enableTimeline?, enableCopyUrl?})`,一次注册 `useShareAppMessage` + `useShareTimeline` + 可选 `onCopyUrl` 复制链接;自动在 path/query 追加 `inviter=${当前用户id}` 做裂变归因;`isTimelineSinglePage()` 判断朋友圈单页模式 scene===1154 - 统一封装:`src/hooks/useShare.ts``useShare({title, path?, query?, imageUrl?, enableTimeline?, enableCopyUrl?})`,一次注册 `useShareAppMessage` + `useShareTimeline` + 可选 `onCopyUrl` 复制链接;自动在 path/query 追加 `inviter=${当前用户id}` 做裂变归因;`isTimelineSinglePage()` 判断朋友圈单页模式 scene===1154
- 海报组件:`src/components/SharePoster/index.tsx`Canvas 2D 绘制封面+标题+价格+小程序码,返回临时图路径作 imageUrl - 海报组件:`src/components/SharePoster/index.tsx`Canvas 2D 绘制封面+标题+价格+小程序码,返回临时图路径作 imageUrl

View File

@@ -53,11 +53,52 @@ const computeSignature = (accessKeySecret: string, canonicalString: string): str
return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret)); return crypto.enc.Base64.stringify(crypto.HmacSHA1(canonicalString, accessKeySecret));
} }
/**
* 在调用涉及用户隐私的 APIchooseImage / chooseMedia / getLocation 等)前,
* 等待用户完成微信隐私协议授权。基础库 3.16.1+ 强制要求,未授权会抛 errno:112。
* 旧基础库或不支持隐私协议的版本直接放行。
*/
const ensurePrivacyAuthorized = (): Promise<void> => {
return new Promise((resolve) => {
const wxAny: any = Taro
if (typeof wxAny.requirePrivacyAuthorize !== 'function') {
resolve()
return
}
if (typeof wxAny.getPrivacySetting === 'function') {
wxAny.getPrivacySetting({
success: (res: any) => {
if (res && res.needAuthorization) {
wxAny.requirePrivacyAuthorize({
success: () => resolve(),
fail: () => resolve(),
})
} else {
resolve()
}
},
fail: () => resolve(),
})
} else {
wxAny.requirePrivacyAuthorize({
success: () => resolve(),
fail: () => resolve(),
})
}
})
}
/** /**
* 上传阿里云OSS * 上传阿里云OSS
*/ */
export async function uploadFile() { export async function uploadFile() {
return new Promise(async (resolve: (result: FileRecord) => void, reject) => { return new Promise(async (resolve: (result: FileRecord) => void, reject) => {
// 修复:基础库 3.16.1+ 强制隐私协议校验,先确保用户已同意
try {
await ensurePrivacyAuthorized()
} catch {
// 忽略前置授权错误,让 chooseImage 自己抛具体的 fail
}
Taro.chooseImage({ Taro.chooseImage({
count: 1, count: 1,
sizeType: ['compressed'], sizeType: ['compressed'],

View File

@@ -140,7 +140,7 @@ export default {
desc: '用于获取您的位置,按距离展示附近门店和选择收货地址定位', desc: '用于获取您的位置,按距离展示附近门店和选择收货地址定位',
}, },
}, },
requiredPrivateInfos: ['getLocation', 'chooseLocation'], requiredPrivateInfos: ['getLocation', 'chooseLocation', 'chooseImage', 'chooseMedia'],
tabBar: { tabBar: {
custom: false, custom: false,
color: '#8a8a8a', color: '#8a8a8a',

View File

@@ -22,6 +22,29 @@ function App(props: AppProps) {
if (savedTheme === 'light' || savedTheme === 'dark') { if (savedTheme === 'light' || savedTheme === 'dark') {
setTheme(savedTheme) setTheme(savedTheme)
} }
// 微信隐私协议(基础库 3.16.1+ 强制)
// 当小程序使用 chooseImage/chooseMedia/getLocation 等接口时,若用户未在隐私协议中同意,
// 微信会回调这里,开发者必须弹一个自定义的协议确认弹窗,并提供"同意并继续"/"拒绝"两个分支。
const wxAny: any = Taro
if (typeof wxAny.onNeedPrivacyAuthorization === 'function') {
wxAny.onNeedPrivacyAuthorization((resolve: any) => {
wxAny.showModal({
title: '用户隐私协议',
content: '为了正常使用图片上传、拍照、定位等功能,需要您同意《用户隐私协议》。',
confirmText: '同意',
cancelText: '拒绝',
success: (modalRes: any) => {
if (modalRes.confirm) {
resolve({ event: 'agree', button: 'agree' })
} else {
resolve({ event: 'disagree' })
}
},
fail: () => resolve({ event: 'disagree' }),
})
})
}
}) })
useDidShow(() => {}) useDidShow(() => {})