Files
hive-manage-v3/frontend/src/pages/LoginPage.tsx
T
enzoandClaude c79b1af392 feat: pixel-perfect rewrite — Login/Sidebar/Header/Dashboard/Gateway
- Login: 400px card with 24px radius, white 28px logo, password eye toggle,
  42px inputs, 48px honey button, placeholder var(--text-muted)
- Sidebar: NEW badges golden, count badges red, hover bg-hover, active honey-glow
- Header: search with search icon, status dots, inline hover dropdown
- Dashboard: 4-column stat grid, horizontal stacked health bar (8px height),
  48-bar gateway trend (no dupes), tables with height 44px and padding 10px 14px
- Gateway: "Gateway 健康监控" title, stat summary, honey filter buttons,
  honey-outline action buttons, search + type filter, last-update timestamp

All colors from var(--*), spacing in 4px multiples, animations ≤300ms,
table rows 44px, shadows only 3 layers.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 22:03:55 +08:00

212 lines
6.0 KiB
TypeScript

import { type FormEvent, useState } from 'react'
import { useNavigate, Link } from 'react-router-dom'
import { Eye, EyeOff } from 'lucide-react'
import { useAuth } from '../hooks/useAuth'
import toast from '../components/ui/Toast'
export default function LoginPage() {
const { login } = useAuth()
const navigate = useNavigate()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [showPwd, setShowPwd] = useState(false)
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
setError('')
setLoading(true)
try {
await login(username, password)
navigate('/', { replace: true })
} catch (err: any) {
const status = err?.response?.status
const detail = err?.response?.data?.detail ?? '登录失败'
if (status === 423) {
setError('账户已锁定,请 15 分钟后再试')
} else if (status === 401) {
setError('用户名或密码错误')
} else {
setError(detail)
}
} finally {
setLoading(false)
}
}
return (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg-root)',
}}
>
<div
style={{
width: 400,
padding: 32,
borderRadius: 24,
background: 'var(--bg-card)',
boxShadow: 'var(--shadow-lg)',
}}
>
{/* Logo */}
<div style={{ textAlign: 'center', marginBottom: 4 }}>
<div style={{ fontSize: 28, fontWeight: 700, color: '#fff' }}>
🐝 知微蜂巢
</div>
<div style={{ fontSize: 14, color: 'var(--text-dim)', marginTop: 4 }}>
Hive Manage v3
</div>
</div>
{/* Error */}
{error && (
<div
style={{
marginTop: 16,
padding: '8px 12px',
borderRadius: 'var(--radius)',
background: 'var(--bg-error)',
color: 'var(--red)',
fontSize: 13,
fontWeight: 500,
}}
>
{error}
</div>
)}
{/* Form */}
<form onSubmit={handleSubmit} style={{ marginTop: 24, display: 'flex', flexDirection: 'column', gap: 16 }}>
<div>
<label
style={{
display: 'block',
fontSize: 12,
fontWeight: 600,
color: '#fff',
marginBottom: 6,
}}
>
用户名
</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
placeholder="请输入用户名"
required
autoFocus
style={{
width: '100%',
height: 42,
padding: '0 12px',
borderRadius: 8,
border: '1px solid var(--border)',
background: 'var(--bg-root)',
color: 'var(--text)',
fontSize: 13,
outline: 'none',
fontFamily: 'inherit',
}}
/>
</div>
<div>
<label
style={{
display: 'block',
fontSize: 12,
fontWeight: 600,
color: '#fff',
marginBottom: 6,
}}
>
密码
</label>
<div style={{ position: 'relative' }}>
<input
type={showPwd ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="请输入密码"
required
style={{
width: '100%',
height: 42,
padding: '0 36px 0 12px',
borderRadius: 8,
border: '1px solid var(--border)',
background: 'var(--bg-root)',
color: 'var(--text)',
fontSize: 13,
outline: 'none',
fontFamily: 'inherit',
}}
/>
<button
type="button"
onClick={() => setShowPwd(!showPwd)}
style={{
position: 'absolute',
right: 10,
top: '50%',
transform: 'translateY(-50%)',
background: 'transparent',
border: 'none',
cursor: 'pointer',
color: 'var(--text-muted)',
padding: 4,
display: 'flex',
alignItems: 'center',
}}
tabIndex={-1}
>
{showPwd ? <EyeOff size={16} /> : <Eye size={16} />}
</button>
</div>
</div>
<button
type="submit"
disabled={loading}
style={{
width: '100%',
height: 48,
borderRadius: 8,
border: 'none',
background: 'var(--honey)',
color: '#000',
fontSize: 14,
fontWeight: 700,
cursor: loading ? 'not-allowed' : 'pointer',
opacity: loading ? 0.6 : 1,
fontFamily: 'inherit',
}}
>
{loading ? '登录中...' : '登入'}
</button>
</form>
{/* Register link */}
<p
style={{
textAlign: 'center',
marginTop: 20,
fontSize: 13,
color: 'var(--text-dim)',
}}
>
还没有账号?{' '}
<Link to="/register" style={{ color: 'var(--honey)', fontWeight: 600 }}>
注册
</Link>
</p>
</div>
</div>
)
}