226 lines
6.4 KiB
JavaScript
226 lines
6.4 KiB
JavaScript
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;
|