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
2025-06-13 15:31:12 +08:00

226 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState } from 'react';
import styled, { keyframes } from 'styled-components';
import { login } from '../services/api';
const fadeIn = keyframes`
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
`;
const LoginContainer = styled.div`
width: 100%;
max-width: 400px;
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};
}
`;
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;
}
`;
const Form = styled.form`
display: flex;
flex-direction: column;
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};
`;
const Input = styled.input`
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};
&:focus {
outline: none;
border-color: ${({ theme }) => theme.colors.primary};
box-shadow: 0 0 0 3px ${({ theme }) => theme.colors.primary}40;
}
&::placeholder {
color: ${({ theme }) => theme.colors.text.secondary};
}
@media (prefers-color-scheme: dark) {
background: ${({ theme }) => theme.colors.glass.dark};
border-color: ${({ theme }) => theme.colors.glass.dark};
}
`;
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};
color: white;
background: ${({ theme }) => theme.colors.primary};
border: none;
border-radius: ${({ theme }) => theme.borderRadius.lg};
cursor: pointer;
transition: all ${({ theme }) => theme.transitions.default};
display: flex;
align-items: center;
justify-content: center;
gap: ${({ theme }) => theme.spacing.sm};
&:hover {
background: ${({ theme }) => theme.colors.primary}dd;
transform: translateY(-1px);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
&::after {
content: '→';
font-size: 1.2em;
transition: transform ${({ theme }) => theme.transitions.default};
}
&:hover::after {
transform: translateX(4px);
}
`;
const ErrorMessage = styled.div`
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;
`;
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};
}
`;
function LoginForm({ onLogin }) {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
setIsLoading(true);
try {
const result = await login(username, password);
// 保存token和用户信息
localStorage.setItem('token', result.token);
localStorage.setItem('user', JSON.stringify(result.user));
onLogin(result.token, result.user);
} catch (err) {
setError(err.response?.data?.error || '登录失败,请重试');
} finally {
setIsLoading(false);
}
};
return (
<LoginContainer>
<LoginTitle>工作待办</LoginTitle>
<Form onSubmit={handleSubmit}>
<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>}
</Form>
<LoginHint>
{/* <strong>管理员:</strong> 用户名 admin密码 weiMonkey2024<br /> */}
<strong>普通用户:</strong> 使
</LoginHint>
</LoginContainer>
);
}
export default LoginForm;