Final push for project

Change-Id: I9103078156eca93df2482b9fe3854d9301bb98b3
diff --git a/frontend/my-app/src/pages/Login.jsx b/frontend/my-app/src/pages/Login.jsx
new file mode 100644
index 0000000..19f13ca
--- /dev/null
+++ b/frontend/my-app/src/pages/Login.jsx
@@ -0,0 +1,214 @@
+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;
\ No newline at end of file