Your Name 5f3a1dd9f7
All checks were successful
main / Build and push to Aliyun ACR (push) Successful in 4m38s
main / Deploy to server (push) Successful in 26s
fix:更新cicd
2026-06-22 18:05:27 +08:00

207 lines
8.0 KiB
TypeScript
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.

'use client';
import { useEffect, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { adminApi } from '@/lib/admin-services';
import { useAdminStore } from '@/store/adminStore';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Building2,
CheckCircle2,
User,
Lock,
Loader2,
} from 'lucide-react';
const schema = z.object({
username: z.string().min(1, '请输入账号'),
password: z.string().min(1, '请输入密码'),
});
type FormData = z.infer<typeof schema>;
export default function AdminLoginPage() {
const router = useRouter();
const search = useSearchParams();
const { setLogin, token } = useAdminStore();
const [serverError, setServerError] = useState<string | null>(null);
// 已登录则直接跳到后台
useEffect(() => {
if (token) {
const redirect = search.get('redirect');
router.replace(redirect || '/admin/dashboard');
}
}, [token, router, search]);
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { username: 'admin', password: '' },
});
const onSubmit = async (data: FormData) => {
setServerError(null);
try {
const res = await adminApi.login(data);
setLogin(res.token, res.admin);
const redirect = search.get('redirect');
router.replace(redirect || '/admin/dashboard');
} catch (e) {
setServerError((e as Error).message);
}
};
return (
<div className="grid min-h-screen lg:grid-cols-2">
{/* ===== 左侧:品牌展示区(深色,呼应首页 Hero===== */}
<div className="relative hidden overflow-hidden bg-[#0a0e27] lg:flex lg:flex-col lg:justify-between lg:p-12">
{/* 渐变底 + 辉光 + 几何图形 */}
<div className="pointer-events-none absolute inset-0">
<div className="absolute inset-0 bg-gradient-to-br from-[#0a0e27] via-[#0d1130] to-[#0a0e27]" />
<div className="absolute -left-20 top-0 h-72 w-72 rounded-full bg-brand-600/15 blur-[120px]" />
<div className="absolute bottom-0 right-0 h-80 w-80 rounded-full bg-brand-700/15 blur-[120px]" />
<div
className="hero-anim-spin absolute right-10 top-16 h-32 w-32 rounded-full border-2 border-dashed border-white/10"
style={{ animationDuration: '24s' }}
/>
<div className="hero-anim-float absolute bottom-20 left-[8%] h-12 w-12 rounded-lg border border-white/10 bg-white/[0.03]" />
<div className="hero-anim-pulse absolute left-[18%] top-[28%] h-2.5 w-2.5 rounded-full bg-brand-400/40" />
<div className="hero-anim-float-rev absolute right-[14%] bottom-[24%] rotate-45 h-8 w-8 rounded-sm border border-white/10 bg-white/[0.03]" />
<div className="hero-anim-float absolute right-[22%] top-[20%] text-2xl font-light text-white/10">+</div>
</div>
{/* Logo */}
<div className="relative z-10 flex items-center gap-2">
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-brand-500 to-brand-700 text-white shadow-lg shadow-brand-600/30 ring-1 ring-inset ring-white/10">
<Building2 className="h-5 w-5" />
</span>
<span className="text-lg font-bold text-white"></span>
</div>
{/* 标语 */}
<div className="relative z-10">
<h2 className="text-3xl font-bold leading-tight text-white xl:text-4xl">
<br />
<span className="bg-gradient-to-r from-white to-slate-400 bg-clip-text text-transparent">
</span>
</h2>
<p className="mt-4 text-lg text-slate-400">&ldquo;&rdquo;</p>
<div className="mt-10 space-y-3">
{[
'一站式物业管理 SaaS 平台',
'覆盖缴费、报修、公告、巡检全流程',
'三端协同,助力物业降本增效',
].map((t) => (
<div key={t} className="flex items-center gap-2.5 text-sm text-slate-300">
<CheckCircle2 className="h-4 w-4 shrink-0 text-brand-400" />
{t}
</div>
))}
</div>
</div>
{/* 版权 */}
<div className="relative z-10 text-xs text-slate-500">
© {new Date().getFullYear()} · SaaS
</div>
</div>
{/* ===== 右侧:表单区(干净浅色)===== */}
<div className="flex min-h-screen items-center justify-center bg-white px-6 py-12">
<div className="w-full max-w-sm">
{/* 移动端 Logo左侧面板在小屏隐藏*/}
<div className="mb-10 flex items-center gap-2 lg:hidden">
<span className="flex h-9 w-9 items-center justify-center rounded-lg bg-gradient-to-br from-slate-800 to-slate-900 text-white shadow-md shadow-slate-900/25 ring-1 ring-inset ring-white/10">
<Building2 className="h-5 w-5" />
</span>
<span className="text-lg font-bold text-slate-900"></span>
</div>
<div className="mb-8">
<span className="text-xs font-semibold uppercase tracking-widest text-brand-600">
Admin
</span>
<h1 className="mt-2 text-2xl font-bold text-slate-900"></h1>
<p className="mt-1.5 text-sm text-slate-500"></p>
</div>
{search.get('expired') && (
<div className="mb-5 rounded-lg border border-yellow-200 bg-yellow-50 px-3.5 py-2.5 text-sm text-yellow-700">
</div>
)}
<form
onSubmit={(e) => {
e.preventDefault();
void handleSubmit(onSubmit)(e);
}}
className="space-y-5"
>
<div className="space-y-1.5">
<Label htmlFor="username"></Label>
<div className="relative">
<User className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<Input
id="username"
className="pl-9"
placeholder="请输入账号"
{...register('username')}
/>
</div>
{errors.username && (
<p className="text-xs text-red-600">{errors.username.message}</p>
)}
</div>
<div className="space-y-1.5">
<Label htmlFor="password"></Label>
<div className="relative">
<Lock className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
<Input
id="password"
type="password"
className="pl-9"
placeholder="请输入密码"
{...register('password')}
/>
</div>
{errors.password && (
<p className="text-xs text-red-600">{errors.password.message}</p>
)}
</div>
{serverError && (
<p className="text-sm text-red-600">{serverError}</p>
)}
<Button
type="submit"
className="w-full bg-slate-900 text-white hover:bg-black"
disabled={isSubmitting}
>
{isSubmitting && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
</Button>
</form>
{/* <p className="mt-8 text-center text-xs text-slate-400">
初始账号admin / 密码123456
</p> */}
</div>
</div>
</div>
);
}