148 lines
4.8 KiB
TypeScript
148 lines
4.8 KiB
TypeScript
import { useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { Button, Card, Form, Input, Tabs, message, Typography } from 'antd';
|
|
import { MOCK_SMS_FIXED_CODE } from '@dukang/shared-types';
|
|
import { saveAuth, request } from '../lib/api';
|
|
|
|
type LoginResult = { accessToken: string; refreshToken: string };
|
|
|
|
export default function LoginPage() {
|
|
const navigate = useNavigate();
|
|
const [smsForm] = Form.useForm();
|
|
const [passwordForm] = Form.useForm();
|
|
const [loading, setLoading] = useState(false);
|
|
const [codeCooldown, setCodeCooldown] = useState(0);
|
|
const phone = Form.useWatch('phone', smsForm);
|
|
|
|
async function sendCode() {
|
|
if (!phone) {
|
|
message.warning('请先输入手机号');
|
|
return;
|
|
}
|
|
await request('/admin/auth/sms/send', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ phone, scene: 'HQ_LOGIN' }),
|
|
});
|
|
message.success('验证码已发送');
|
|
setCodeCooldown(60);
|
|
const timer = setInterval(() => {
|
|
setCodeCooldown((c) => {
|
|
if (c <= 1) {
|
|
clearInterval(timer);
|
|
return 0;
|
|
}
|
|
return c - 1;
|
|
});
|
|
}, 1000);
|
|
}
|
|
|
|
async function finishLogin(data: LoginResult) {
|
|
saveAuth(data);
|
|
message.success('登录成功');
|
|
navigate('/');
|
|
}
|
|
|
|
async function onSmsFinish(values: { phone: string; code: string }) {
|
|
setLoading(true);
|
|
try {
|
|
const data = await request<LoginResult>('/admin/auth/login/sms', {
|
|
method: 'POST',
|
|
body: JSON.stringify(values),
|
|
});
|
|
await finishLogin(data);
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '登录失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
async function onPasswordFinish(values: { loginName: string; password: string }) {
|
|
setLoading(true);
|
|
try {
|
|
const data = await request<LoginResult>('/admin/auth/login/password', {
|
|
method: 'POST',
|
|
body: JSON.stringify(values),
|
|
});
|
|
await finishLogin(data);
|
|
} catch (e) {
|
|
message.error(e instanceof Error ? e.message : '登录失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div
|
|
style={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
background: '#f5f5f5',
|
|
}}
|
|
>
|
|
<Card style={{ width: 400 }}>
|
|
<Typography.Title level={3} style={{ textAlign: 'center', marginBottom: 24 }}>
|
|
HQ 管理后台
|
|
</Typography.Title>
|
|
<Tabs
|
|
items={[
|
|
{
|
|
key: 'password',
|
|
label: '账号密码',
|
|
children: (
|
|
<Form
|
|
form={passwordForm}
|
|
layout="vertical"
|
|
onFinish={onPasswordFinish}
|
|
initialValues={{ loginName: 'admin' }}
|
|
>
|
|
<Form.Item name="loginName" label="账号" rules={[{ required: true, message: '请输入账号' }]}>
|
|
<Input placeholder="admin" autoComplete="username" />
|
|
</Form.Item>
|
|
<Form.Item name="password" label="密码" rules={[{ required: true, message: '请输入密码' }]}>
|
|
<Input.Password placeholder="请输入密码" autoComplete="current-password" />
|
|
</Form.Item>
|
|
<Button type="primary" htmlType="submit" block loading={loading}>
|
|
登录
|
|
</Button>
|
|
</Form>
|
|
),
|
|
},
|
|
{
|
|
key: 'sms',
|
|
label: '短信验证码',
|
|
children: (
|
|
<Form
|
|
form={smsForm}
|
|
layout="vertical"
|
|
onFinish={onSmsFinish}
|
|
initialValues={{ phone: '13600000001', code: MOCK_SMS_FIXED_CODE }}
|
|
>
|
|
<Form.Item name="phone" label="手机号" rules={[{ required: true, message: '请输入手机号' }]}>
|
|
<Input placeholder="13600000001" maxLength={11} />
|
|
</Form.Item>
|
|
<Form.Item name="code" label="验证码" rules={[{ required: true, message: '请输入验证码' }]}>
|
|
<Input
|
|
placeholder={MOCK_SMS_FIXED_CODE}
|
|
addonAfter={
|
|
<Button type="link" size="small" disabled={codeCooldown > 0} onClick={() => void sendCode()}>
|
|
{codeCooldown > 0 ? `${codeCooldown}s` : '获取验证码'}
|
|
</Button>
|
|
}
|
|
/>
|
|
</Form.Item>
|
|
<Button type="primary" htmlType="submit" block loading={loading}>
|
|
登录
|
|
</Button>
|
|
</Form>
|
|
),
|
|
},
|
|
]}
|
|
/>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|