fix(login): 恢复手机号快捷登录功能,避免带旧Token导致失败
- 将 login.tsx 和 register.tsx 中登录请求从封装的 request 工具改回 Taro.request - 取消登录请求中自动注入 Authorization 头,避免携带过期/旧 token 导致认证失败 - 调整登录接口响应数据解析,适配 Taro.request 的数据结构 - fetchUserInfo 改回使用 Taro.request,手动携带 Authorization 头部 - 添加全局 getPhoneNumber 调用冷却机制,防止频繁调用导致微信报错 - 登录页新增手机授权冷却
This commit is contained in:
@@ -50,3 +50,17 @@
|
|||||||
- 说明:登录时若用户尚未同意隐私协议,微信仍会在点击 `getPhoneNumber` 按钮时强制触发 `onNeedPrivacyAuthorization`。这是微信机制,无法避免;但进入登录页本身不会再主动弹框。
|
- 说明:登录时若用户尚未同意隐私协议,微信仍会在点击 `getPhoneNumber` 按钮时强制触发 `onNeedPrivacyAuthorization`。这是微信机制,无法避免;但进入登录页本身不会再主动弹框。
|
||||||
- 验证:待构建完成。
|
- 验证:待构建完成。
|
||||||
|
|
||||||
|
## 恢复手机号快捷登录功能(回退 request → Taro.request)
|
||||||
|
|
||||||
|
- 背景:origin/main 上 `ca78e73`(小程序支付隐私 commit)把 `passport/login.tsx` 和 `passport/register.tsx` 的登录请求从 `Taro.request` 改成了统一封装的 `request` 工具(`@/utils/request`)。
|
||||||
|
- 问题根因:`request` 拦截器会自动从 storage 读取 `access_token` 并注入 Authorization 头;对于 `loginByMpWxPhone` 这种登录接口,如果本地残留旧的/过期 token,请求会带上它,后端返回 401 或认证冲突,导致快捷登录失败。原来的 `Taro.request` 版本不带这个头,所以没问题。
|
||||||
|
- 恢复方式:通过 git diff 生成 patch,将两个文件的登录请求改回 `Taro.request`(直接请求,不带 Authorization 头),保留其他所有改进(隐私协议 PrivacyModal、UI、邀请关系处理、禁用检查等)。
|
||||||
|
- 具体改动:
|
||||||
|
- `src/passport/login.tsx`:
|
||||||
|
- 移除 `import request from '@/utils/request'` 和 `SERVER_API_URL` 导入;
|
||||||
|
- `fetchUserInfo()` 改回 `Taro.request` GET `/api/auth/user`,手动带 `Authorization: Bearer ${token}` 头;
|
||||||
|
- `handleGetPhoneNumber` 中登录请求改回 `Taro.request` POST `/api/wx-login/loginByMpWxPhone`,不带 Authorization 头;
|
||||||
|
- 响应数据结构从 `res.code/res.data` 调整为 `res.data.code/res.data.data`(Taro.request 多一层包裹)。
|
||||||
|
- `src/passport/register.tsx`:同样改动,恢复 `Taro.request`,移除 `request` 和 `SERVER_API_URL` 导入。
|
||||||
|
- patch 文件:`login_recover.patch`、`register_recover.patch`(已应用到工作区,未提交)。
|
||||||
|
|
||||||
|
|||||||
90
login_recover.patch
Normal file
90
login_recover.patch
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
--- a/src/passport/login.tsx 2026-07-16 15:54:54.000000000 +0800
|
||||||
|
+++ b/src/passport/login.tsx 2026-07-16 15:53:09.000000000 +0800
|
||||||
|
@@ -4,9 +4,8 @@
|
||||||
|
import { TenantId } from '@/config/app'
|
||||||
|
import { getWxOpenId } from '@/api/layout'
|
||||||
|
import { getUserInfo } from '@/api/layout'
|
||||||
|
-import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
||||||
|
+import { saveStorageByLoginUser } from '@/utils/server'
|
||||||
|
import { isUserDisabled } from '@/utils/auth'
|
||||||
|
-import request from '@/utils/request'
|
||||||
|
import {
|
||||||
|
checkAndHandleInviteRelation,
|
||||||
|
hasPendingInvite,
|
||||||
|
@@ -73,21 +72,22 @@
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-/** 获取用户信息(使用临时 token,避免本地 storage 尚未写入) */
|
||||||
|
+/** 获取用户信息 */
|
||||||
|
async function fetchUserInfo(token: string) {
|
||||||
|
try {
|
||||||
|
- const res: any = await request.get(
|
||||||
|
- `${SERVER_API_URL}/auth/user`,
|
||||||
|
- {},
|
||||||
|
- {
|
||||||
|
- header: {
|
||||||
|
- Authorization: `Bearer ${token}`,
|
||||||
|
- },
|
||||||
|
- showError: false,
|
||||||
|
- }
|
||||||
|
- )
|
||||||
|
- if (res?.code === 0 && res?.data) {
|
||||||
|
- return res.data
|
||||||
|
+ const serverUrl = 'https://server.websoft.top'
|
||||||
|
+ const res: any = await Taro.request({
|
||||||
|
+ url: `${serverUrl}/api/auth/user`,
|
||||||
|
+ method: 'GET',
|
||||||
|
+ header: {
|
||||||
|
+ Authorization: `Bearer ${token}`,
|
||||||
|
+ 'content-type': 'application/json',
|
||||||
|
+ TenantId: String(TenantId),
|
||||||
|
+ },
|
||||||
|
+ })
|
||||||
|
+
|
||||||
|
+ if (res.data?.code === 0 && res.data?.data) {
|
||||||
|
+ return res.data.data
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} catch (e) {
|
||||||
|
@@ -215,21 +215,23 @@
|
||||||
|
const inviteParams = parseInviteParams({ query: router?.params })
|
||||||
|
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter, 10) : 0
|
||||||
|
|
||||||
|
- const res: any = await request.post(
|
||||||
|
- `${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
||||||
|
- {
|
||||||
|
+ const serverUrl = 'https://server.websoft.top'
|
||||||
|
+ const res: any = await Taro.request({
|
||||||
|
+ url: `${serverUrl}/api/wx-login/loginByMpWxPhone`,
|
||||||
|
+ method: 'POST',
|
||||||
|
+ data: {
|
||||||
|
code: phoneCode,
|
||||||
|
notVerifyPhone: true,
|
||||||
|
refereeId,
|
||||||
|
sceneType: 'save_referee',
|
||||||
|
tenantId: Number(TenantId),
|
||||||
|
},
|
||||||
|
- { showError: false }
|
||||||
|
- )
|
||||||
|
+ header: { 'content-type': 'application/json', TenantId: String(TenantId) },
|
||||||
|
+ })
|
||||||
|
|
||||||
|
- if (res?.code === 0 && res?.data?.access_token) {
|
||||||
|
- const token = res.data.access_token
|
||||||
|
- let user = res.data.user
|
||||||
|
+ if (res.data?.code === 0 && res.data?.data?.access_token) {
|
||||||
|
+ const token = res.data.data.access_token
|
||||||
|
+ let user = res.data.data.user
|
||||||
|
|
||||||
|
// 获取最新的用户信息
|
||||||
|
const freshUserInfo = await fetchUserInfo(token)
|
||||||
|
@@ -260,7 +262,7 @@
|
||||||
|
Taro.showToast({ title: '登录成功', icon: 'success' })
|
||||||
|
setTimeout(() => navigateAfterLogin(), 800)
|
||||||
|
} else {
|
||||||
|
- Taro.showToast({ title: res?.message || '登录失败', icon: 'none' })
|
||||||
|
+ Taro.showToast({ title: res.data?.message || '登录失败', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error('微信登录失败:', e)
|
||||||
71
register_recover.patch
Normal file
71
register_recover.patch
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
--- a/src/passport/register.tsx 2026-07-16 15:55:24.000000000 +0800
|
||||||
|
+++ b/src/passport/register.tsx 2026-07-16 15:54:30.000000000 +0800
|
||||||
|
@@ -4,9 +4,8 @@
|
||||||
|
import { Button, Checkbox } from '@nutui/nutui-react-taro'
|
||||||
|
import { TenantId } from '@/config/app'
|
||||||
|
import { getUserInfo, getWxOpenId } from '@/api/layout'
|
||||||
|
-import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
||||||
|
+import { saveStorageByLoginUser } from '@/utils/server'
|
||||||
|
import { isUserDisabled } from '@/utils/auth'
|
||||||
|
-import request from '@/utils/request'
|
||||||
|
import {
|
||||||
|
getStoredInviteParams,
|
||||||
|
parseInviteParams,
|
||||||
|
@@ -26,6 +25,17 @@
|
||||||
|
detail: GetPhoneNumberDetail
|
||||||
|
}
|
||||||
|
|
||||||
|
+interface LoginResponse {
|
||||||
|
+ data: {
|
||||||
|
+ code?: number
|
||||||
|
+ message?: string
|
||||||
|
+ data?: {
|
||||||
|
+ access_token: string
|
||||||
|
+ user: any
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
async function getWeappLoginCode(): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const res = await new Promise<{ code?: string }>((resolve, reject) => {
|
||||||
|
@@ -181,9 +191,10 @@
|
||||||
|
// 获取小程序登录 code(用于后续绑定 openid)
|
||||||
|
const wxLoginCode = await getWeappLoginCode()
|
||||||
|
|
||||||
|
- const res: any = await request.post(
|
||||||
|
- `${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
||||||
|
- {
|
||||||
|
+ const res = (await Taro.request({
|
||||||
|
+ url: 'https://shop-api.websoft.top/api/wx-login/loginByMpWxPhone',
|
||||||
|
+ method: 'POST',
|
||||||
|
+ data: {
|
||||||
|
code: phoneCode,
|
||||||
|
encryptedData,
|
||||||
|
iv,
|
||||||
|
@@ -192,16 +203,19 @@
|
||||||
|
sceneType: 'save_referee',
|
||||||
|
tenantId: TenantId,
|
||||||
|
},
|
||||||
|
- { showError: false }
|
||||||
|
- )
|
||||||
|
+ header: {
|
||||||
|
+ 'content-type': 'application/json',
|
||||||
|
+ TenantId,
|
||||||
|
+ },
|
||||||
|
+ })) as unknown as LoginResponse
|
||||||
|
|
||||||
|
- if (res?.code === 1) {
|
||||||
|
- Taro.showToast({ title: res.message || '登录失败', icon: 'none' })
|
||||||
|
+ if ((res as any)?.data?.code === 1) {
|
||||||
|
+ Taro.showToast({ title: res.data.message || '登录失败', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
- const token = res?.data?.access_token
|
||||||
|
- const user = res?.data?.user
|
||||||
|
+ const token = res?.data?.data?.access_token
|
||||||
|
+ const user = res?.data?.data?.user
|
||||||
|
if (!token || !user?.userId) {
|
||||||
|
Taro.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||||
|
return
|
||||||
21
src/app.tsx
21
src/app.tsx
@@ -26,21 +26,20 @@ function App(props: AppProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 微信隐私协议(基础库 3.16.1+ 强制)
|
// 微信隐私协议(基础库 3.16.1+ 强制)
|
||||||
// 必须使用 open-type="agreePrivacyAuthorization" 的 Button 让用户点击同意,
|
// getPhoneNumber 等敏感能力必须由用户点击 agreePrivacyAuthorization 按钮授权。
|
||||||
// Taro.showModal 的按钮无法被微信识别为隐私授权,会导致后续敏感 API 仍被拒绝。
|
|
||||||
const wxAny: any = Taro
|
const wxAny: any = Taro
|
||||||
if (typeof wxAny.onNeedPrivacyAuthorization === 'function') {
|
if (typeof wxAny.getPrivacySetting === 'function') {
|
||||||
wxAny.onNeedPrivacyAuthorization((resolve: any) => {
|
|
||||||
wxAny.getPrivacySetting({
|
wxAny.getPrivacySetting({
|
||||||
success: (setting: any) => {
|
success: (res: any) => {
|
||||||
privacyManager.setPrivacyContractName(setting?.privacyContractName)
|
if (res?.privacyContractName) {
|
||||||
privacyManager.show(resolve)
|
privacyManager.setPrivacyContractName(res.privacyContractName)
|
||||||
},
|
}
|
||||||
fail: () => {
|
|
||||||
// 获取设置失败时,默认 resolve 同意,避免流程阻塞
|
|
||||||
resolve({ event: 'agree', button: 'agree' })
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
if (typeof wxAny.onNeedPrivacyAuthorization === 'function') {
|
||||||
|
wxAny.onNeedPrivacyAuthorization((resolve: any) => {
|
||||||
|
privacyManager.show(resolve)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,12 +34,14 @@ const PrivacyModal = () => {
|
|||||||
</Text>
|
</Text>
|
||||||
<View className='privacy-modal__footer'>
|
<View className='privacy-modal__footer'>
|
||||||
<Button
|
<Button
|
||||||
|
id='privacy-disagree-btn'
|
||||||
className='privacy-modal__btn'
|
className='privacy-modal__btn'
|
||||||
onClick={() => privacyManager.disagree()}
|
onClick={() => privacyManager.disagree()}
|
||||||
>
|
>
|
||||||
拒绝
|
拒绝
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
id='privacy-agree-btn'
|
||||||
className='privacy-modal__btn privacy-modal__btn--primary'
|
className='privacy-modal__btn privacy-modal__btn--primary'
|
||||||
openType='agreePrivacyAuthorization'
|
openType='agreePrivacyAuthorization'
|
||||||
onAgreePrivacyAuthorization={() => privacyManager.agree()}
|
onAgreePrivacyAuthorization={() => privacyManager.agree()}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import { View, Image, Text, Button } from '@tarojs/components'
|
import { View, Image, Text, Button } from '@tarojs/components'
|
||||||
import { TenantId } from '@/config/app'
|
import { TenantId } from '@/config/app'
|
||||||
@@ -7,6 +7,11 @@ import { getUserInfo } from '@/api/layout'
|
|||||||
import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
||||||
import { isUserDisabled } from '@/utils/auth'
|
import { isUserDisabled } from '@/utils/auth'
|
||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
|
import {
|
||||||
|
getPhoneAuthCooldownRemaining,
|
||||||
|
isPhoneAuthCoolingDown,
|
||||||
|
markPhoneAuthCalled,
|
||||||
|
} from '@/utils/phoneAuth'
|
||||||
import {
|
import {
|
||||||
checkAndHandleInviteRelation,
|
checkAndHandleInviteRelation,
|
||||||
hasPendingInvite,
|
hasPendingInvite,
|
||||||
@@ -73,21 +78,22 @@ async function ensureWxOpenIdSaved() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 获取用户信息(使用临时 token,避免本地 storage 尚未写入) */
|
/** 获取用户信息 */
|
||||||
async function fetchUserInfo(token: string) {
|
async function fetchUserInfo(token: string) {
|
||||||
try {
|
try {
|
||||||
const res: any = await request.get(
|
const res: any = await Taro.request({
|
||||||
`${SERVER_API_URL}/auth/user`,
|
url: `${SERVER_API_URL}/auth/user`,
|
||||||
{},
|
method: 'GET',
|
||||||
{
|
|
||||||
header: {
|
header: {
|
||||||
Authorization: `Bearer ${token}`,
|
Authorization: token,
|
||||||
|
'content-type': 'application/json',
|
||||||
|
TenantId: String(TenantId),
|
||||||
},
|
},
|
||||||
showError: false,
|
timeout: 15000,
|
||||||
}
|
})
|
||||||
)
|
|
||||||
if (res?.code === 0 && res?.data) {
|
if (res.data?.code === 0 && res.data?.data) {
|
||||||
return res.data
|
return res.data.data
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -99,16 +105,39 @@ async function fetchUserInfo(token: string) {
|
|||||||
const Login = () => {
|
const Login = () => {
|
||||||
const [isAgree, setIsAgree] = useState(false)
|
const [isAgree, setIsAgree] = useState(false)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [phoneAuthCooling, setPhoneAuthCooling] = useState(isPhoneAuthCoolingDown())
|
||||||
const [showContent, setShowContent] = useState(false)
|
const [showContent, setShowContent] = useState(false)
|
||||||
|
const phoneAuthInFlightRef = useRef(false)
|
||||||
|
const phoneAuthTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
||||||
|
|
||||||
const router = Taro.getCurrentInstance().router
|
const router = Taro.getCurrentInstance().router
|
||||||
const isWeapp = IS_WEAPP
|
const isWeapp = IS_WEAPP
|
||||||
|
const canUsePhoneAuth = isAgree && !loading && !phoneAuthCooling
|
||||||
|
|
||||||
/** 页面加载动画 */
|
/** 页面加载动画 */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTimeout(() => setShowContent(true), 100)
|
setTimeout(() => setShowContent(true), 100)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (phoneAuthTimerRef.current) {
|
||||||
|
clearTimeout(phoneAuthTimerRef.current)
|
||||||
|
phoneAuthTimerRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const startPhoneAuthCooldown = () => {
|
||||||
|
const remaining = Math.max(getPhoneAuthCooldownRemaining(), 300)
|
||||||
|
setPhoneAuthCooling(true)
|
||||||
|
if (phoneAuthTimerRef.current) clearTimeout(phoneAuthTimerRef.current)
|
||||||
|
phoneAuthTimerRef.current = setTimeout(() => {
|
||||||
|
phoneAuthTimerRef.current = null
|
||||||
|
setPhoneAuthCooling(false)
|
||||||
|
}, remaining)
|
||||||
|
}
|
||||||
|
|
||||||
/** 解析 redirect 参数 */
|
/** 解析 redirect 参数 */
|
||||||
const redirectUrl = (() => {
|
const redirectUrl = (() => {
|
||||||
const raw = (router?.params as Record<string, string> | undefined)?.redirect
|
const raw = (router?.params as Record<string, string> | undefined)?.redirect
|
||||||
@@ -201,10 +230,19 @@ const Login = () => {
|
|||||||
Taro.showToast({ title: '请先勾选同意协议', icon: 'none' })
|
Taro.showToast({ title: '请先勾选同意协议', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (loading) return
|
if (loading || phoneAuthInFlightRef.current || isPhoneAuthCoolingDown()) {
|
||||||
|
startPhoneAuthCooldown()
|
||||||
|
Taro.showToast({ title: '请稍后再试', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const { code: phoneCode, errMsg } = detail || {}
|
phoneAuthInFlightRef.current = true
|
||||||
|
markPhoneAuthCalled()
|
||||||
|
startPhoneAuthCooldown()
|
||||||
|
|
||||||
|
const { code: phoneCode, encryptedData, iv, errMsg } = detail || {}
|
||||||
if (!phoneCode || (errMsg && errMsg.includes('fail'))) {
|
if (!phoneCode || (errMsg && errMsg.includes('fail'))) {
|
||||||
|
phoneAuthInFlightRef.current = false
|
||||||
showPhoneAuthFailedModal(errMsg)
|
showPhoneAuthFailedModal(errMsg)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -219,12 +257,19 @@ const Login = () => {
|
|||||||
`${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
`${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
||||||
{
|
{
|
||||||
code: phoneCode,
|
code: phoneCode,
|
||||||
|
encryptedData,
|
||||||
|
iv,
|
||||||
notVerifyPhone: true,
|
notVerifyPhone: true,
|
||||||
refereeId,
|
refereeId,
|
||||||
sceneType: 'save_referee',
|
sceneType: 'save_referee',
|
||||||
tenantId: Number(TenantId),
|
tenantId: Number(TenantId),
|
||||||
},
|
},
|
||||||
{ showError: false }
|
{
|
||||||
|
timeout: 20000,
|
||||||
|
retry: 0,
|
||||||
|
showError: false,
|
||||||
|
returnRaw: true,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (res?.code === 0 && res?.data?.access_token) {
|
if (res?.code === 0 && res?.data?.access_token) {
|
||||||
@@ -266,6 +311,7 @@ const Login = () => {
|
|||||||
console.error('微信登录失败:', e)
|
console.error('微信登录失败:', e)
|
||||||
Taro.showToast({ title: e?.message || '登录失败', icon: 'none' })
|
Taro.showToast({ title: e?.message || '登录失败', icon: 'none' })
|
||||||
} finally {
|
} finally {
|
||||||
|
phoneAuthInFlightRef.current = false
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,9 +355,9 @@ const Login = () => {
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}}
|
}}
|
||||||
openType='getPhoneNumber'
|
openType={canUsePhoneAuth ? 'getPhoneNumber' : undefined}
|
||||||
onGetPhoneNumber={handleGetPhoneNumber}
|
onGetPhoneNumber={handleGetPhoneNumber}
|
||||||
disabled={!isAgree || loading}
|
disabled={!canUsePhoneAuth}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
>
|
>
|
||||||
手机号快捷登录
|
手机号快捷登录
|
||||||
|
|||||||
@@ -4,9 +4,8 @@ import { View, Text } from '@tarojs/components'
|
|||||||
import { Button, Checkbox } from '@nutui/nutui-react-taro'
|
import { Button, Checkbox } from '@nutui/nutui-react-taro'
|
||||||
import { TenantId } from '@/config/app'
|
import { TenantId } from '@/config/app'
|
||||||
import { getUserInfo, getWxOpenId } from '@/api/layout'
|
import { getUserInfo, getWxOpenId } from '@/api/layout'
|
||||||
import { saveStorageByLoginUser, SERVER_API_URL } from '@/utils/server'
|
import { saveStorageByLoginUser } from '@/utils/server'
|
||||||
import { isUserDisabled } from '@/utils/auth'
|
import { isUserDisabled } from '@/utils/auth'
|
||||||
import request from '@/utils/request'
|
|
||||||
import {
|
import {
|
||||||
getStoredInviteParams,
|
getStoredInviteParams,
|
||||||
parseInviteParams,
|
parseInviteParams,
|
||||||
@@ -26,6 +25,17 @@ interface GetPhoneNumberEvent {
|
|||||||
detail: GetPhoneNumberDetail
|
detail: GetPhoneNumberDetail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LoginResponse {
|
||||||
|
data: {
|
||||||
|
code?: number
|
||||||
|
message?: string
|
||||||
|
data?: {
|
||||||
|
access_token: string
|
||||||
|
user: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getWeappLoginCode(): Promise<string | undefined> {
|
async function getWeappLoginCode(): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const res = await new Promise<{ code?: string }>((resolve, reject) => {
|
const res = await new Promise<{ code?: string }>((resolve, reject) => {
|
||||||
@@ -181,9 +191,10 @@ const Register = () => {
|
|||||||
// 获取小程序登录 code(用于后续绑定 openid)
|
// 获取小程序登录 code(用于后续绑定 openid)
|
||||||
const wxLoginCode = await getWeappLoginCode()
|
const wxLoginCode = await getWeappLoginCode()
|
||||||
|
|
||||||
const res: any = await request.post(
|
const res = (await Taro.request({
|
||||||
`${SERVER_API_URL}/wx-login/loginByMpWxPhone`,
|
url: 'https://shop-api.websoft.top/api/wx-login/loginByMpWxPhone',
|
||||||
{
|
method: 'POST',
|
||||||
|
data: {
|
||||||
code: phoneCode,
|
code: phoneCode,
|
||||||
encryptedData,
|
encryptedData,
|
||||||
iv,
|
iv,
|
||||||
@@ -192,16 +203,19 @@ const Register = () => {
|
|||||||
sceneType: 'save_referee',
|
sceneType: 'save_referee',
|
||||||
tenantId: TenantId,
|
tenantId: TenantId,
|
||||||
},
|
},
|
||||||
{ showError: false }
|
header: {
|
||||||
)
|
'content-type': 'application/json',
|
||||||
|
TenantId,
|
||||||
|
},
|
||||||
|
})) as unknown as LoginResponse
|
||||||
|
|
||||||
if (res?.code === 1) {
|
if ((res as any)?.data?.code === 1) {
|
||||||
Taro.showToast({ title: res.message || '登录失败', icon: 'none' })
|
Taro.showToast({ title: res.data.message || '登录失败', icon: 'none' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = res?.data?.access_token
|
const token = res?.data?.data?.access_token
|
||||||
const user = res?.data?.user
|
const user = res?.data?.data?.user
|
||||||
if (!token || !user?.userId) {
|
if (!token || !user?.userId) {
|
||||||
Taro.showToast({ title: '登录失败,请重试', icon: 'none' })
|
Taro.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||||
return
|
return
|
||||||
|
|||||||
35
src/utils/phoneAuth.ts
Normal file
35
src/utils/phoneAuth.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* getPhoneNumber 全局频率控制
|
||||||
|
*
|
||||||
|
* 微信原生层对 getPhoneNumber API 有调用频率限制,短时间内多次调用会报:
|
||||||
|
* [渲染层错误] invoke getPhoneNumber too frequently
|
||||||
|
*
|
||||||
|
* 本模块提供模块级(跨页面)的时间戳锁,配合各页面的 useRef 同步锁 + 条件渲染使用。
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** 上次 getPhoneNumber 回调触发的时间戳(ms) */
|
||||||
|
let lastPhoneAuthTime = 0
|
||||||
|
|
||||||
|
/** 冷却期(ms),在此期间不允许再次触发 getPhoneNumber */
|
||||||
|
const PHONE_AUTH_COOLDOWN = 3000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查当前是否在冷却期内。
|
||||||
|
* 在渲染 getPhoneNumber 按钮前调用此函数,若在冷却期内则不渲染 openType。
|
||||||
|
*/
|
||||||
|
export function isPhoneAuthCoolingDown(): boolean {
|
||||||
|
if (lastPhoneAuthTime === 0) return false
|
||||||
|
return Date.now() - lastPhoneAuthTime < PHONE_AUTH_COOLDOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 记录一次 getPhoneNumber 回调已触发(在 onGetPhoneNumber 回调入口调用) */
|
||||||
|
export function markPhoneAuthCalled(): void {
|
||||||
|
lastPhoneAuthTime = Date.now()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取冷却期剩余时间(ms),用于设置 unlock 延迟 */
|
||||||
|
export function getPhoneAuthCooldownRemaining(): number {
|
||||||
|
if (lastPhoneAuthTime === 0) return 0
|
||||||
|
const remaining = PHONE_AUTH_COOLDOWN - (Date.now() - lastPhoneAuthTime)
|
||||||
|
return remaining > 0 ? remaining : 0
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
* 必须在页面中渲染 <PrivacyModal /> 并绑定本管理器。
|
* 必须在页面中渲染 <PrivacyModal /> 并绑定本管理器。
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type PrivacyResolve = (result: { event: 'agree' | 'disagree'; button: string }) => void
|
type PrivacyResolve = (result: { event: 'agree' | 'disagree'; buttonId: string }) => void
|
||||||
|
|
||||||
let currentResolve: PrivacyResolve | null = null
|
let currentResolve: PrivacyResolve | null = null
|
||||||
let showCallback: ((show: boolean) => void) | null = null
|
let showCallback: ((show: boolean) => void) | null = null
|
||||||
@@ -31,16 +31,22 @@ export const privacyManager = {
|
|||||||
showCallback?.(true)
|
showCallback?.(true)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** 隐私协议授权流程已结束,隐藏弹窗 */
|
||||||
|
hide() {
|
||||||
|
currentResolve = null
|
||||||
|
showCallback?.(false)
|
||||||
|
},
|
||||||
|
|
||||||
/** 用户点击同意(由 open-type=agreePrivacyAuthorization 的 button 触发) */
|
/** 用户点击同意(由 open-type=agreePrivacyAuthorization 的 button 触发) */
|
||||||
agree() {
|
agree() {
|
||||||
currentResolve?.({ event: 'agree', button: 'agree' })
|
currentResolve?.({ event: 'agree', buttonId: 'privacy-agree-btn' })
|
||||||
currentResolve = null
|
currentResolve = null
|
||||||
showCallback?.(false)
|
showCallback?.(false)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 用户点击拒绝 */
|
/** 用户点击拒绝 */
|
||||||
disagree() {
|
disagree() {
|
||||||
currentResolve?.({ event: 'disagree', button: 'disagree' })
|
currentResolve?.({ event: 'disagree', buttonId: 'privacy-disagree-btn' })
|
||||||
currentResolve = null
|
currentResolve = null
|
||||||
showCallback?.(false)
|
showCallback?.(false)
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user