2025-06-13 06:04:40 +00:00
|
|
|
|
import React, { useState } from 'react';
|
2025-06-13 07:31:12 +00:00
|
|
|
|
import styled, { keyframes } from 'styled-components';
|
2025-06-13 06:04:40 +00:00
|
|
|
|
import { login } from '../services/api';
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const fadeIn = keyframes`
|
|
|
|
|
|
from {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
transform: translateY(20px);
|
|
|
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
2025-06-13 06:04:40 +00:00
|
|
|
|
const LoginContainer = styled.div`
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
max-width: 400px;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
margin: 0 auto;
|
|
|
|
|
|
padding: ${({ theme }) => theme.spacing.xl};
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.glass.light};
|
|
|
|
|
|
backdrop-filter: blur(10px);
|
|
|
|
|
|
-webkit-backdrop-filter: blur(10px);
|
|
|
|
|
|
border-radius: ${({ theme }) => theme.borderRadius.xl};
|
|
|
|
|
|
box-shadow: ${({ theme }) => theme.shadows.lg};
|
|
|
|
|
|
animation: ${fadeIn} 0.5s ease-out;
|
|
|
|
|
|
border: 1px solid ${({ theme }) => theme.colors.glass.light};
|
|
|
|
|
|
|
|
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.glass.dark};
|
|
|
|
|
|
border-color: ${({ theme }) => theme.colors.glass.dark};
|
|
|
|
|
|
}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const LoginTitle = styled.h1`
|
|
|
|
|
|
color: ${({ theme }) => theme.colors.text.primary};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize['3xl']};
|
|
|
|
|
|
font-weight: ${({ theme }) => theme.typography.fontWeight.bold};
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-bottom: ${({ theme }) => theme.spacing.xl};
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: ${({ theme }) => theme.spacing.sm};
|
|
|
|
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
|
|
content: '🔒';
|
|
|
|
|
|
font-size: 1.2em;
|
|
|
|
|
|
}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const Form = styled.form`
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
gap: ${({ theme }) => theme.spacing.lg};
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const InputGroup = styled.div`
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const Label = styled.label`
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
margin-bottom: ${({ theme }) => theme.spacing.xs};
|
|
|
|
|
|
color: ${({ theme }) => theme.colors.text.primary};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize.sm};
|
|
|
|
|
|
font-weight: ${({ theme }) => theme.typography.fontWeight.medium};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const Input = styled.input`
|
2025-06-13 07:31:12 +00:00
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: ${({ theme }) => theme.spacing.md};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize.base};
|
|
|
|
|
|
border: 2px solid ${({ theme }) => theme.colors.glass.light};
|
|
|
|
|
|
border-radius: ${({ theme }) => theme.borderRadius.lg};
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.glass.light};
|
|
|
|
|
|
color: ${({ theme }) => theme.colors.text.primary};
|
|
|
|
|
|
transition: all ${({ theme }) => theme.transitions.default};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
&:focus {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
outline: none;
|
|
|
|
|
|
border-color: ${({ theme }) => theme.colors.primary};
|
|
|
|
|
|
box-shadow: 0 0 0 3px ${({ theme }) => theme.colors.primary}40;
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&::placeholder {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
color: ${({ theme }) => theme.colors.text.secondary};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.glass.dark};
|
|
|
|
|
|
border-color: ${({ theme }) => theme.colors.glass.dark};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const SubmitButton = styled.button`
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
padding: ${({ theme }) => theme.spacing.md};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize.base};
|
|
|
|
|
|
font-weight: ${({ theme }) => theme.typography.fontWeight.semibold};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
color: white;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
background: ${({ theme }) => theme.colors.primary};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
border: none;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
border-radius: ${({ theme }) => theme.borderRadius.lg};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
cursor: pointer;
|
2025-06-13 07:31:12 +00:00
|
|
|
|
transition: all ${({ theme }) => theme.transitions.default};
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
gap: ${({ theme }) => theme.spacing.sm};
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
&:hover {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
background: ${({ theme }) => theme.colors.primary}dd;
|
|
|
|
|
|
transform: translateY(-1px);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
|
|
transform: translateY(0);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:disabled {
|
|
|
|
|
|
opacity: 0.6;
|
|
|
|
|
|
cursor: not-allowed;
|
|
|
|
|
|
transform: none;
|
|
|
|
|
|
}
|
2025-06-13 07:31:12 +00:00
|
|
|
|
|
|
|
|
|
|
&::after {
|
|
|
|
|
|
content: '→';
|
|
|
|
|
|
font-size: 1.2em;
|
|
|
|
|
|
transition: transform ${({ theme }) => theme.transitions.default};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:hover::after {
|
|
|
|
|
|
transform: translateX(4px);
|
|
|
|
|
|
}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
const ErrorMessage = styled.div`
|
2025-06-13 07:31:12 +00:00
|
|
|
|
color: ${({ theme }) => theme.colors.status.error};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize.sm};
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-top: ${({ theme }) => theme.spacing.sm};
|
|
|
|
|
|
padding: ${({ theme }) => theme.spacing.sm};
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.status.error}20;
|
|
|
|
|
|
border-radius: ${({ theme }) => theme.borderRadius.md};
|
|
|
|
|
|
animation: ${fadeIn} 0.3s ease-out;
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const LoginHint = styled.div`
|
|
|
|
|
|
color: ${({ theme }) => theme.colors.text.secondary};
|
|
|
|
|
|
font-size: ${({ theme }) => theme.typography.fontSize.sm};
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
margin-top: ${({ theme }) => theme.spacing.md};
|
|
|
|
|
|
padding: ${({ theme }) => theme.spacing.sm};
|
|
|
|
|
|
background: ${({ theme }) => theme.colors.secondary}20;
|
|
|
|
|
|
border-radius: ${({ theme }) => theme.borderRadius.md};
|
|
|
|
|
|
|
|
|
|
|
|
strong {
|
|
|
|
|
|
color: ${({ theme }) => theme.colors.secondary};
|
|
|
|
|
|
}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
function LoginForm({ onLogin }) {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const [username, setUsername] = useState('');
|
2025-06-13 06:04:40 +00:00
|
|
|
|
const [password, setPassword] = useState('');
|
|
|
|
|
|
const [error, setError] = useState('');
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
const handleSubmit = async (e) => {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
setError('');
|
2025-06-13 07:31:12 +00:00
|
|
|
|
setIsLoading(true);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
const result = await login(username, password);
|
|
|
|
|
|
// 保存token和用户信息
|
|
|
|
|
|
localStorage.setItem('token', result.token);
|
|
|
|
|
|
localStorage.setItem('user', JSON.stringify(result.user));
|
|
|
|
|
|
onLogin(result.token, result.user);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
} catch (err) {
|
|
|
|
|
|
setError(err.response?.data?.error || '登录失败,请重试');
|
|
|
|
|
|
} finally {
|
2025-06-13 07:31:12 +00:00
|
|
|
|
setIsLoading(false);
|
2025-06-13 06:04:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<LoginContainer>
|
2025-06-13 07:31:12 +00:00
|
|
|
|
<LoginTitle>工作待办</LoginTitle>
|
2025-06-13 06:04:40 +00:00
|
|
|
|
<Form onSubmit={handleSubmit}>
|
2025-06-13 07:31:12 +00:00
|
|
|
|
<InputGroup>
|
|
|
|
|
|
<Label>用户名</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
placeholder="请输入用户名"
|
|
|
|
|
|
value={username}
|
|
|
|
|
|
onChange={(e) => setUsername(e.target.value)}
|
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</InputGroup>
|
|
|
|
|
|
<InputGroup>
|
|
|
|
|
|
<Label>密码</Label>
|
|
|
|
|
|
<Input
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
placeholder="请输入密码"
|
|
|
|
|
|
value={password}
|
|
|
|
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
|
|
|
|
disabled={isLoading}
|
|
|
|
|
|
required
|
|
|
|
|
|
/>
|
|
|
|
|
|
</InputGroup>
|
|
|
|
|
|
<SubmitButton type="submit" disabled={isLoading}>
|
|
|
|
|
|
{isLoading ? '登录中...' : '登录'}
|
|
|
|
|
|
</SubmitButton>
|
|
|
|
|
|
{error && <ErrorMessage>{error}</ErrorMessage>}
|
2025-06-13 06:04:40 +00:00
|
|
|
|
</Form>
|
2025-06-13 07:31:12 +00:00
|
|
|
|
<LoginHint>
|
|
|
|
|
|
{/* <strong>管理员:</strong> 用户名 admin,密码 weiMonkey2024<br /> */}
|
|
|
|
|
|
<strong>普通用户:</strong> 请使用管理员分配的账号密码
|
|
|
|
|
|
</LoginHint>
|
2025-06-13 06:04:40 +00:00
|
|
|
|
</LoginContainer>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default LoginForm;
|