feat(passport): 实现管理员短信登录功能
- 更新登录页面标题为"管理员登录" - 移除账号密码登录方式,仅保留短信验证码登录 - 新增手机号格式验证逻辑 - 集成短信验证码发送与登录接口- 添加图形验证码验证流程 - 实现短信验证码倒计时功能 - 更新用户中心跳转链接指向新的登录页 - 优化登录页面UI布局与样式
This commit is contained in:
@@ -250,10 +250,10 @@ const UserCell = () => {
|
|||||||
/>
|
/>
|
||||||
<Cell
|
<Cell
|
||||||
className="nutui-cell-clickable"
|
className="nutui-cell-clickable"
|
||||||
title="短信登录"
|
title="管理员登录"
|
||||||
align="center"
|
align="center"
|
||||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||||
onClick={() => navTo('/passport/sms-login', true)}
|
onClick={() => navTo('/passport/login', true)}
|
||||||
/>
|
/>
|
||||||
<Cell
|
<Cell
|
||||||
className="nutui-cell-clickable"
|
className="nutui-cell-clickable"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '登录',
|
navigationBarTitleText: '管理员登录',
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,56 +1,411 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {Input, Radio, Button} from '@nutui/nutui-react-taro'
|
import {Input, Radio, Button} from '@nutui/nutui-react-taro'
|
||||||
|
import {loginBySms, getCaptcha, sendSmsCaptcha} from '@/api/passport/login'
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const [isAgree, setIsAgree] = useState(false)
|
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 = () => {
|
const reload = () => {
|
||||||
Taro.hideTabBar()
|
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(() => {
|
useEffect(() => {
|
||||||
reload()
|
reload()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'flex flex-col justify-center px-5'}>
|
<div style={{
|
||||||
<div className={'text-3xl text-center py-5 font-normal my-10'}>账号登录</div>
|
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'}>
|
<div style={{
|
||||||
<Input type="text" placeholder="手机号" maxLength={11}
|
display: 'none', // 隐藏登录方式切换
|
||||||
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
|
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>
|
||||||
<div className={'flex flex-col justify-between items-center my-2'}>
|
<div
|
||||||
<Input type="password" placeholder="密码" style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
|
style={{
|
||||||
|
padding: '10px 20px',
|
||||||
|
borderBottom: loginType === 'sms' ? '2px solid #1890ff' : 'none',
|
||||||
|
color: loginType === 'sms' ? '#1890ff' : '#999',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
onClick={() => setLoginType('sms')}
|
||||||
|
>
|
||||||
|
短信登录
|
||||||
</div>
|
</div>
|
||||||
<div className={'flex justify-between my-2 text-left px-1'}>
|
</div>
|
||||||
<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>
|
<div>
|
||||||
<span className={'text-gray-400'} onClick={() => setIsAgree(!isAgree)}>勾选表示您已阅读并同意</span><a
|
<div style={{
|
||||||
onClick={() => Taro.navigateTo({url: '/passport/agreement'})}
|
display: 'flex',
|
||||||
className={'text-blue-600'}>《服务协议及隐私政策》</a>
|
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'})}
|
||||||
|
style={{color: '#1890ff'}}
|
||||||
|
>
|
||||||
|
《服务协议及隐私政策》
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</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
|
export default Login
|
||||||
|
|||||||
Reference in New Issue
Block a user