import {useEffect, useState} from "react"; import Taro from '@tarojs/taro' import { View } from '@tarojs/components' import {Input, Button} from '@nutui/nutui-react-taro' import {loginBySms, sendSmsCaptcha} from "@/api/passport/login"; import {LoginParam} from "@/api/passport/login/model"; import {checkAndHandleInviteRelation, hasPendingInvite, parseInviteParams, saveInviteParams, trackInviteSource} from "@/utils/invite"; const SmsLogin = () => { const [loading, setLoading] = useState(false) const [sendingCode, setSendingCode] = useState(false) const [countdown, setCountdown] = useState(0) const [formData, setFormData] = useState({ phone: '', code: '' }) const router = Taro.getCurrentInstance().router const redirectParam = (router?.params as any)?.redirect as string | undefined const safeDecodeMaybeEncoded = (input?: string) => { if (!input) return '' try { return decodeURIComponent(input) } catch (_e) { return input } } const redirectUrl = (() => { const decoded = safeDecodeMaybeEncoded(redirectParam) if (!decoded) return '' return decoded.startsWith('/') ? decoded : `/${decoded}` })() const isTabBarUrl = (url: string) => { const pure = url.split('?')[0] return ( pure === '/pages/index/index' || pure === '/pages/cart/cart' || pure === '/pages/user/user' || pure === '/pages/category/index' ) } const navigateAfterLogin = async () => { if (!redirectUrl) { await Taro.reLaunch({ url: '/pages/index/index' }) return } if (isTabBarUrl(redirectUrl)) { await Taro.switchTab({ url: redirectUrl.split('?')[0] }) return } await Taro.redirectTo({ url: redirectUrl }) } const reload = () => { // sms-login 页面不是 tabBar 页面,无需调用 hideTabBar } useEffect(() => { reload() }, []) // 如果从分享/二维码链接进入短信登录页,先暂存邀请信息 useEffect(() => { try { const inviteParams = parseInviteParams({ query: router?.params }) if (inviteParams?.inviter) { saveInviteParams(inviteParams) trackInviteSource(inviteParams.source || 'share', parseInt(inviteParams.inviter, 10)) } } catch (e) { console.error('短信登录页处理邀请参数失败:', e) } }, [router?.params]) // 倒计时效果 useEffect(() => { let timer: NodeJS.Timeout if (countdown > 0) { timer = setTimeout(() => { setCountdown(countdown - 1) }, 1000) } return () => { if (timer) clearTimeout(timer) } }, [countdown]) // 验证手机号格式 const validatePhone = (phone: string): boolean => { const phoneRegex = /^1[3-9]\d{9}$/ return phoneRegex.test(phone) } // 发送短信验证码 const handleSendCode = async () => { if (!formData.phone) { Taro.showToast({ title: '请输入手机号码', icon: 'none' }) return } if (!validatePhone(formData.phone)) { Taro.showToast({ title: '请输入正确的手机号码', icon: 'none' }) return } if (sendingCode || countdown > 0) { return } try { setSendingCode(true) await sendSmsCaptcha({ phone: formData.phone }) Taro.showToast({ title: '验证码已发送', icon: 'success' }) // 开始60秒倒计时 setCountdown(60) } catch (error: any) { Taro.showToast({ title: error.message || '发送失败', icon: 'error' }) } finally { setSendingCode(false) } } // 处理登录 const handleLogin = async () => { // 防止重复提交 if (loading) { return } // 表单验证 if (!formData.phone) { Taro.showToast({ title: '请输入手机号码', icon: 'none' }) return } if (!validatePhone(formData.phone)) { Taro.showToast({ title: '请输入正确的手机号码', icon: 'none' }) return } if (!formData.code) { Taro.showToast({ title: '请输入验证码', icon: 'none' }) return } if (formData.code.length !== 6) { Taro.showToast({ title: '请输入6位验证码', icon: 'none' }) return } try { setLoading(true) await loginBySms({ phone: formData.phone, code: formData.code }) // 登录成功后(可能是新注册用户),检查是否存在待处理的邀请关系并尝试绑定 if (hasPendingInvite()) { try { await checkAndHandleInviteRelation() } catch (e) { console.error('短信登录后处理邀请关系失败:', e) } } Taro.showToast({ title: '登录成功', icon: 'success' }) // 延迟跳转到首页 setTimeout(() => { navigateAfterLogin().catch((e) => { console.error('短信登录后跳转失败:', e) Taro.reLaunch({ url: '/pages/index/index' }) }) }, 1500) } catch (error: any) { Taro.showToast({ title: error.message || '登录失败', icon: 'error' }) } finally { setLoading(false) } } return ( <> setFormData({...formData, phone: value})} style={{backgroundColor: '#ffffff', borderRadius: '8px'}} /> setFormData({...formData, code: value})} style={{ backgroundColor: '#ffffff', borderRadius: '8px'}} /> ) } export default SmsLogin