| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | 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; |
| |
|