fix: 获取验证码有问题

This commit is contained in:
郭文文 2026-01-26 17:56:46 +08:00
parent 8fc4f0c7e6
commit 8f88302624
2 changed files with 74 additions and 33 deletions

View File

@ -1,13 +1,13 @@
import { Ionicons } from '@expo/vector-icons' import { Ionicons } from '@expo/vector-icons'
import { Block, Text, Toast } from '@share/components' import { Block, Text, Toast } from '@share/components'
import { router } from 'expo-router' import { router } from 'expo-router'
import React, { useCallback, useMemo, useState } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { TextInput } from 'react-native' import { TextInput } from 'react-native'
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller' import { KeyboardAwareScrollView } from 'react-native-keyboard-controller'
import { APP_VERSION } from '@/app.config' import { APP_VERSION } from '@/app.config'
import BannerSection from '@/components/BannerSection' import BannerSection from '@/components/BannerSection'
import { phoneNumber, setAuthToken } from '@/lib/auth' import { phoneNumber } from '@/lib/auth'
import { isValidPhone } from '@/utils' import { isValidPhone } from '@/utils'
import { openUrl } from '@/utils/webview-helper' import { openUrl } from '@/utils/webview-helper'
@ -19,30 +19,49 @@ export default function Auth() {
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [countdown, setCountdown] = useState(0) const [countdown, setCountdown] = useState(0)
const [agreed, setAgreed] = useState(false) const [agreed, setAgreed] = useState(false)
const countdownTimerRef = useRef<ReturnType<typeof setInterval> | null>(null)
const canSendCode = useMemo(() => { const canSendCode = useMemo(() => {
return isValidPhone(phone) return isValidPhone(phone) && countdown === 0
}, [phone]) }, [phone, countdown])
// TODO: 获取验证码接口还未实现,需要后端提供手机号验证码发送接口 // 清理倒计时定时器
useEffect(() => {
return () => {
if (countdownTimerRef.current) {
clearInterval(countdownTimerRef.current)
}
}
}, [])
// 获取验证码
const handleSendCode = useCallback(async () => { const handleSendCode = useCallback(async () => {
if (!canSendCode) { if (!isValidPhone(phone)) {
Toast.show({ title: '请输入有效的手机号' }) Toast.show({ title: '请输入有效的手机号' })
return return
} }
Toast.showLoading({ title: '正在发送验证码...' }) if (countdown > 0) {
return
}
Toast.showLoading({ title: '正在获取验证码...' })
setLoading(true) setLoading(true)
try { try {
// TODO: 调用获取验证码接口
// 当前使用 better-auth 的 phoneNumber.sendOtp但后端可能还未实现
// 如果后端未实现,这里会报错,需要后端提供手机号验证码发送接口
const result = await phoneNumber.sendOtp({ const result = await phoneNumber.sendOtp({
phoneNumber: phone, phoneNumber: phone,
}) })
if (result.error) { if (result.error) {
Toast.show({ title: result.error.message || '验证码发送失败,请检查后端接口是否已实现' }) // 处理不同类型的错误
const errorMessage = result.error.message || '验证码发送失败'
// 如果是 403 错误,可能是请求过于频繁
if (result.error.status === 403) {
Toast.show({ title: '请求过于频繁,请稍后再试' })
} else {
Toast.show({ title: errorMessage })
}
return return
} }
@ -50,23 +69,31 @@ export default function Auth() {
setCountdown(60) // 开始倒计时 setCountdown(60) // 开始倒计时
// 倒计时逻辑 // 倒计时逻辑
const timer = setInterval(() => { if (countdownTimerRef.current) {
clearInterval(countdownTimerRef.current)
}
countdownTimerRef.current = setInterval(() => {
setCountdown((prev) => { setCountdown((prev) => {
if (prev <= 1) { if (prev <= 1) {
clearInterval(timer) if (countdownTimerRef.current) {
clearInterval(countdownTimerRef.current)
countdownTimerRef.current = null
}
return 0 return 0
} }
return prev - 1 return prev - 1
}) })
}, 1000) }, 1000)
} catch (error: any) { } catch (error: any) {
Toast.show({ title: error.message || '验证码发送失败' }) console.error('获取验证码失败:', error)
Toast.show({ title: error.message || '验证码发送失败,请稍后重试' })
} finally { } finally {
Toast.hideLoading() Toast.hideLoading()
setLoading(false) setLoading(false)
} }
}, [phone, canSendCode]) }, [phone, countdown])
// 验证码登录/注册
const handleLogin = useCallback(async () => { const handleLogin = useCallback(async () => {
if (!phone || !code) { if (!phone || !code) {
Toast.show({ title: '请填写手机号和验证码' }) Toast.show({ title: '请填写手机号和验证码' })
@ -78,15 +105,19 @@ export default function Auth() {
return return
} }
if (code.length !== 6) {
Toast.show({ title: '请输入6位验证码' })
return
}
if (!agreed) { if (!agreed) {
Toast.show({ title: '请先阅读并同意服务条款和隐私协议' }) Toast.show({ title: '请先阅读并同意服务条款和隐私协议' })
return return
} }
setLoading(true) setLoading(true)
Toast.showLoading({ title: '正在验证...' })
try { try {
// 使用 better-auth 的 phoneNumber.verify 进行验证和登录
// verify 方法会自动创建会话,如果用户不存在会自动注册
const result = await phoneNumber.verify({ const result = await phoneNumber.verify({
phoneNumber: phone, phoneNumber: phone,
code, code,
@ -94,21 +125,35 @@ export default function Auth() {
}) })
if (result.error) { if (result.error) {
Toast.show({ title: result.error.message || '验证码错误或已过期' }) const errorStatus = result.error.status
const errorMessage = result.error.message || '验证失败'
if (errorStatus === 403) {
Toast.show({ title: '验证码尝试次数过多,请重新获取验证码' })
setCode('') // 清空验证码
setCountdown(0) // 重置倒计时,允许重新发送
if (countdownTimerRef.current) {
clearInterval(countdownTimerRef.current)
countdownTimerRef.current = null
}
} else if (errorStatus === 401) {
Toast.show({ title: '手机号未验证,请先验证手机号' })
} else {
Toast.show({ title: errorMessage })
}
return return
} }
// 获取认证 token如果后端返回
// better-auth 会自动处理 session但如果有自定义 token需要手动设置
if (result.data && 'token' in result.data && result.data.token) {
await setAuthToken(result.data.token)
}
Toast.show({ title: '登录成功!' }) Toast.show({ title: '登录成功!' })
router.replace('/(tabs)')
// 延迟跳转,确保 toast 显示
setTimeout(() => {
router.replace('/(tabs)')
}, 500)
} catch (error: any) { } catch (error: any) {
Toast.show({ title: error.message || '登录失败' }) console.error('登录失败:', error)
Toast.show({ title: error.message || '登录失败,请稍后重试' })
} finally { } finally {
Toast.hideLoading()
setLoading(false) setLoading(false)
} }
}, [phone, code, agreed]) }, [phone, code, agreed])
@ -160,7 +205,7 @@ export default function Auth() {
className={`border-2 border-black px-[6px] py-[4px] ${canSendCode && countdown === 0 ? 'bg-black' : 'bg-gray-200'}`} className={`border-2 border-black px-[6px] py-[4px] ${canSendCode && countdown === 0 ? 'bg-black' : 'bg-gray-200'}`}
> >
<Text className={`text-[10px] font-[900] ${canSendCode && countdown === 0 ? 'text-accent' : 'text-gray-500'}`}> <Text className={`text-[10px] font-[900] ${canSendCode && countdown === 0 ? 'text-accent' : 'text-gray-500'}`}>
{countdown > 0 ? `${countdown}` : '发送验证码'} {countdown > 0 ? `${countdown}` : '获取验证码'}
</Text> </Text>
</Block> </Block>
</Block> </Block>
@ -225,7 +270,7 @@ export default function Auth() {
) : ( ) : (
<Ionicons color="black" name="flash" size={20} /> <Ionicons color="black" name="flash" size={20} />
)} )}
<Text className="font-900 text-[16px] text-black">{loading ? '处理中...' : '登录'}</Text> <Text className="font-900 text-[16px] text-black">{loading ? '验证中...' : '登录/注册'}</Text>
</Block> </Block>
</Block> </Block>
</Block> </Block>

View File

@ -127,9 +127,5 @@ export const {
phoneNumber, phoneNumber,
} = authClient } = authClient
// TODO: 手机号验证码发送接口还未实现
// 需要后端提供手机号验证码发送接口,类似于 emailOtp.sendVerificationOtp
// 预期接口phoneOtp.sendVerificationOtp({ phone, type: 'phone-verification' })
// 当前在 app/auth.tsx 中的 handleSendCode 函数中使用了临时模拟代码,需要替换为真实 API 调用
export const subscription: ISubscription = Reflect.get(authClient, 'subscription') export const subscription: ISubscription = Reflect.get(authClient, 'subscription')