Files
template-10584/src/passport/sms-login.tsx
赵忠林 0d6eb331c8 feat(shop): 添加商品分享邀请功能
- 切换API基础URL到生产环境地址
- 在商品详情页添加邀请参数解析和存储逻辑
- 实现分享链接携带邀请者ID和来源信息
- 新增商品分享来源类型标识
- 在短信登录成功后处理待绑定的邀请关系
- 添加邀请关系跟踪和统计功能
2026-01-20 15:18:48 +08:00

215 lines
5.1 KiB
TypeScript

import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
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} from "@/utils/invite";
const SmsLogin = () => {
const [loading, setLoading] = useState<boolean>(false)
const [sendingCode, setSendingCode] = useState<boolean>(false)
const [countdown, setCountdown] = useState<number>(0)
const [formData, setFormData] = useState<LoginParam>({
phone: '',
code: ''
})
const reload = () => {
Taro.hideTabBar()
}
useEffect(() => {
reload()
}, [])
// 倒计时效果
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(() => {
Taro.reLaunch({
url: '/pages/index/index'
})
}, 1500)
} catch (error: any) {
Taro.showToast({
title: error.message || '登录失败',
icon: 'error'
})
} finally {
setLoading(false)
}
}
return (
<>
<div className={'flex flex-col justify-center px-5 pt-3'}>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input
type="number"
placeholder="请输入手机号码"
maxLength={11}
value={formData.phone}
onChange={(value) => setFormData({...formData, phone: value})}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
</div>
<div className={'flex justify-between items-center bg-white rounded-lg my-2 pr-2'}>
<Input
type="number"
placeholder="请输入6位验证码"
maxLength={6}
value={formData.code}
onChange={(value) => setFormData({...formData, code: value})}
style={{ backgroundColor: '#ffffff', borderRadius: '8px'}}
/>
<Button
size="small"
type={countdown > 0 ? "default" : "primary"}
loading={sendingCode}
disabled={sendingCode || countdown > 0}
onClick={handleSendCode}
>
{countdown > 0 ? `${countdown}s` : sendingCode ? '发送中...' : '获取验证码'}
</Button>
</div>
<div className={'flex justify-center my-5'}>
<Button
type="info"
size={'large'}
className={'w-full rounded-lg p-2'}
loading={loading}
disabled={loading}
onClick={handleLogin}
>
{loading ? '登录中...' : '登录'}
</Button>
</div>
</div>
</>
)
}
export default SmsLogin