refactor(privacy): 调整隐私授权触发逻辑,登录页取消主动预检
- 移除登录页和注册页 `useDidShow` 中主动调用 `ensurePrivacyAuthorized()` 预检的逻辑 - 登录页不再主动弹出隐私协议弹窗,由微信在用户点击手机号登录按钮时强制触发授权 - 门店订单管理页面调用 `chooseProofImage` 前新增 `ensurePrivacyAuthorized()` 预检,确保上传凭证时授权 - 修改隐私弹窗文案为更通用描述,避免误导用户以为是相册授权 - 保留全局 `onNeedPrivacyAuthorization` 通过 `PrivacyModal` 处理,适配微信基础库强制机制 - 统一门店商品管理上传使用已内置预检的 `uploadFile()` 接口调用流程
This commit is contained in:
@@ -35,3 +35,18 @@
|
||||
- 后续提醒:
|
||||
- 由于之前的 `showModal` 授权无效,用户可能需要清理开发者工具/真机缓存后重新进入小程序,才能触发新的 PrivacyModal。
|
||||
- 仍需在小程序后台添加「手机号」隐私声明,并确认手机号快速验证组件已开通、额度充足。
|
||||
|
||||
## 调整隐私授权触发位置:登录页不再主动预检,仅在门店上传图片时触发
|
||||
|
||||
- 用户反馈:隐私授权框不应在手机号登录时主动弹出,应只在门店中心上传图片/拍照时弹出。
|
||||
- 修改文件:
|
||||
- `src/passport/login.tsx`:移除 `useDidShow` 中主动调用 `ensurePrivacyAuthorized()` 的逻辑;删除对应 import。
|
||||
- `src/passport/register.tsx`:同样移除 `useDidShow` 中的隐私预检;删除对应 import。
|
||||
- `src/components/PrivacyModal/index.tsx`:将弹窗文案改为更通用表述,避免用户误以为是相册授权。
|
||||
- `src/pages/store/orders/index.tsx`:在 `chooseProofImage` 调用 `Taro.chooseImage` 前增加 `ensurePrivacyAuthorized()` 预检(该页面直接调 chooseImage,不走 `uploadFile`)。
|
||||
- 保留逻辑:
|
||||
- `src/app.tsx` 全局 `onNeedPrivacyAuthorization` 仍通过 `PrivacyModal` 处理,这是微信基础库 3.16.1+ 强制机制,无法移除。
|
||||
- `src/api/system/file/index.ts` 的 `uploadFile()` 内部已调用 `ensurePrivacyAuthorized()`,门店商品管理上传图片时会自动触发隐私授权。
|
||||
- 说明:登录时若用户尚未同意隐私协议,微信仍会在点击 `getPhoneNumber` 按钮时强制触发 `onNeedPrivacyAuthorization`。这是微信机制,无法避免;但进入登录页本身不会再主动弹框。
|
||||
- 验证:待构建完成。
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
## 微信隐私协议(基础库 3.16.1+ 强制)
|
||||
- 报错特征:`chooseImage:fail api scope is not declared in the privacy agreement` / `getPhoneNumber:fail ... privacy ...` + `errno:112`
|
||||
- 代码修复:
|
||||
1. 调用 `Taro.chooseImage` / `getPhoneNumber` 等敏感 API 前,先 `getPrivacySetting` → `requirePrivacyAuthorize` 预检(封装在 `src/api/system/file/index.ts` 的 `ensurePrivacyAuthorized()` 里,已 export)
|
||||
1. 调用 `Taro.chooseImage` / `chooseMedia` / `getPhoneNumber` 等敏感 API 前,先 `getPrivacySetting` → `requirePrivacyAuthorize` 预检(封装在 `src/api/system/file/index.ts` 的 `ensurePrivacyAuthorized()` 里,已 export)。**注意**:不应在登录页 `useDidShow` 中主动预检,避免用户一进入登录页就弹框;应由微信在用户点击 `getPhoneNumber` 时自行触发 `onNeedPrivacyAuthorization`。
|
||||
2. `src/app.tsx` 的 `useLaunch` 中注册 `Taro.onNeedPrivacyAuthorization` 回调,并通过 `src/components/PrivacyModal` 展示隐私协议授权弹窗。弹窗内的 Button 必须设置 `open-type="agreePrivacyAuthorization"`,用户点击后触发 `onAgreePrivacyAuthorization` 再 `resolve({ event: 'agree', button: 'agree' })`;`Taro.showModal` 的按钮无法被微信识别为有效的隐私授权
|
||||
3. 登录/注册页在 `useDidShow` 中主动预检隐私协议,避免点击授权按钮时因未授权隐私协议而失败
|
||||
3. 门店上传图片页面在调用 `chooseImage` 前主动预检:`src/pages/store/orders/index.tsx` 的 `chooseProofImage` 已加 `ensurePrivacyAuthorized()`;`src/pages/store/goods/index.tsx` 通过 `uploadFile()` 上传,`uploadFile()` 内部已预检。
|
||||
- 手动配置:**小程序管理后台** → 设置 → 第三方设置 → 用户隐私保护指引 → 添加「开发者收集你的相册/摄像头」和「手机号」声明
|
||||
- ⚠️ **`requiredPrivateInfos` 字段只接受位置类 API 白名单**(chooseAddress/chooseLocation/choosePoi/getFuzzyLocation/getLocation/onLocationChange/startLocationUpdate/startLocationUpdateBackground),不要加 `chooseImage`/`chooseMedia`/`getPhoneNumber` 等非位置类,会导致 app.json 解析失败
|
||||
- 直接调 `Taro.chooseImage` 的页面:`pages/order/evaluate/index.tsx`、`pages/after-sale/apply/index.tsx`、`pages/store/orders/index.tsx` —— 这些页面绕过了 `uploadFile`,需要单独加 `ensurePrivacyAuthorized` 预检
|
||||
- 直接调 `getPhoneNumber` 的页面:`passport/login.tsx`、`passport/register.tsx` —— 已增加隐私协议预检 + 降级短信登录
|
||||
- 直接调 `getPhoneNumber` 的页面:`passport/login.tsx`、`passport/register.tsx` —— 不再主动预检,依赖微信点击按钮时自动触发 `onNeedPrivacyAuthorization`;保留降级短信登录
|
||||
- 隐私授权弹窗组件:`src/components/PrivacyModal/index.tsx`(全局单例,通过 `src/utils/privacy.ts` 管理显隐与 resolve)
|
||||
|
||||
## 手机号授权登录降级方案(2026-07-15 ~ 07-16 修复)
|
||||
|
||||
19
overview.md
19
overview.md
@@ -6,14 +6,19 @@
|
||||
- 原代码在 `onNeedPrivacyAuthorization` 中直接 `resolve({ event: 'agree' })`,在基础库 3.16.1+ 下无法通过微信校验。
|
||||
- 新增 `src/components/PrivacyModal`,使用原生 Button 的 `open-type="agreePrivacyAuthorization"` 触发真正的隐私授权;`Taro.showModal` 的按钮无法被微信识别。
|
||||
|
||||
2. **登录/注册页预检隐私协议**
|
||||
- `src/passport/login.tsx` 和 `src/passport/register.tsx` 在 `useDidShow` 中调用 `ensurePrivacyAuthorized()`,避免用户点击授权按钮时因隐私协议未授权而失败。
|
||||
2. **登录/注册页不再主动预检隐私协议**
|
||||
- 移除 `src/passport/login.tsx` 和 `src/passport/register.tsx` 中 `useDidShow` 主动调用 `ensurePrivacyAuthorized()` 的逻辑。
|
||||
- 进入登录页本身不再主动弹出隐私授权框;仅在用户点击手机号登录按钮,且微信检测到未授权时,才由微信强制触发 `onNeedPrivacyAuthorization`。
|
||||
|
||||
3. **统一接口调用**
|
||||
3. **门店中心上传图片前预检隐私协议**
|
||||
- 门店商品管理(`src/pages/store/goods/index.tsx`)通过 `uploadFile()` 上传图片,该接口内部已调用 `ensurePrivacyAuthorized()`,点击上传时自动触发隐私授权。
|
||||
- 门店订单管理(`src/pages/store/orders/index.tsx`)直接调用 `Taro.chooseImage` 上传凭证,已在其 `chooseProofImage` 中增加 `ensurePrivacyAuthorized()` 预检。
|
||||
|
||||
4. **统一接口调用**
|
||||
- 登录/注册页统一使用 `request.post` + `SERVER_API_URL` 调用 `/wx-login/loginByMpWxPhone`。
|
||||
- 修复 `register.tsx` 原本错误使用 `https://shop-api.websoft.top` 的问题。
|
||||
|
||||
4. **顺手修复构建错误**
|
||||
5. **顺手修复构建错误**
|
||||
- `src/passport/pay/index.tsx` 引用了不存在的 `@/api/passport/wx-login`,改为使用 `@/api/layout` 中的 `getWxOpenId` / `loginByOpenId`。
|
||||
|
||||
## 验证
|
||||
@@ -28,3 +33,9 @@
|
||||
## 测试前清理
|
||||
|
||||
由于之前 `Taro.showModal` 的授权无效,微信可能已缓存该状态。重新上传后请先在开发者工具或真机中清理小程序缓存,再重新进入登录页,触发新的 PrivacyModal。
|
||||
|
||||
## 重要说明
|
||||
|
||||
- 微信基础库 3.16.1+ 强制要求:调用 `getPhoneNumber`、`chooseImage` 等敏感 API 前,用户必须先同意《用户隐私保护指引》。
|
||||
- 即使登录页不再主动预检,当用户点击「手机号快捷登录」且未授权时,微信仍会强制弹出隐私授权框。这是微信机制,无法绕过。
|
||||
- 如果用户已同意隐私协议但手机号登录仍失败,请重点检查后台是否已开通「手机号快速验证组件」能力,以及隐私指引中是否包含「手机号」声明。
|
||||
|
||||
@@ -30,7 +30,7 @@ const PrivacyModal = () => {
|
||||
<View className='privacy-modal__content'>
|
||||
<Text className='privacy-modal__title'>隐私协议授权</Text>
|
||||
<Text className='privacy-modal__desc'>
|
||||
{`为提供完整服务,需您同意 ${contractName}。点击同意后可继续使用手机号快捷登录、相册等功能。`}
|
||||
{`为提供完整服务,需您同意 ${contractName}。点击同意后可继续使用相关功能。`}
|
||||
</Text>
|
||||
<View className='privacy-modal__footer'>
|
||||
<Button
|
||||
|
||||
@@ -7,6 +7,7 @@ import {TenantId} from '@/config/app'
|
||||
import {useNewOrderDetector} from '@/hooks/useNewOrderDetector'
|
||||
import { getCompressedImageUrl } from '@/utils/image'
|
||||
import { getMyClerk } from '@/api/shop/shopStoreUser'
|
||||
import { ensurePrivacyAuthorized } from '@/api/system/file'
|
||||
|
||||
definePageConfig({
|
||||
navigationBarTitleText: '订单管理',
|
||||
@@ -327,11 +328,18 @@ export default function StoreOrdersPage() {
|
||||
}
|
||||
|
||||
/** 选择并上传凭证图片 */
|
||||
const chooseProofImage = () => {
|
||||
const chooseProofImage = async () => {
|
||||
const maxCount = 3
|
||||
const remaining = maxCount - proofImages.length
|
||||
if (remaining <= 0) return
|
||||
|
||||
// 门店上传凭证需要访问相册/摄像头,先预检隐私协议授权
|
||||
try {
|
||||
await ensurePrivacyAuthorized()
|
||||
} catch {
|
||||
// 授权失败由 chooseImage 自行处理
|
||||
}
|
||||
|
||||
Taro.chooseImage({
|
||||
count: remaining,
|
||||
sizeType: ['compressed'],
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Image, Text, Button } from '@tarojs/components'
|
||||
import { TenantId } from '@/config/app'
|
||||
import { getWxOpenId } from '@/api/layout'
|
||||
@@ -7,7 +7,6 @@ import { getUserInfo } from '@/api/layout'
|
||||
import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
||||
import { isUserDisabled } from '@/utils/auth'
|
||||
import request from '@/utils/request'
|
||||
import { ensurePrivacyAuthorized } from '@/api/system/file'
|
||||
import {
|
||||
checkAndHandleInviteRelation,
|
||||
hasPendingInvite,
|
||||
@@ -110,14 +109,6 @@ const Login = () => {
|
||||
setTimeout(() => setShowContent(true), 100)
|
||||
}, [])
|
||||
|
||||
/** 页面显示时预检隐私协议,避免 getPhoneNumber 因未授权隐私协议而失败 */
|
||||
useDidShow(() => {
|
||||
if (!isWeapp) return
|
||||
ensurePrivacyAuthorized().catch((e) => {
|
||||
console.warn('登录页隐私协议预检失败:', e)
|
||||
})
|
||||
})
|
||||
|
||||
/** 解析 redirect 参数 */
|
||||
const redirectUrl = (() => {
|
||||
const raw = (router?.params as Record<string, string> | undefined)?.redirect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import Taro, { useDidShow } from '@tarojs/taro'
|
||||
import Taro from '@tarojs/taro'
|
||||
import { View, Text } from '@tarojs/components'
|
||||
import { Button, Checkbox } from '@nutui/nutui-react-taro'
|
||||
import { TenantId } from '@/config/app'
|
||||
@@ -7,7 +7,6 @@ import { getUserInfo, getWxOpenId } from '@/api/layout'
|
||||
import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
||||
import { isUserDisabled } from '@/utils/auth'
|
||||
import request from '@/utils/request'
|
||||
import { ensurePrivacyAuthorized } from '@/api/system/file'
|
||||
import {
|
||||
getStoredInviteParams,
|
||||
parseInviteParams,
|
||||
@@ -101,14 +100,6 @@ const Register = () => {
|
||||
|
||||
const router = Taro.getCurrentInstance().router
|
||||
|
||||
/** 页面显示时预检隐私协议,避免 getPhoneNumber 因未授权隐私协议而失败 */
|
||||
useDidShow(() => {
|
||||
if (!isWeapp) return
|
||||
ensurePrivacyAuthorized().catch((e) => {
|
||||
console.warn('注册页隐私协议预检失败:', e)
|
||||
})
|
||||
})
|
||||
|
||||
const redirectUrl = useMemo(() => {
|
||||
const raw = (router?.params as any)?.redirect as string | undefined
|
||||
const decoded = safeDecodeMaybeEncoded(raw)
|
||||
|
||||
Reference in New Issue
Block a user