feat(passport): 优化手机号授权流程及超时处理机制

- 新增手机号授权等待状态及定时器,防止重复触发授权操作
- 增加手机号授权超时弹窗提示,支持切换短信验证码登录
- 修改手机号授权冷却时间由3000ms调整为8000ms,减少频繁操作
- 控制手机号授权按钮状态,确保用户协议同意且无冷却或等待中才能操作
- 清理手机号授权等待定时器,防止内存泄露
- 调整加载状态时按钮UI表现,提高用户交互体验
This commit is contained in:
2026-07-16 21:33:13 +08:00
parent 46298c011f
commit 8d46016d8b
2 changed files with 59 additions and 6 deletions

View File

@@ -105,14 +105,17 @@ 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 [phoneAuthPending, setPhoneAuthPending] = useState(false)
const [phoneAuthCooling, setPhoneAuthCooling] = useState(isPhoneAuthCoolingDown()) const [phoneAuthCooling, setPhoneAuthCooling] = useState(isPhoneAuthCoolingDown())
const [showContent, setShowContent] = useState(false) const [showContent, setShowContent] = useState(false)
const phoneAuthInFlightRef = useRef(false) const phoneAuthInFlightRef = useRef(false)
const phoneAuthPendingRef = useRef(false)
const phoneAuthTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null) const phoneAuthTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const phoneAuthPendingTimerRef = 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 const canUsePhoneAuth = isAgree && !loading && !phoneAuthPending && !phoneAuthCooling
/** 页面加载动画 */ /** 页面加载动画 */
useEffect(() => { useEffect(() => {
@@ -125,6 +128,10 @@ const Login = () => {
clearTimeout(phoneAuthTimerRef.current) clearTimeout(phoneAuthTimerRef.current)
phoneAuthTimerRef.current = null phoneAuthTimerRef.current = null
} }
if (phoneAuthPendingTimerRef.current) {
clearTimeout(phoneAuthPendingTimerRef.current)
phoneAuthPendingTimerRef.current = null
}
} }
}, []) }, [])
@@ -138,6 +145,49 @@ const Login = () => {
}, remaining) }, remaining)
} }
const clearPhoneAuthPending = () => {
phoneAuthPendingRef.current = false
setPhoneAuthPending(false)
if (phoneAuthPendingTimerRef.current) {
clearTimeout(phoneAuthPendingTimerRef.current)
phoneAuthPendingTimerRef.current = null
}
}
const startPhoneAuthPending = () => {
phoneAuthPendingRef.current = true
setPhoneAuthPending(true)
markPhoneAuthCalled()
startPhoneAuthCooldown()
if (phoneAuthPendingTimerRef.current) clearTimeout(phoneAuthPendingTimerRef.current)
phoneAuthPendingTimerRef.current = setTimeout(() => {
clearPhoneAuthPending()
Taro.showModal({
title: '手机号授权超时',
content: '微信手机号授权暂时无响应,请稍后重试或改用短信验证码登录。',
confirmText: '短信登录',
cancelText: '稍后重试',
confirmColor: '#07c160',
success: (res) => {
if (res.confirm) goSmsLogin()
},
})
}, 12000)
}
const handlePhoneAuthTap = () => {
if (!isAgree) {
Taro.showToast({ title: '请先勾选同意协议', icon: 'none' })
return
}
if (loading || phoneAuthInFlightRef.current || phoneAuthPendingRef.current || isPhoneAuthCoolingDown()) {
startPhoneAuthCooldown()
Taro.showToast({ title: '请稍后再试', icon: 'none' })
return
}
startPhoneAuthPending()
}
/** 解析 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
@@ -226,18 +276,19 @@ const Login = () => {
/** 手机号快捷登录 */ /** 手机号快捷登录 */
const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => { const handleGetPhoneNumber = async ({ detail }: GetPhoneNumberEvent) => {
clearPhoneAuthPending()
if (!isAgree) { if (!isAgree) {
Taro.showToast({ title: '请先勾选同意协议', icon: 'none' }) Taro.showToast({ title: '请先勾选同意协议', icon: 'none' })
return return
} }
if (loading || phoneAuthInFlightRef.current || isPhoneAuthCoolingDown()) { if (loading || phoneAuthInFlightRef.current) {
startPhoneAuthCooldown() startPhoneAuthCooldown()
Taro.showToast({ title: '请稍后再试', icon: 'none' }) Taro.showToast({ title: '请稍后再试', icon: 'none' })
return return
} }
phoneAuthInFlightRef.current = true phoneAuthInFlightRef.current = true
markPhoneAuthCalled()
startPhoneAuthCooldown() startPhoneAuthCooldown()
const { code: phoneCode, encryptedData, iv, errMsg } = detail || {} const { code: phoneCode, encryptedData, iv, errMsg } = detail || {}
@@ -312,6 +363,7 @@ const Login = () => {
Taro.showToast({ title: e?.message || '登录失败', icon: 'none' }) Taro.showToast({ title: e?.message || '登录失败', icon: 'none' })
} finally { } finally {
phoneAuthInFlightRef.current = false phoneAuthInFlightRef.current = false
clearPhoneAuthPending()
setLoading(false) setLoading(false)
} }
} }
@@ -351,14 +403,15 @@ const Login = () => {
color: '#ffffff', color: '#ffffff',
fontWeight: '600', fontWeight: '600',
textAlign: 'center', textAlign: 'center',
opacity: (!isAgree || loading) ? 0.5 : 1, opacity: canUsePhoneAuth ? 1 : 0.5,
margin: 0, margin: 0,
padding: 0, padding: 0,
}} }}
openType={canUsePhoneAuth ? 'getPhoneNumber' : undefined} openType={canUsePhoneAuth ? 'getPhoneNumber' : undefined}
onClick={handlePhoneAuthTap}
onGetPhoneNumber={handleGetPhoneNumber} onGetPhoneNumber={handleGetPhoneNumber}
disabled={!canUsePhoneAuth} disabled={!canUsePhoneAuth}
loading={loading} loading={loading || phoneAuthPending}
> >
</Button> </Button>

View File

@@ -11,7 +11,7 @@
let lastPhoneAuthTime = 0 let lastPhoneAuthTime = 0
/** 冷却期ms在此期间不允许再次触发 getPhoneNumber */ /** 冷却期ms在此期间不允许再次触发 getPhoneNumber */
const PHONE_AUTH_COOLDOWN = 3000 const PHONE_AUTH_COOLDOWN = 8000
/** /**
* 检查当前是否在冷却期内。 * 检查当前是否在冷却期内。