Files
school2/web/pages/login/index.tsx
T
2026-07-13 15:23:29 +08:00

222 lines
7.2 KiB
TypeScript

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;