| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- /**
- * 管理后台登录门 - 对接真实 API(演示环境默认 localhost:8081)
- * 已登录(localStorage 持有 admin token)渲染管理界面;否则显示管理员登录表单
- */
- 'use client';
- import { useState, type ReactNode } from 'react';
- import { AdminShell } from '@/components/admin/AdminShell';
- /** 演示环境 API 地址(生产替换为真实网关域名) */
- const API_BASE = process.env['NEXT_PUBLIC_API_URL'] || 'http://localhost:8081/v1';
- export function AdminGate({ children }: { children: ReactNode }) {
- const [token, setToken] = useState<string | null>(() => {
- try {
- return localStorage.getItem('dotouch_admin_token');
- } catch {
- return null;
- }
- });
- const [email, setEmail] = useState('');
- const [password, setPassword] = useState('');
- const [msg, setMsg] = useState<string | null>(null);
- const [msgType, setMsgType] = useState<'err' | 'ok'>('err');
- const [loading, setLoading] = useState(false);
- const logout = () => {
- try {
- localStorage.removeItem('dotouch_admin_token');
- localStorage.removeItem('dotouch_admin_user');
- } catch { /* 忽略 */ }
- setToken(null);
- };
- const login = async () => {
- if (!email || !password) {
- setMsg('请输入账号和密码');
- setMsgType('err');
- return;
- }
- setLoading(true);
- setMsg(null);
- try {
- const resp = await fetch(`${API_BASE}/auth/login`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ email, password }),
- });
- const data = await resp.json().catch(() => ({}));
- if (resp.ok && data.accessToken && data.user?.role === 'admin') {
- try {
- localStorage.setItem('dotouch_admin_token', data.accessToken);
- localStorage.setItem('dotouch_admin_user', email);
- } catch { /* 忽略 */ }
- setToken(data.accessToken);
- } else if (resp.ok && data.accessToken) {
- setMsg('该账号无管理员权限(需要 role=admin)');
- setMsgType('err');
- } else {
- setMsg(data.message || '登录失败,请检查账号密码');
- setMsgType('err');
- }
- } catch {
- setMsg('无法连接 API,请确认演示服务已启动(localhost:8081)');
- setMsgType('err');
- } finally {
- setLoading(false);
- }
- };
- if (token) {
- return (
- <AdminShell>
- <button
- type="button"
- onClick={logout}
- className="fixed right-4 top-2 z-[500] rounded-lg border border-white/[0.08] bg-brand-card px-3 py-1 text-xs text-brand-muted transition-colors hover:border-[rgba(255,107,107,.4)] hover:text-[#FF8A8A]"
- >
- 退出登录
- </button>
- {children}
- </AdminShell>
- );
- }
- return (
- <div className="flex min-h-screen items-center justify-center bg-brand-bg px-5">
- <div className="w-[380px] max-w-full rounded-[20px] border border-[rgba(124,92,255,.4)] bg-brand-card px-[30px] py-[34px] shadow-[0_24px_80px_rgba(0,0,0,.6)]">
- <div className="mb-6 text-center">
- <div className="text-xl font-extrabold tracking-wide">
- DoTouch<span className="text-brand-purple-l">.AI</span>
- </div>
- <div className="mt-1 text-[12.5px] text-brand-muted">平台管理后台 · 管理员登录</div>
- </div>
- {msg && (
- <div
- className={`mb-3 rounded-[9px] border px-3 py-[9px] text-[12.5px] ${
- msgType === 'err'
- ? 'border-[rgba(255,107,107,.35)] bg-[rgba(255,107,107,.1)] text-[#FF8A8A]'
- : 'border-[rgba(0,229,160,.3)] bg-[rgba(0,229,160,.1)] text-brand-green-l'
- }`}
- >
- {msg}
- </div>
- )}
- <label className="mb-1.5 block text-[12.5px] text-brand-muted">账号(邮箱)</label>
- <input
- type="email"
- value={email}
- onChange={(e) => setEmail(e.target.value)}
- placeholder="admin@dotouch.ai"
- className="mb-4 w-full rounded-[10px] border border-white/[0.08] bg-brand-card2 px-[14px] py-[11px] text-sm text-brand-text outline-none focus:border-brand-purple"
- />
- <label className="mb-1.5 block text-[12.5px] text-brand-muted">密码</label>
- <input
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- onKeyDown={(e) => e.key === 'Enter' && login()}
- placeholder="••••••••"
- className="mb-6 w-full rounded-[10px] border border-white/[0.08] bg-brand-card2 px-[14px] py-[11px] text-sm text-brand-text outline-none focus:border-brand-purple"
- />
- <button
- type="button"
- onClick={login}
- disabled={loading}
- className="w-full justify-center rounded-[10px] bg-gradient-to-br from-brand-purple to-[#5C3DF0] px-[18px] py-[11px] text-sm font-semibold text-white shadow-[0_4px_20px_rgba(124,92,255,.35)] transition-all hover:-translate-y-px hover:shadow-[0_6px_28px_rgba(124,92,255,.5)] disabled:cursor-not-allowed disabled:opacity-60"
- >
- {loading ? '登录中…' : '登 录'}
- </button>
- <div className="mt-5 text-center text-[11.5px] text-brand-muted">
- 演示账号:admin@dotouch.ai / Admin2026!
- </div>
- </div>
- </div>
- );
- }
|