blob: 19f13caa2184eebd06b4682d60c2a18ce4d498a4 [file] [log] [blame]
import React, { useState } from 'react';
import {
Box, Container, Typography, Paper, TextField, Button, CircularProgress, Alert
} from '@mui/material';
// import { useNavigate } from 'react-router-dom'; // 如果需要重定向,请取消注释
function LoginPage() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState(null); // 用于显示成功或错误消息
const [messageSeverity, setMessageSeverity] = useState('info'); // 'success', 'error', 'info'
// const navigate = useNavigate(); // 如果需要重定向,请取消注释
const handleSubmit = async (event) => {
event.preventDefault(); // 阻止表单默认提交行为
setMessage(null); // 清除之前的消息
setLoading(true); // 显示加载指示器
try {
const response = await fetch('http://localhost:8080/api/auth/login', { // 确保这里的URL与您的后端接口一致
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const data = await response.json();
if (response.ok) {
// 登录成功
setMessage('登录成功!欢迎 ' + data.username);
setMessageSeverity('success');
console.log('登录成功:', data);
// 将 JWT Token 保存到 localStorage
localStorage.setItem('jwt_token', data.token);
// 可选:登录成功后重定向到其他页面
// navigate('/dashboard');
} else {
// 登录失败
const errorMessage = data.error || '未知错误';
setMessage('登录失败: ' + errorMessage);
setMessageSeverity('error');
console.error('登录失败:', data);
}
} catch (error) {
// 网络错误或请求发送失败
setMessage('网络错误,请稍后再试。');
setMessageSeverity('error');
console.error('请求发送失败:', error);
} finally {
setLoading(false); // 隐藏加载指示器
}
};
return (
<Box sx={{
minHeight: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: 'linear-gradient(135deg, #2c3e50, #4ca1af)', // 与 Home 组件相同的背景
color: 'white',
py: 4 // 垂直内边距
}}>
<Container maxWidth="xs" sx={{ position: 'relative', zIndex: 10 }}>
<Typography variant="h4" align="center" sx={{ mb: 4, fontWeight: 'bold' }}>
🔐 用户登录
</Typography>
<Paper sx={{
p: 4, // 内边距
backgroundColor: 'rgba(30, 30, 30, 0.9)', // 半透明深色背景
borderRadius: '12px', // 圆角
boxShadow: '0 8px 16px rgba(0,0,0,0.3)' // 阴影
}}>
{message && (
<Alert severity={messageSeverity} sx={{ mb: 2, borderRadius: '8px' }}>
{message}
</Alert>
)}
<form onSubmit={handleSubmit}>
<TextField
label="用户名"
variant="outlined"
fullWidth
margin="normal"
value={username}
onChange={(e) => setUsername(e.target.value)}
required
sx={{
mb: 2, // 底部外边距
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
color: 'white', // 输入文字颜色
'& fieldset': { borderColor: 'rgba(255, 255, 255, 0.3)' }, // 边框颜色
'&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.5)' },
'&.Mui-focused fieldset': { borderColor: '#8b5cf6' }, // 聚焦时边框颜色
},
'& .MuiInputLabel-root': { color: '#bbb' }, // 标签颜色
'& .MuiInputLabel-root.Mui-focused': { color: '#8b5cf6' }, // 聚焦时标签颜色
}}
/>
<TextField
label="密码"
type="password"
variant="outlined"
fullWidth
margin="normal"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
sx={{
mb: 3, // 底部外边距
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
color: 'white', // 输入文字颜色
'& fieldset': { borderColor: 'rgba(255, 255, 255, 0.3)' },
'&:hover fieldset': { borderColor: 'rgba(255, 255, 255, 0.5)' },
'&.Mui-focused fieldset': { borderColor: '#8b5cf6' },
},
'& .MuiInputLabel-root': { color: '#bbb' },
'& .MuiInputLabel-root.Mui-focused': { color: '#8b5cf6' },
}}
/>
<Button
type="submit"
variant="contained"
fullWidth
disabled={loading}
sx={{
py: '0.75rem', // 垂直内边距
backgroundImage: 'linear-gradient(to right, #6366f1, #8b5cf6)', // 渐变背景
color: 'white',
borderRadius: '0.375rem', // 圆角
fontWeight: 600,
position: 'relative', // 用于加载指示器定位
'&:hover': {
backgroundImage: 'linear-gradient(to right, #4f46e5, #7c3aed)',
},
}}
>
{loading ? <CircularProgress size={24} color="inherit" sx={{ position: 'absolute' }} /> : '登录'}
</Button>
</form>
</Paper>
</Container>
{/* 背景泡泡动画 - 复用 Home 组件中的样式 */}
<Box className="bubbles" sx={{
pointerEvents: 'none',
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
overflow: 'hidden',
zIndex: 1,
}}>
{[...Array(40)].map((_, i) => (
<Box
key={i}
className="bubble"
sx={{
position: 'absolute',
bottom: '-150px', // 确保从屏幕外开始
background: `hsla(${Math.random() * 360}, 70%, 80%, 0.15)`, // 增加颜色多样性
borderRadius: '50%',
animation: 'rise 20s infinite ease-in',
width: `${Math.random() * 25 + 10}px`, // 调整大小范围
height: `${Math.random() * 25 + 10}px`,
left: `${Math.random() * 100}%`,
animationDuration: `${15 + Math.random() * 20}s`, // 调整动画时长
animationDelay: `${Math.random() * 10}s`, // 调整动画延迟
opacity: 0, // 初始透明
}}
/>
))}
</Box>
{/* 定义动画和其他全局可能需要的样式 */}
<style>
{`
body { /* 基本重置 */
margin: 0;
font-family: 'Roboto', sans-serif; /* 确保字体一致 */
}
@keyframes rise {
0% {
transform: translateY(0) scale(0.8);
opacity: 0; /* 从透明开始 */
}
10% {
opacity: 1; /* 渐显 */
}
90% {
opacity: 1; /* 保持可见 */
}
100% {
transform: translateY(-130vh) scale(0.3); /* 飘得更高更小 */
opacity: 0; /* 渐隐 */
}
}
`}
</style>
</Box>
);
}
export default LoginPage;