This repository has been archived on 2026-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
todo/client/src/components/LoginForm.js

226 lines
6.4 KiB
JavaScript
Raw Normal View History

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;