2025-12-25 10:48:35 +08:00
|
|
|
import React, { useState, useEffect } from 'react';
|
2025-12-29 11:15:55 +08:00
|
|
|
import { Form, Input, Button, Card, message, Typography, Divider, Space, Alert } from 'antd';
|
|
|
|
|
import {
|
|
|
|
|
UserOutlined,
|
|
|
|
|
LockOutlined,
|
|
|
|
|
MailOutlined,
|
|
|
|
|
PhoneOutlined,
|
|
|
|
|
SafetyCertificateOutlined,
|
|
|
|
|
RobotOutlined
|
|
|
|
|
} from '@ant-design/icons';
|
2025-12-25 10:48:35 +08:00
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
|
import { useAuth } from '../context/AuthContext';
|
|
|
|
|
|
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
|
|
|
|
|
|
const Login = () => {
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
const [isFirstUser, setIsFirstUser] = useState(false);
|
|
|
|
|
const [registerMode, setRegisterMode] = useState(false);
|
|
|
|
|
const { login, register, checkAdmin } = useAuth();
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
checkIsFirstUser();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const checkIsFirstUser = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const response = await checkAdmin();
|
|
|
|
|
if (response.success) {
|
|
|
|
|
setIsFirstUser(!response.data.hasAdmin);
|
|
|
|
|
if (!response.data.hasAdmin) {
|
|
|
|
|
setRegisterMode(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('检查用户状态失败:', error);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onFinishLogin = async (values) => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const result = await login(values.username, values.password);
|
|
|
|
|
if (result.success) {
|
|
|
|
|
message.success('登录成功');
|
|
|
|
|
navigate('/');
|
|
|
|
|
} else {
|
|
|
|
|
message.error(result.message || '登录失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
message.error(error || '登录失败');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onFinishRegister = async (values) => {
|
|
|
|
|
if (values.password !== values.confirmPassword) {
|
|
|
|
|
message.error('两次输入的密码不一致');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const result = await register({
|
|
|
|
|
username: values.username,
|
|
|
|
|
password: values.password,
|
|
|
|
|
email: values.email,
|
|
|
|
|
phone: values.phone,
|
|
|
|
|
realName: values.realName
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (result.success) {
|
|
|
|
|
message.success(result.isFirstUser ? '注册成功,已为您创建管理员账户' : '注册成功');
|
|
|
|
|
navigate('/');
|
|
|
|
|
} else {
|
|
|
|
|
message.error(result.message || '注册失败');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
message.error(error || '注册失败');
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const containerStyle = {
|
|
|
|
|
minHeight: '100vh',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
alignItems: 'center',
|
2025-12-29 11:15:55 +08:00
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #6B8DD6 100%)',
|
|
|
|
|
padding: '24px',
|
|
|
|
|
position: 'relative',
|
|
|
|
|
overflow: 'hidden'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const backgroundDecorationStyle = {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
borderRadius: '50%',
|
|
|
|
|
filter: 'blur(80px)',
|
|
|
|
|
opacity: '0.3'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const cardStyle = {
|
|
|
|
|
width: '100%',
|
2025-12-29 11:15:55 +08:00
|
|
|
maxWidth: isFirstUser ? 480 : 420,
|
|
|
|
|
borderRadius: '20px',
|
|
|
|
|
boxShadow: '0 20px 60px rgba(0,0,0,0.25), 0 8px 20px rgba(0,0,0,0.15)',
|
|
|
|
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
|
|
|
backdropFilter: 'blur(20px)',
|
|
|
|
|
border: '1px solid rgba(255, 255, 255, 0.3)'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const headerStyle = {
|
|
|
|
|
textAlign: 'center',
|
2025-12-29 11:15:55 +08:00
|
|
|
marginBottom: '32px',
|
|
|
|
|
paddingTop: '8px'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const iconContainerStyle = {
|
|
|
|
|
width: '80px',
|
|
|
|
|
height: '80px',
|
|
|
|
|
borderRadius: '20px',
|
|
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
margin: '0 auto 20px',
|
|
|
|
|
boxShadow: '0 8px 24px rgba(102, 126, 234, 0.4)'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const titleStyle = {
|
2025-12-29 11:15:55 +08:00
|
|
|
fontSize: '26px',
|
2025-12-25 10:48:35 +08:00
|
|
|
fontWeight: '700',
|
2025-12-29 11:15:55 +08:00
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
WebkitBackgroundClip: 'text',
|
|
|
|
|
WebkitTextFillColor: 'transparent',
|
2025-12-25 10:48:35 +08:00
|
|
|
marginBottom: '8px'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const subtitleStyle = {
|
|
|
|
|
fontSize: '14px',
|
2025-12-29 11:15:55 +08:00
|
|
|
color: '#8c8c8c'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const formStyle = {
|
|
|
|
|
marginTop: '24px'
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-29 11:15:55 +08:00
|
|
|
const inputStyle = {
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
height: '48px',
|
|
|
|
|
border: '1px solid #e8e8e8'
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
const submitButtonStyle = {
|
|
|
|
|
width: '100%',
|
|
|
|
|
height: '48px',
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
fontWeight: '600',
|
2025-12-29 11:15:55 +08:00
|
|
|
borderRadius: '8px',
|
|
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
border: 'none',
|
|
|
|
|
boxShadow: '0 4px 15px rgba(102, 126, 234, 0.4)',
|
|
|
|
|
transition: 'all 0.3s ease'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const footerStyle = {
|
|
|
|
|
textAlign: 'center',
|
2025-12-29 11:15:55 +08:00
|
|
|
marginTop: '24px',
|
|
|
|
|
paddingBottom: '16px'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleButtonStyle = {
|
|
|
|
|
color: '#667eea',
|
|
|
|
|
fontWeight: '500',
|
|
|
|
|
padding: '4px 8px',
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
transition: 'all 0.3s ease'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const inputPrefixStyle = {
|
|
|
|
|
color: '#667eea',
|
|
|
|
|
fontSize: '18px'
|
2025-12-25 10:48:35 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div style={containerStyle}>
|
2025-12-29 11:15:55 +08:00
|
|
|
<div style={{
|
|
|
|
|
...backgroundDecorationStyle,
|
|
|
|
|
width: '400px',
|
|
|
|
|
height: '400px',
|
|
|
|
|
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
|
|
|
top: '-100px',
|
|
|
|
|
right: '-100px'
|
|
|
|
|
}} />
|
|
|
|
|
<div style={{
|
|
|
|
|
...backgroundDecorationStyle,
|
|
|
|
|
width: '300px',
|
|
|
|
|
height: '300px',
|
|
|
|
|
background: 'linear-gradient(135deg, #764ba2 0%, #6B8DD6 100%)',
|
|
|
|
|
bottom: '-50px',
|
|
|
|
|
left: '-50px'
|
|
|
|
|
}} />
|
|
|
|
|
|
2025-12-25 10:48:35 +08:00
|
|
|
<Card style={cardStyle}>
|
|
|
|
|
<div style={headerStyle}>
|
2025-12-29 11:15:55 +08:00
|
|
|
<div style={iconContainerStyle}>
|
|
|
|
|
<RobotOutlined style={{ fontSize: '40px', color: '#fff' }} />
|
2025-12-25 10:48:35 +08:00
|
|
|
</div>
|
|
|
|
|
<Title level={2} style={titleStyle}>
|
|
|
|
|
{isFirstUser ? '创建管理员账户' : 'IDC设备管理系统'}
|
|
|
|
|
</Title>
|
|
|
|
|
<Text style={subtitleStyle}>
|
2025-12-29 11:15:55 +08:00
|
|
|
{isFirstUser ? '首次使用,请创建系统管理员账户' : '安全登录您的账户'}
|
2025-12-25 10:48:35 +08:00
|
|
|
</Text>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{isFirstUser && (
|
|
|
|
|
<Alert
|
|
|
|
|
message="欢迎使用IDC设备管理系统"
|
|
|
|
|
description="您是第一个用户,系统将自动为您分配管理员权限。"
|
|
|
|
|
type="success"
|
|
|
|
|
showIcon
|
2025-12-29 11:15:55 +08:00
|
|
|
style={{ marginBottom: '24px', borderRadius: '8px' }}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Form
|
|
|
|
|
name={registerMode ? 'register' : 'login'}
|
|
|
|
|
size="large"
|
|
|
|
|
onFinish={registerMode ? onFinishRegister : onFinishLogin}
|
|
|
|
|
style={formStyle}
|
|
|
|
|
>
|
|
|
|
|
{registerMode ? (
|
|
|
|
|
<>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="username"
|
|
|
|
|
rules={[
|
|
|
|
|
{ required: true, message: '请输入用户名' },
|
|
|
|
|
{ min: 3, max: 20, message: '用户名长度必须在3-20个字符之间' },
|
|
|
|
|
{ pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线' }
|
|
|
|
|
]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input
|
|
|
|
|
prefix={<UserOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="用户名"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="realName"
|
|
|
|
|
rules={[{ required: true, message: '请输入真实姓名' }]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input
|
|
|
|
|
prefix={<SafetyCertificateOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="真实姓名"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="email"
|
|
|
|
|
rules={[
|
|
|
|
|
{ required: true, message: '请输入邮箱' },
|
|
|
|
|
{ type: 'email', message: '请输入有效的邮箱地址' }
|
|
|
|
|
]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input
|
|
|
|
|
prefix={<MailOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="邮箱"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="phone"
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input
|
|
|
|
|
prefix={<PhoneOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="手机号(可选)"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="password"
|
|
|
|
|
rules={[
|
|
|
|
|
{ required: true, message: '请输入密码' },
|
|
|
|
|
{ min: 6, message: '密码长度不能少于6个字符' }
|
|
|
|
|
]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input.Password
|
|
|
|
|
prefix={<LockOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="密码"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="confirmPassword"
|
|
|
|
|
dependencies={['password']}
|
|
|
|
|
rules={[
|
|
|
|
|
{ required: true, message: '请确认密码' },
|
|
|
|
|
({ getFieldValue }) => ({
|
|
|
|
|
validator(_, value) {
|
|
|
|
|
if (!value || getFieldValue('password') === value) {
|
|
|
|
|
return Promise.resolve();
|
|
|
|
|
}
|
|
|
|
|
return Promise.reject(new Error('两次输入的密码不一致'));
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input.Password
|
|
|
|
|
prefix={<LockOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="确认密码"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</>
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="username"
|
|
|
|
|
rules={[{ required: true, message: '请输入用户名' }]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input
|
|
|
|
|
prefix={<UserOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="用户名"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
name="password"
|
|
|
|
|
rules={[{ required: true, message: '请输入密码' }]}
|
|
|
|
|
>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Input.Password
|
|
|
|
|
prefix={<LockOutlined style={inputPrefixStyle} />}
|
|
|
|
|
placeholder="密码"
|
|
|
|
|
style={inputStyle}
|
2025-12-25 10:48:35 +08:00
|
|
|
/>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
2025-12-29 11:15:55 +08:00
|
|
|
<Form.Item style={{ marginBottom: '16px', marginTop: '24px' }}>
|
|
|
|
|
<Button
|
|
|
|
|
type="primary"
|
|
|
|
|
htmlType="submit"
|
2025-12-25 10:48:35 +08:00
|
|
|
loading={loading}
|
|
|
|
|
style={submitButtonStyle}
|
|
|
|
|
>
|
|
|
|
|
{registerMode ? '立即注册' : '登 录'}
|
|
|
|
|
</Button>
|
|
|
|
|
</Form.Item>
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
{!isFirstUser && (
|
|
|
|
|
<div style={footerStyle}>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Divider plain>
|
|
|
|
|
<Text style={{ color: '#8c8c8c', fontSize: '12px' }}>其他方式</Text>
|
|
|
|
|
</Divider>
|
2025-12-25 10:48:35 +08:00
|
|
|
<Space split={<Divider type="vertical" />}>
|
2025-12-29 11:15:55 +08:00
|
|
|
<Button
|
|
|
|
|
type="link"
|
2025-12-25 10:48:35 +08:00
|
|
|
size="small"
|
2025-12-29 11:15:55 +08:00
|
|
|
style={toggleButtonStyle}
|
|
|
|
|
onMouseEnter={(e) => {
|
|
|
|
|
e.target.style.background = 'rgba(102, 126, 234, 0.1)';
|
|
|
|
|
}}
|
|
|
|
|
onMouseLeave={(e) => {
|
|
|
|
|
e.target.style.background = 'transparent';
|
|
|
|
|
}}
|
2025-12-25 10:48:35 +08:00
|
|
|
onClick={() => setRegisterMode(!registerMode)}
|
|
|
|
|
>
|
|
|
|
|
{registerMode ? '已有账户?去登录' : '注册新账户'}
|
|
|
|
|
</Button>
|
|
|
|
|
</Space>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default Login;
|