refactor: 重构页面布局,样式
This commit is contained in:
+164
-89
@@ -3,11 +3,11 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { loginApi, getCaptchaApi } from '../../api/auth';
|
||||
import { toast } from 'sonner';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../components/ui/card';
|
||||
import { Button } from '../../components/ui/button';
|
||||
import { Input } from '../../components/ui/input';
|
||||
import { Label } from '../../components/ui/label';
|
||||
import { Radio } from 'lucide-react';
|
||||
import { ShieldCheck, Disc3, ArrowRight, Heart } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export default function Login() {
|
||||
const navigate = useNavigate();
|
||||
@@ -21,7 +21,6 @@ export default function Login() {
|
||||
const [captchaImage, setCaptchaImage] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Guard to ensure we only fetch on mount once
|
||||
const mounted = useRef(false);
|
||||
|
||||
const fetchCaptcha = async () => {
|
||||
@@ -48,15 +47,8 @@ export default function Login() {
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!account || !password) {
|
||||
toast.error('请输入账号和密码');
|
||||
return;
|
||||
}
|
||||
if (!captcha) {
|
||||
toast.error('请输入验证码');
|
||||
return;
|
||||
}
|
||||
if (!account || !password) return toast.error('请输入账号和密码');
|
||||
if (!captcha) return toast.error('请输入验证码');
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -66,7 +58,7 @@ export default function Login() {
|
||||
captcha,
|
||||
captchaId
|
||||
});
|
||||
toast.success('登录成功');
|
||||
toast.success('欢迎回来');
|
||||
setToken(res.token);
|
||||
setUserInfo(res.user);
|
||||
navigate('/');
|
||||
@@ -79,86 +71,169 @@ export default function Login() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-950 p-4">
|
||||
<Card className="w-full max-w-sm border-none shadow-2xl rounded-3xl overflow-hidden bg-white dark:bg-slate-900">
|
||||
<CardHeader className="space-y-2 flex flex-col items-center pt-10 pb-6 bg-slate-50/50">
|
||||
<div className="w-16 h-16 bg-primary text-primary-foreground rounded-2xl flex items-center justify-center mb-2 shadow-xl shadow-primary/20 rotate-3 transform hover:rotate-0 transition-transform">
|
||||
<Radio className="w-8 h-8" />
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<CardTitle className="text-3xl font-extrabold tracking-tight text-slate-900 dark:text-white">早安电台</CardTitle>
|
||||
<CardDescription className="text-slate-500 font-medium">
|
||||
后台管理系统 portal
|
||||
</CardDescription>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-8">
|
||||
<form onSubmit={handleLogin} className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="account">账号</Label>
|
||||
<Input
|
||||
id="account"
|
||||
placeholder="请输入管理员账号"
|
||||
className="h-12 rounded-xl bg-slate-50 border-none focus:ring-2 focus:ring-primary"
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
<div className="min-h-screen flex items-center justify-center bg-[#FAF5E6] relative overflow-hidden font-sans warm-noise">
|
||||
{/* Ambient Background Elements */}
|
||||
<div className="absolute top-[-10%] left-[-10%] w-[40%] h-[40%] bg-gradient-to-br from-[#D28F4F]/10 to-transparent rounded-full blur-[120px] animate-pulse" />
|
||||
<div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] bg-gradient-to-tr from-[#A64452]/10 to-transparent rounded-full blur-[140px] animate-pulse" />
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95, y: 20 }}
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
transition={{ duration: 1, ease: "easeOut" }}
|
||||
className="w-full max-w-[1100px] grid grid-cols-1 lg:grid-cols-2 bg-white/30 backdrop-blur-3xl rounded-[4rem] shadow-glass border border-white/40 overflow-hidden relative z-10 m-4"
|
||||
>
|
||||
{/* Visual Side (Twilight) */}
|
||||
<div className="hidden lg:flex flex-col justify-between p-16 sidebar-noise text-white relative overflow-hidden group">
|
||||
<div className="absolute inset-0 opacity-40 mix-blend-overlay pointer-events-none" />
|
||||
<div className="absolute top-[-20%] right-[-20%] w-80 h-80 bg-[#D28F4F]/30 rounded-full blur-[100px] group-hover:scale-125 transition-transform duration-1000" />
|
||||
|
||||
<div className="relative z-10">
|
||||
<motion.div
|
||||
initial={{ x: -20, opacity: 0 }}
|
||||
animate={{ x: 0, opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
className="flex items-center gap-4"
|
||||
>
|
||||
<div className="w-16 h-16 bg-white rounded-3xl flex items-center justify-center shadow-2xl shadow-[#D28F4F]/20 ring-1 ring-white/20 overflow-hidden relative group p-2">
|
||||
<img src="/favicon.jpg" alt="logo" className="w-full h-full object-cover rounded-2xl" />
|
||||
<div className="absolute inset-0 bg-[#D28F4F] opacity-0 group-hover:opacity-10 transition-opacity" />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<span className="text-xl font-black tracking-tighter uppercase leading-none">Morning Radio</span>
|
||||
<span className="text-[10px] uppercase font-black tracking-[0.3em] opacity-40 mt-1">Management Console</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="mt-20 space-y-8">
|
||||
<h2 className="text-5xl font-black text-white leading-[1.1] tracking-tighter">
|
||||
声音的<span className="text-[#D28F4F] italic px-2">温度</span><br />
|
||||
伴随每个<br />
|
||||
清晨与黄昏。
|
||||
</h2>
|
||||
<p className="text-white/40 text-lg font-medium max-w-sm leading-relaxed border-l-2 border-[#D28F4F]/30 pl-6">
|
||||
重新设计的管理架构,在日式简练与法式优雅间,寻找高效操作的节奏感。
|
||||
</p>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
className="h-12 rounded-xl bg-slate-50 border-none focus:ring-2 focus:ring-primary"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex items-center gap-10">
|
||||
<div className="flex flex-col">
|
||||
<span className="text-[#D28F4F] text-3xl font-black">2.5</span>
|
||||
<span className="text-white/20 text-[10px] uppercase font-black tracking-widest mt-1">Version Control</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="captcha">验证码</Label>
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
id="captcha"
|
||||
placeholder="验证码"
|
||||
autoComplete="off"
|
||||
className="h-12 rounded-xl bg-slate-50 border-none focus:ring-2 focus:ring-primary flex-1"
|
||||
value={captcha}
|
||||
onChange={(e) => setCaptcha(e.target.value)}
|
||||
disabled={loading}
|
||||
required
|
||||
/>
|
||||
{captchaImage ? (
|
||||
<div
|
||||
className="h-12 w-32 border rounded-xl overflow-hidden bg-white shrink-0 cursor-pointer hover:opacity-80 transition-opacity"
|
||||
onClick={() => fetchCaptcha()}
|
||||
>
|
||||
<img
|
||||
src={captchaImage}
|
||||
alt="captcha"
|
||||
className="w-full h-full object-contain p-1"
|
||||
title="点击刷新"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-12 w-32 border rounded-xl bg-slate-100 animate-pulse shrink-0" />
|
||||
)}
|
||||
<div className="w-px h-10 bg-white/10" />
|
||||
<div className="text-white/60 text-sm font-bold flex items-center gap-2">
|
||||
<ShieldCheck className="w-4 h-4 text-[#D28F4F]" />
|
||||
安全加固基建
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Form Side (Warm Cream) */}
|
||||
<div className="p-10 lg:p-20 flex flex-col justify-center bg-[#FAF5E6]/40 backdrop-blur-md relative overflow-hidden">
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-[#D28F4F]/5 rounded-full blur-3xl" />
|
||||
<div className="max-w-md mx-auto w-full relative z-10">
|
||||
<div className="mb-14">
|
||||
<h3 className="text-4xl font-black text-[#4A3A2C] tracking-tight mb-3">欢迎回来</h3>
|
||||
<div className="flex items-center gap-3">
|
||||
<p className="text-[#8C7E6C]/60 font-black uppercase text-[10px] tracking-[0.4em]">Control Center Access</p>
|
||||
<div className="h-px flex-1 bg-[#4A3A2C]/5" />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="w-full h-12 text-base font-bold rounded-2xl shadow-lg shadow-primary/20 mt-4 active:scale-[0.98] transition-all"
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? '验证中...' : '立即登录'}
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<form onSubmit={handleLogin} className="space-y-10">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-3">
|
||||
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1">管理员身份账号</Label>
|
||||
<Input
|
||||
placeholder="Username / Email"
|
||||
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all pl-8 placeholder:text-[#8C7E6C]/30"
|
||||
value={account}
|
||||
onChange={(e) => setAccount(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1">受限制加密密码</Label>
|
||||
<Input
|
||||
type="password"
|
||||
placeholder="Security Token"
|
||||
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all pl-8 placeholder:text-[#8C7E6C]/30"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<Label className="text-[11px] uppercase font-black tracking-widest text-[#8C7E6C] ml-1">即时验证码</Label>
|
||||
<div className="flex gap-4">
|
||||
<Input
|
||||
placeholder="Code"
|
||||
className="h-16 rounded-[1.5rem] border-none bg-white shadow-sm font-bold text-[#4A3A2C] focus:ring-4 ring-[#D28F4F]/10 transition-all pl-8 flex-1 placeholder:text-[#8C7E6C]/30"
|
||||
value={captcha}
|
||||
onChange={(e) => setCaptcha(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<div
|
||||
className="h-16 w-36 bg-white rounded-[1.5rem] p-2 shadow-sm border border-[#D28F4F]/10 cursor-pointer hover:ring-4 ring-[#D28F4F]/10 transition-all flex items-center justify-center overflow-hidden group"
|
||||
onClick={fetchCaptcha}
|
||||
>
|
||||
{captchaImage ? (
|
||||
<img src={captchaImage} alt="captcha" className="h-full object-contain filter group-hover:scale-110 transition-transform" />
|
||||
) : (
|
||||
<Disc3 className="w-5 h-5 text-[#8C7E6C]/20 animate-spin" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className="w-full h-20 rounded-[2.2rem] bg-gradient-to-r from-[#D28F4F] to-[#A64452] hover:scale-[1.02] active:scale-[0.98] transition-all shadow-2xl shadow-[#D28F4F]/30 border-none group overflow-hidden relative"
|
||||
>
|
||||
<div className="relative z-10 flex items-center justify-center gap-4 text-xl font-black tracking-tight text-white">
|
||||
{loading ? (
|
||||
<>
|
||||
<Disc3 className="w-6 h-6 animate-spin" />
|
||||
同步权限中...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
进入广播控制中心
|
||||
<ArrowRight className="w-6 h-6 group-hover:translate-x-1.5 transition-transform" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute inset-0 bg-white/10 opacity-0 group-hover:opacity-100 transition-opacity" />
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<div className="mt-16 pt-10 border-t border-[#4A3A2C]/5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Heart className="w-3.5 h-3.5 text-[#A64452] fill-[#A64452]/20 animate-pulse" />
|
||||
<span className="text-[10px] font-black text-[#8C7E6C]/40 uppercase tracking-[0.2em]">Crafted with Soul</span>
|
||||
</div>
|
||||
<button className="text-[10px] font-black text-[#D28F4F] uppercase tracking-[0.2em] hover:underline underline-offset-4">
|
||||
无法访问?
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Micro Interaction Decorative elements */}
|
||||
<div className="absolute bottom-20 left-20 flex gap-3 rotate-12 opacity-5 pointer-events-none">
|
||||
<div className="w-3 h-12 bg-[#D28F4F] rounded-full" />
|
||||
<div className="w-3 h-24 bg-[#D28F4F] rounded-full mt-6" />
|
||||
<div className="w-3 h-12 bg-[#D28F4F] rounded-full mt-3" />
|
||||
</div>
|
||||
<div className="absolute top-20 right-20 flex gap-3 -rotate-12 opacity-5 pointer-events-none">
|
||||
<div className="w-3 h-12 bg-[#A64452] rounded-full" />
|
||||
<div className="w-3 h-24 bg-[#A64452] rounded-full mt-6" />
|
||||
<div className="w-3 h-12 bg-[#A64452] rounded-full mt-3" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user