feat: 初始化 ChatOne 管理后台前端
基于 React + Vite + Ant Design Pro 搭建登录、动态菜单、布局主题与数据总览,并补充按菜单整理的 UI 设计稿。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
216
src/pages/login/index.tsx
Normal file
216
src/pages/login/index.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import { LockOutlined, MobileOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
LoginFormPage,
|
||||
ProConfigProvider,
|
||||
ProFormCaptcha,
|
||||
ProFormCheckbox,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Tabs, message, theme } from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { login, getUserInfo } from '@/api/auth';
|
||||
import { useUserStore } from '@/stores/user';
|
||||
import type { LoginType } from '@/api/types/auth';
|
||||
import type { LoginAccountParams } from '@/api/types/auth';
|
||||
import logo from '@/assets/logo.png';
|
||||
|
||||
const LoginFrom = () => {
|
||||
const [loginType, setLoginType] = useState<LoginType>('account');
|
||||
const { token } = theme.useToken();
|
||||
const setToken = useUserStore(s => s.setToken);
|
||||
const setUserInfo = useUserStore(s => s.setUserInfo);
|
||||
const navigate = useNavigate();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
const onFinish = async (values: LoginAccountParams) => {
|
||||
console.log(values);
|
||||
if (loginType !== 'account') {
|
||||
message.warning('暂不支持短信登录');
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await login(values);
|
||||
if (res.code !== 200) {
|
||||
message.error(res.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = await getUserInfo();
|
||||
if (userInfo.code !== 200) {
|
||||
message.error(userInfo.message);
|
||||
return;
|
||||
}
|
||||
|
||||
setToken(res.data.token);
|
||||
setUserInfo(userInfo.data);
|
||||
|
||||
message.success('登录成功');
|
||||
const redirect = searchParams.get('redirect') || '/dashboard';
|
||||
navigate(redirect, { replace: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: 'white',
|
||||
height: '100vh',
|
||||
}}
|
||||
>
|
||||
<LoginFormPage
|
||||
backgroundImageUrl="https://mdn.alipayobjects.com/huamei_gcee1x/afts/img/A*y0ZTS6WLwvgAAAAAAAAAAAAADml6AQ/fmt.webp"
|
||||
logo={logo}
|
||||
backgroundVideoUrl="https://gw.alipayobjects.com/v/huamei_gcee1x/afts/video/jXRBRK_VAwoAAAAAAAAAAAAAK4eUAQBr"
|
||||
title="ChatOne"
|
||||
containerStyle={{
|
||||
backgroundColor: 'rgba(0, 0, 0,0.65)',
|
||||
backdropFilter: 'blur(4px)',
|
||||
}}
|
||||
subTitle="整合AI聊天管理平台"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Tabs
|
||||
items={[
|
||||
{ key: 'account', label: '账号密码登录' },
|
||||
{ key: 'phone', label: '手机号登录' },
|
||||
]}
|
||||
onChange={key => setLoginType(key as LoginType)}
|
||||
activeKey={loginType}
|
||||
/>
|
||||
{loginType === 'account' && (
|
||||
<>
|
||||
<ProFormText
|
||||
name="username"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: (
|
||||
<UserOutlined
|
||||
style={{
|
||||
color: token.colorText,
|
||||
}}
|
||||
className={'prefixIcon'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
placeholder={'用户名: admin or user'}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入用户名!',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormText.Password
|
||||
name="password"
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: (
|
||||
<LockOutlined
|
||||
style={{
|
||||
color: token.colorText,
|
||||
}}
|
||||
className={'prefixIcon'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
placeholder={'密码: ant.design'}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入密码!',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{loginType === 'phone' && (
|
||||
<>
|
||||
<ProFormText
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: (
|
||||
<MobileOutlined
|
||||
style={{
|
||||
color: token.colorText,
|
||||
}}
|
||||
className={'prefixIcon'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
name="mobile"
|
||||
placeholder={'手机号'}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入手机号!',
|
||||
},
|
||||
{
|
||||
pattern: /^1\d{10}$/,
|
||||
message: '手机号格式错误!',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormCaptcha
|
||||
fieldProps={{
|
||||
size: 'large',
|
||||
prefix: (
|
||||
<LockOutlined
|
||||
style={{
|
||||
color: token.colorText,
|
||||
}}
|
||||
className={'prefixIcon'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
captchaProps={{
|
||||
size: 'large',
|
||||
}}
|
||||
placeholder={'请输入验证码'}
|
||||
captchaTextRender={(timing, count) => {
|
||||
if (timing) {
|
||||
return `${count} ${'获取验证码'}`;
|
||||
}
|
||||
return '获取验证码';
|
||||
}}
|
||||
name="captcha"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入验证码!',
|
||||
},
|
||||
]}
|
||||
onGetCaptcha={async () => {
|
||||
message.success('获取验证码成功!验证码为:1234');
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
marginBlockEnd: 24,
|
||||
}}
|
||||
>
|
||||
<ProFormCheckbox noStyle name="autoLogin">
|
||||
自动登录
|
||||
</ProFormCheckbox>
|
||||
<a
|
||||
style={{
|
||||
float: 'right',
|
||||
}}
|
||||
>
|
||||
忘记密码
|
||||
</a>
|
||||
</div>
|
||||
</LoginFormPage>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default function Login() {
|
||||
return (
|
||||
<ProConfigProvider dark>
|
||||
<LoginFrom />
|
||||
</ProConfigProvider>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user