|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { API, showError, showSuccess } from '../../helpers'; |
|
|
import { |
|
|
Button, |
|
|
Card, |
|
|
Divider, |
|
|
Form, |
|
|
Input, |
|
|
Typography, |
|
|
} from '@douyinfe/semi-ui'; |
|
|
import React, { useState } from 'react'; |
|
|
|
|
|
const { Title, Text, Paragraph } = Typography; |
|
|
|
|
|
const TwoFAVerification = ({ onSuccess, onBack, isModal = false }) => { |
|
|
const [loading, setLoading] = useState(false); |
|
|
const [useBackupCode, setUseBackupCode] = useState(false); |
|
|
const [verificationCode, setVerificationCode] = useState(''); |
|
|
|
|
|
const handleSubmit = async () => { |
|
|
if (!verificationCode) { |
|
|
showError('请输入验证码'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (useBackupCode && verificationCode.length !== 8) { |
|
|
showError('备用码必须是8位'); |
|
|
return; |
|
|
} else if (!useBackupCode && !/^\d{6}$/.test(verificationCode)) { |
|
|
showError('验证码必须是6位数字'); |
|
|
return; |
|
|
} |
|
|
|
|
|
setLoading(true); |
|
|
try { |
|
|
const res = await API.post('/api/user/login/2fa', { |
|
|
code: verificationCode, |
|
|
}); |
|
|
|
|
|
if (res.data.success) { |
|
|
showSuccess('登录成功'); |
|
|
|
|
|
localStorage.setItem('user', JSON.stringify(res.data.data)); |
|
|
if (onSuccess) { |
|
|
onSuccess(res.data.data); |
|
|
} |
|
|
} else { |
|
|
showError(res.data.message); |
|
|
} |
|
|
} catch (error) { |
|
|
showError('验证失败,请重试'); |
|
|
} finally { |
|
|
setLoading(false); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleKeyPress = (e) => { |
|
|
if (e.key === 'Enter') { |
|
|
handleSubmit(); |
|
|
} |
|
|
}; |
|
|
|
|
|
if (isModal) { |
|
|
return ( |
|
|
<div className='space-y-4'> |
|
|
<Paragraph className='text-gray-600 dark:text-gray-300'> |
|
|
请输入认证器应用显示的验证码完成登录 |
|
|
</Paragraph> |
|
|
|
|
|
<Form onSubmit={handleSubmit}> |
|
|
<Form.Input |
|
|
field='code' |
|
|
label={useBackupCode ? '备用码' : '验证码'} |
|
|
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'} |
|
|
value={verificationCode} |
|
|
onChange={setVerificationCode} |
|
|
onKeyPress={handleKeyPress} |
|
|
size='large' |
|
|
style={{ marginBottom: 16 }} |
|
|
autoFocus |
|
|
/> |
|
|
|
|
|
<Button |
|
|
htmlType='submit' |
|
|
type='primary' |
|
|
loading={loading} |
|
|
block |
|
|
size='large' |
|
|
style={{ marginBottom: 16 }} |
|
|
> |
|
|
验证并登录 |
|
|
</Button> |
|
|
</Form> |
|
|
|
|
|
<Divider /> |
|
|
|
|
|
<div style={{ textAlign: 'center' }}> |
|
|
<Button |
|
|
theme='borderless' |
|
|
type='tertiary' |
|
|
onClick={() => { |
|
|
setUseBackupCode(!useBackupCode); |
|
|
setVerificationCode(''); |
|
|
}} |
|
|
style={{ marginRight: 16, color: '#1890ff', padding: 0 }} |
|
|
> |
|
|
{useBackupCode ? '使用认证器验证码' : '使用备用码'} |
|
|
</Button> |
|
|
|
|
|
{onBack && ( |
|
|
<Button |
|
|
theme='borderless' |
|
|
type='tertiary' |
|
|
onClick={onBack} |
|
|
style={{ color: '#1890ff', padding: 0 }} |
|
|
> |
|
|
返回登录 |
|
|
</Button> |
|
|
)} |
|
|
</div> |
|
|
|
|
|
<div className='bg-gray-50 dark:bg-gray-800 rounded-lg p-3'> |
|
|
<Text size='small' type='secondary'> |
|
|
<strong>提示:</strong> |
|
|
<br /> |
|
|
• 验证码每30秒更新一次 |
|
|
<br /> |
|
|
• 如果无法获取验证码,请使用备用码 |
|
|
<br />• 每个备用码只能使用一次 |
|
|
</Text> |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
} |
|
|
|
|
|
return ( |
|
|
<div |
|
|
style={{ |
|
|
display: 'flex', |
|
|
justifyContent: 'center', |
|
|
alignItems: 'center', |
|
|
minHeight: '60vh', |
|
|
}} |
|
|
> |
|
|
<Card style={{ width: 400, padding: 24 }}> |
|
|
<div style={{ textAlign: 'center', marginBottom: 24 }}> |
|
|
<Title heading={3}>两步验证</Title> |
|
|
<Paragraph type='secondary'> |
|
|
请输入认证器应用显示的验证码完成登录 |
|
|
</Paragraph> |
|
|
</div> |
|
|
|
|
|
<Form onSubmit={handleSubmit}> |
|
|
<Form.Input |
|
|
field='code' |
|
|
label={useBackupCode ? '备用码' : '验证码'} |
|
|
placeholder={useBackupCode ? '请输入8位备用码' : '请输入6位验证码'} |
|
|
value={verificationCode} |
|
|
onChange={setVerificationCode} |
|
|
onKeyPress={handleKeyPress} |
|
|
size='large' |
|
|
style={{ marginBottom: 16 }} |
|
|
autoFocus |
|
|
/> |
|
|
|
|
|
<Button |
|
|
htmlType='submit' |
|
|
type='primary' |
|
|
loading={loading} |
|
|
block |
|
|
size='large' |
|
|
style={{ marginBottom: 16 }} |
|
|
> |
|
|
验证并登录 |
|
|
</Button> |
|
|
</Form> |
|
|
|
|
|
<Divider /> |
|
|
|
|
|
<div style={{ textAlign: 'center' }}> |
|
|
<Button |
|
|
theme='borderless' |
|
|
type='tertiary' |
|
|
onClick={() => { |
|
|
setUseBackupCode(!useBackupCode); |
|
|
setVerificationCode(''); |
|
|
}} |
|
|
style={{ marginRight: 16, color: '#1890ff', padding: 0 }} |
|
|
> |
|
|
{useBackupCode ? '使用认证器验证码' : '使用备用码'} |
|
|
</Button> |
|
|
|
|
|
{onBack && ( |
|
|
<Button |
|
|
theme='borderless' |
|
|
type='tertiary' |
|
|
onClick={onBack} |
|
|
style={{ color: '#1890ff', padding: 0 }} |
|
|
> |
|
|
返回登录 |
|
|
</Button> |
|
|
)} |
|
|
</div> |
|
|
|
|
|
<div |
|
|
style={{ |
|
|
marginTop: 24, |
|
|
padding: 16, |
|
|
background: '#f6f8fa', |
|
|
borderRadius: 6, |
|
|
}} |
|
|
> |
|
|
<Text size='small' type='secondary'> |
|
|
<strong>提示:</strong> |
|
|
<br /> |
|
|
• 验证码每30秒更新一次 |
|
|
<br /> |
|
|
• 如果无法获取验证码,请使用备用码 |
|
|
<br />• 每个备用码只能使用一次 |
|
|
</Text> |
|
|
</div> |
|
|
</Card> |
|
|
</div> |
|
|
); |
|
|
}; |
|
|
|
|
|
export default TwoFAVerification; |
|
|
|