first commit
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
import {
|
||||
AlipayOutlined,
|
||||
LockOutlined,
|
||||
QqOutlined,
|
||||
TaobaoOutlined,
|
||||
UserOutlined,
|
||||
WechatOutlined,
|
||||
WeiboOutlined,
|
||||
BulbOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { Col, Divider, Row, Space, Button, Form, Input, Checkbox, Typography } from 'antd';
|
||||
import {type CSSProperties, useEffect, useState} from 'react';
|
||||
import React from 'react';
|
||||
import useGlobalStore from '@/stores/global';
|
||||
import useAuthStore from '@/stores/user';
|
||||
import {useNavigate} from "react-router";
|
||||
import type { LoginParams } from '@/api/system/sys_user';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { darkColorTheme, defaultColorTheme } from '@/layout/theme';
|
||||
import LanguageSwitcher from '@/components/LanguageSwitcher';
|
||||
|
||||
// 样式定义函数,支持暗黑模式
|
||||
const getBodyStyle = (isDark: boolean): CSSProperties => ({
|
||||
backgroundImage: isDark ? 'url(/static/bg-dark.jpg)' : 'url(/static/bg.png)',
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
minHeight: '100vh',
|
||||
minWidth: '520px',
|
||||
backgroundColor: isDark ? '#141414' : '#f0f2f5',
|
||||
transition: 'background-color 0.3s ease',
|
||||
});
|
||||
|
||||
const loginBodyStyle: CSSProperties = {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '40px',
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
const getLoginCardStyle = (isDark: boolean): CSSProperties => ({
|
||||
background: isDark ? '#1f1f1f' : 'white',
|
||||
borderRadius: '24px',
|
||||
overflow: 'hidden',
|
||||
boxShadow: isDark
|
||||
? '0 8px 24px rgba(0, 0, 0, 0.45)'
|
||||
: '0 8px 24px rgba(0, 0, 0, 0.12)',
|
||||
padding: '48px 32px',
|
||||
position: 'relative',
|
||||
transition: 'all 0.3s ease',
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
const getIconStyle = (isDark: boolean): CSSProperties => ({
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.45)',
|
||||
fontSize: '20px',
|
||||
verticalAlign: 'middle',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
});
|
||||
|
||||
const getIconDivStyle = (isDark: boolean): CSSProperties => ({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
flexDirection: 'column',
|
||||
height: 48,
|
||||
width: 48,
|
||||
border: isDark ? '1px solid rgba(255, 255, 255, 0.12)' : '1px solid #D4D8DD',
|
||||
borderRadius: '50%',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
});
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const login = useAuthStore(state => state.login);
|
||||
const user = useAuthStore(state => state.userinfo);
|
||||
const { t } = useTranslation();
|
||||
const themeConfig = useGlobalStore(state => state.themeConfig);
|
||||
const setThemeConfig = useGlobalStore(state => state.setThemeConfig);
|
||||
const logo = useGlobalStore(state => state.logo);
|
||||
const title = useGlobalStore(state => state.title);
|
||||
const subtitle = useGlobalStore(state => state.subtitle);
|
||||
const [isDark, setIsDark] = useState(themeConfig.algorithm === 'darkAlgorithm');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(localStorage.getItem('token') && user) {
|
||||
console.log(t('login.alreadyLoggedIn'));
|
||||
window.location.href = '/';
|
||||
}
|
||||
}, [user, navigate, t]);
|
||||
|
||||
const handleSubmit = (values: LoginParams) => {
|
||||
setLoading(true);
|
||||
login(values).then(() => {
|
||||
window.$message?.success(t('login.success'));
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 1000);
|
||||
}).catch((err) => {
|
||||
console.log(err);
|
||||
setLoading(false);
|
||||
})
|
||||
};
|
||||
|
||||
// 暗黑模式切换
|
||||
const toggleTheme = () => {
|
||||
const newIsDark = !isDark;
|
||||
setIsDark(newIsDark);
|
||||
setThemeConfig({
|
||||
...themeConfig,
|
||||
...newIsDark ? darkColorTheme : defaultColorTheme,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Row style={getBodyStyle(isDark)}>
|
||||
<Col lg={12} xs={24} style={loginBodyStyle}>
|
||||
<div style={getLoginCardStyle(isDark)}>
|
||||
{/* 右上角工具栏 */}
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
top: '10px',
|
||||
right: '10px',
|
||||
zIndex: 10,
|
||||
}}>
|
||||
<Space size="middle">
|
||||
<LanguageSwitcher size={"large"} type={'text'} />
|
||||
<Button
|
||||
icon={<BulbOutlined />}
|
||||
size="large"
|
||||
type="text"
|
||||
onClick={toggleTheme}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
{/* Logo and Title */}
|
||||
<div style={{ textAlign: 'center', marginBottom: 32 }}>
|
||||
<img src={logo} alt="logo" style={{ height: 44, marginBottom: 16 }} />
|
||||
<Typography.Title level={3} style={{ marginBottom: 8, color: isDark ? '#fff' : undefined }}>{title}</Typography.Title>
|
||||
<Typography.Text type="secondary">{subtitle}</Typography.Text>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{ remember: true }}
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t('login.username')}
|
||||
rules={[{ required: true, message: t('login.usernameRequired') }]}
|
||||
>
|
||||
<Input
|
||||
size="large"
|
||||
variant="filled"
|
||||
prefix={<UserOutlined />}
|
||||
placeholder={t('login.usernamePlaceholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t('login.password')}
|
||||
rules={[{ required: true, message: t('login.passwordRequired') }]}
|
||||
>
|
||||
<Input.Password
|
||||
size="large"
|
||||
variant="filled"
|
||||
prefix={<LockOutlined />}
|
||||
placeholder={t('login.passwordPlaceholder')}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Form.Item name="remember" valuePropName="checked" noStyle>
|
||||
<Checkbox>{t('login.remember')}</Checkbox>
|
||||
</Form.Item>
|
||||
<a>{t('login.forgotPassword')}</a>
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" size="large" block loading={loading}>
|
||||
{t('login.submit') || '登录'}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<Divider plain>{t('login.otherLogin')}</Divider>
|
||||
<Space align="center" size={24} style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<div style={getIconDivStyle(isDark)}>
|
||||
<QqOutlined style={{ ...getIconStyle(isDark), color: 'rgb(123, 212, 239)' }} />
|
||||
</div>
|
||||
<div style={getIconDivStyle(isDark)}>
|
||||
<WechatOutlined style={{ ...getIconStyle(isDark), color: 'rgb(51, 204, 0)' }} />
|
||||
</div>
|
||||
<div style={getIconDivStyle(isDark)}>
|
||||
<AlipayOutlined style={{ ...getIconStyle(isDark), color: '#1677FF' }} />
|
||||
</div>
|
||||
<div style={getIconDivStyle(isDark)}>
|
||||
<TaobaoOutlined style={{ ...getIconStyle(isDark), color: '#FF6A10' }} />
|
||||
</div>
|
||||
<div style={getIconDivStyle(isDark)}>
|
||||
<WeiboOutlined style={{ ...getIconStyle(isDark), color: '#e71f19' }} />
|
||||
</div>
|
||||
</Space>
|
||||
</div>
|
||||
</Col>
|
||||
<Col lg={12} xs={24}></Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
Reference in New Issue
Block a user