feat(passport): 实现管理员短信登录功能
- 更新登录页面标题为"管理员登录" - 移除账号密码登录方式,仅保留短信验证码登录 - 新增手机号格式验证逻辑 - 集成短信验证码发送与登录接口- 添加图形验证码验证流程 - 实现短信验证码倒计时功能 - 更新用户中心跳转链接指向新的登录页 - 优化登录页面UI布局与样式
This commit is contained in:
@@ -250,10 +250,10 @@ const UserCell = () => {
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="短信登录"
|
||||
title="管理员登录"
|
||||
align="center"
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => navTo('/passport/sms-login', true)}
|
||||
onClick={() => navTo('/passport/login', true)}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '登录',
|
||||
navigationBarTitleText: '管理员登录',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
|
||||
@@ -1,56 +1,411 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Input, Radio, Button} from '@nutui/nutui-react-taro'
|
||||
import {loginBySms, getCaptcha, sendSmsCaptcha} from '@/api/passport/login'
|
||||
|
||||
const Login = () => {
|
||||
const [isAgree, setIsAgree] = useState(false)
|
||||
// 只保留短信登录方式
|
||||
const [loginType, setLoginType] = useState('sms')
|
||||
// const [username, setUsername] = useState('')
|
||||
// const [password, setPassword] = useState('')
|
||||
const [phone, setPhone] = useState('')
|
||||
const [smsCode, setSmsCode] = useState('')
|
||||
const [captchaImg, setCaptchaImg] = useState('')
|
||||
const [captchaCode, setCaptchaCode] = useState('')
|
||||
const [showCaptchaModal, setShowCaptchaModal] = useState(false)
|
||||
const [countdown, setCountdown] = useState(0) // 短信验证码倒计时
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const reload = () => {
|
||||
Taro.hideTabBar()
|
||||
}
|
||||
|
||||
// 获取图形验证码
|
||||
const fetchCaptcha = async () => {
|
||||
try {
|
||||
const res = await getCaptcha()
|
||||
setCaptchaImg(res.base64)
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: '获取验证码失败',
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
const handleSendSmsCode = async () => {
|
||||
if (!phone) {
|
||||
Taro.showToast({
|
||||
title: '请输入手机号',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
const phoneReg = /^1[3-9]\d{9}$/
|
||||
if (!phoneReg.test(phone)) {
|
||||
Taro.showToast({
|
||||
title: '手机号格式不正确',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 显示图形验证码弹窗
|
||||
fetchCaptcha()
|
||||
setShowCaptchaModal(true)
|
||||
}
|
||||
|
||||
// 确认发送短信验证码
|
||||
const confirmSendSmsCode = async () => {
|
||||
if (!captchaCode) {
|
||||
Taro.showToast({
|
||||
title: '请输入图形验证码',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
// 发送短信验证码时需要传入手机号和图形验证码
|
||||
await sendSmsCaptcha({ phone, code: captchaCode })
|
||||
Taro.showToast({
|
||||
title: '短信验证码已发送',
|
||||
icon: 'success'
|
||||
})
|
||||
setShowCaptchaModal(false)
|
||||
setCaptchaCode('')
|
||||
|
||||
// 开始倒计时
|
||||
setCountdown(60)
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: error.message || '发送失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 短信验证码登录
|
||||
const handleSmsLogin = async () => {
|
||||
if (!phone) {
|
||||
Taro.showToast({
|
||||
title: '请输入手机号',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证手机号格式
|
||||
const phoneReg = /^1[3-9]\d{9}$/
|
||||
if (!phoneReg.test(phone)) {
|
||||
Taro.showToast({
|
||||
title: '手机号格式不正确',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (!smsCode) {
|
||||
Taro.showToast({
|
||||
title: '请输入短信验证码',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true)
|
||||
// 短信登录时传入手机号和短信验证码
|
||||
const res = await loginBySms({ phone, code: smsCode })
|
||||
|
||||
console.log(res,'.......')
|
||||
Taro.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success'
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
setTimeout(() => {
|
||||
Taro.switchTab({ url: '/pages/index/index' })
|
||||
}, 1500)
|
||||
} catch (error) {
|
||||
Taro.showToast({
|
||||
title: error.message || '登录失败',
|
||||
icon: 'error'
|
||||
})
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
// 登录处理
|
||||
const onLogin = async () => {
|
||||
if (!isAgree) {
|
||||
Taro.showToast({
|
||||
title: '请先同意服务协议',
|
||||
icon: 'error'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
handleSmsLogin()
|
||||
}
|
||||
|
||||
// 倒计时处理
|
||||
useEffect(() => {
|
||||
let timer: any
|
||||
if (countdown > 0) {
|
||||
timer = setTimeout(() => {
|
||||
setCountdown(countdown - 1)
|
||||
}, 1000)
|
||||
}
|
||||
return () => clearTimeout(timer)
|
||||
}, [countdown])
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'flex flex-col justify-center px-5'}>
|
||||
<div className={'text-3xl text-center py-5 font-normal my-10'}>账号登录</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
padding: '0 20px',
|
||||
minHeight: '70vh',
|
||||
backgroundColor: '#f5f5f5'
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: '24px',
|
||||
textAlign: 'center',
|
||||
padding: '20px 0',
|
||||
fontWeight: 'normal',
|
||||
margin: '20px 0 20px 0'
|
||||
}}>管理员登录</div>
|
||||
|
||||
<>
|
||||
<div className={'flex flex-col justify-between items-center my-2'}>
|
||||
<Input type="text" placeholder="手机号" maxLength={11}
|
||||
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
|
||||
{/* 登录方式切换 - 隐藏账号登录 */}
|
||||
<div style={{
|
||||
display: 'none', // 隐藏登录方式切换
|
||||
justifyContent: 'center',
|
||||
marginBottom: '20px'
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
borderBottom: loginType === 'account' ? '2px solid #1890ff' : 'none',
|
||||
color: loginType === 'account' ? '#1890ff' : '#999',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => setLoginType('account')}
|
||||
>
|
||||
账号登录
|
||||
</div>
|
||||
<div className={'flex flex-col justify-between items-center my-2'}>
|
||||
<Input type="password" placeholder="密码" style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
|
||||
<div
|
||||
style={{
|
||||
padding: '10px 20px',
|
||||
borderBottom: loginType === 'sms' ? '2px solid #1890ff' : 'none',
|
||||
color: loginType === 'sms' ? '#1890ff' : '#999',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={() => setLoginType('sms')}
|
||||
>
|
||||
短信登录
|
||||
</div>
|
||||
<div className={'flex justify-between my-2 text-left px-1'}>
|
||||
<a href={'#'} className={'text-blue-600 text-sm'}
|
||||
onClick={() => Taro.navigateTo({url: '/passport/forget'})}>忘记密码</a>
|
||||
<a href={'#'} className={'text-blue-600 text-sm'}
|
||||
onClick={() => Taro.navigateTo({url: '/passport/register'})}>立即注册</a>
|
||||
</div>
|
||||
<div className={'flex justify-center my-5'}>
|
||||
<Button type="info" size={'large'} className={'w-full rounded-lg p-2'} disabled={!isAgree}>登录</Button>
|
||||
</div>
|
||||
<div className={'my-2 flex fixed justify-center bottom-20 left-0 text-sm items-center text-center w-full'}>
|
||||
<Button onClick={() => Taro.navigateTo({url: '/passport/setting'})}>服务配置</Button>
|
||||
</div>
|
||||
{/*<div className={'w-full fixed bottom-20 my-2 flex justify-center text-sm items-center text-center'}>*/}
|
||||
{/* 没有账号?<a href={''} onClick={() => Taro.navigateTo({url: '/passport/register'})}*/}
|
||||
{/* className={'text-blue-600'}>立即注册</a>*/}
|
||||
{/*</div>*/}
|
||||
</>
|
||||
|
||||
<div className={'my-2 flex text-sm items-center px-1'}>
|
||||
<Radio style={{color: '#333333'}} checked={isAgree} onClick={() => setIsAgree(!isAgree)}></Radio>
|
||||
<span className={'text-gray-400'} onClick={() => setIsAgree(!isAgree)}>勾选表示您已阅读并同意</span><a
|
||||
{/* 短信验证码登录 - 始终显示 */}
|
||||
<div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
margin: '10px 0'
|
||||
}}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="手机号"
|
||||
maxLength={11}
|
||||
value={phone}
|
||||
onChange={(val) => setPhone(val)}
|
||||
style={{
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px',
|
||||
width: '100%',
|
||||
padding: '10px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
margin: '10px 0'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: '8px'
|
||||
}}>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="短信验证码"
|
||||
maxLength={6}
|
||||
value={smsCode}
|
||||
onChange={(val) => setSmsCode(val)}
|
||||
style={{
|
||||
flex: 1,
|
||||
border: 'none',
|
||||
padding: '10px'
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="info"
|
||||
size="small"
|
||||
disabled={countdown > 0}
|
||||
onClick={handleSendSmsCode}
|
||||
style={{
|
||||
borderRadius: '0 8px 8px 0',
|
||||
height: '40px'
|
||||
}}
|
||||
>
|
||||
{countdown > 0 ? `${countdown}秒后重发` : '获取验证码'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
margin: '20px 0'
|
||||
}}>
|
||||
<Button
|
||||
type="info"
|
||||
size={'large'}
|
||||
style={{
|
||||
width: '100%',
|
||||
borderRadius: '8px',
|
||||
padding: '10px'
|
||||
}}
|
||||
disabled={!isAgree}
|
||||
loading={loading}
|
||||
onClick={onLogin}
|
||||
>
|
||||
立即登录
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: '0 5px',
|
||||
margin: '10px 0'
|
||||
}}>
|
||||
<Radio
|
||||
style={{color: '#333333'}}
|
||||
checked={isAgree}
|
||||
onClick={() => setIsAgree(!isAgree)}
|
||||
/>
|
||||
<span style={{color: '#999', marginLeft: '5px'}} onClick={() => setIsAgree(!isAgree)}>勾选表示您已阅读并同意</span>
|
||||
<a
|
||||
onClick={() => Taro.navigateTo({url: '/passport/agreement'})}
|
||||
className={'text-blue-600'}>《服务协议及隐私政策》</a>
|
||||
style={{color: '#1890ff'}}
|
||||
>
|
||||
《服务协议及隐私政策》
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 图形验证码弹窗 */}
|
||||
{showCaptchaModal && (
|
||||
<div style={{
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 9999
|
||||
}}>
|
||||
<div style={{
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '8px',
|
||||
padding: '20px',
|
||||
width: '80%'
|
||||
}}>
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '15px',
|
||||
fontSize: '16px'
|
||||
}}>请输入图形验证码</div>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '15px'
|
||||
}}>
|
||||
{captchaImg && (
|
||||
<img
|
||||
src={`data:image/png;base64,${captchaImg}`}
|
||||
alt="验证码"
|
||||
style={{
|
||||
width: '128px',
|
||||
height: '48px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
onClick={fetchCaptcha}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="请输入验证码"
|
||||
value={captchaCode}
|
||||
onChange={(val) => setCaptchaCode(val)}
|
||||
style={{
|
||||
marginBottom: '15px'
|
||||
}}
|
||||
/>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
<Button
|
||||
type="default"
|
||||
onClick={() => {
|
||||
setShowCaptchaModal(false)
|
||||
setCaptchaCode('')
|
||||
}}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
<Button
|
||||
type="info"
|
||||
loading={loading}
|
||||
onClick={confirmSendSmsCode}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Login
|
||||
|
||||
Reference in New Issue
Block a user