From 34b85f68ae8148785e6bcb95164d02bd92fe3b77 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 2 Mar 2026 02:57:45 -0800 Subject: [PATCH] =?UTF-8?q?feat(admin-web):=20=E7=99=BB=E5=BD=95=E9=A1=B5?= =?UTF-8?q?=20+=20Auth=20Guard=20+=20API=20URL=20=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E5=9F=9F=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新建 /login 页面(邮箱/密码登录,对接 auth-context) - AdminLayout 添加 auth guard:未登录自动跳转 /login - api-client 默认 URL 从 localhost:8080 → https://api.gogenex.com - Header 头像显示用户首字母,点击登出 - i18n 补充 header_logout (zh/en/ja) API 联通验证(全部正常): - POST /api/v1/auth/sms/send → 400 (手机号未注册) - POST /api/v1/auth/login → 401 (密码错误) - POST /api/v1/auth/register → 400 (验证码过期) Co-Authored-By: Claude Opus 4.6 --- frontend/admin-web/src/app/login/page.tsx | 157 ++++++++++++++++++ frontend/admin-web/src/i18n/locales.ts | 3 + .../admin-web/src/layouts/AdminLayout.tsx | 47 ++++-- frontend/admin-web/src/lib/api-client.ts | 2 +- 4 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 frontend/admin-web/src/app/login/page.tsx diff --git a/frontend/admin-web/src/app/login/page.tsx b/frontend/admin-web/src/app/login/page.tsx new file mode 100644 index 0000000..7b0daef --- /dev/null +++ b/frontend/admin-web/src/app/login/page.tsx @@ -0,0 +1,157 @@ +'use client'; + +import React, { useState, FormEvent } from 'react'; +import { useRouter } from 'next/navigation'; +import Image from 'next/image'; +import { useAuth } from '@/lib/auth-context'; + +export default function LoginPage() { + const router = useRouter(); + const { login, isAuthenticated } = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + // 已登录则跳转 + if (isAuthenticated) { + router.replace('/dashboard'); + return null; + } + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(''); + setLoading(true); + try { + await login(email, password); + router.replace('/dashboard'); + } catch (err: unknown) { + const msg = + (err as { response?: { data?: { message?: string } } })?.response?.data + ?.message || '登录失败,请检查账号密码'; + setError(msg); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+ Genex + Genex Admin +
+

管理后台登录

+ +
+ + setEmail(e.target.value)} + required + autoFocus + /> + + + setPassword(e.target.value)} + required + /> + + {error &&

{error}

} + + +
+ +

Genex 券金融平台 © 2025

+
+
+ ); +} + +/* ---------- inline styles (no extra CSS file) ---------- */ +const styles: Record = { + wrapper: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: '100vh', + background: 'linear-gradient(135deg, #F3F1FF 0%, #E8E5FF 50%, #F8F9FC 100%)', + }, + card: { + width: 400, + padding: '40px 36px', + borderRadius: 16, + background: '#fff', + boxShadow: '0 8px 32px rgba(108,92,231,0.10)', + }, + logoRow: { + display: 'flex', + alignItems: 'center', + gap: 10, + marginBottom: 8, + }, + brand: { + fontSize: 20, + fontWeight: 700, + color: '#6C5CE7', + }, + title: { + fontSize: 24, + fontWeight: 600, + color: '#262B3A', + margin: '16px 0 28px', + }, + form: { + display: 'flex', + flexDirection: 'column' as const, + gap: 12, + }, + label: { + fontSize: 14, + fontWeight: 500, + color: '#5C6478', + }, + input: { + padding: '10px 14px', + fontSize: 15, + border: '1px solid #E4E7F0', + borderRadius: 8, + outline: 'none', + transition: 'border-color 0.2s', + }, + error: { + fontSize: 13, + color: '#FF4757', + margin: 0, + }, + btn: { + marginTop: 8, + padding: '12px 0', + fontSize: 16, + fontWeight: 600, + color: '#fff', + background: '#6C5CE7', + border: 'none', + borderRadius: 8, + cursor: 'pointer', + transition: 'opacity 0.2s', + }, + footer: { + textAlign: 'center' as const, + marginTop: 28, + fontSize: 12, + color: '#A0A8BE', + }, +}; diff --git a/frontend/admin-web/src/i18n/locales.ts b/frontend/admin-web/src/i18n/locales.ts index 51c74b2..965d45a 100644 --- a/frontend/admin-web/src/i18n/locales.ts +++ b/frontend/admin-web/src/i18n/locales.ts @@ -134,6 +134,7 @@ const translations: Record> = { // ── Header ── 'header_search_placeholder': '搜索用户/订单/交易...', 'header_ai_assistant': 'AI 助手', + 'header_logout': '退出登录', // ── Dashboard ── 'dashboard_title': '运营总览', @@ -866,6 +867,7 @@ const translations: Record> = { // ── Header ── 'header_search_placeholder': 'Search users/orders/trades...', 'header_ai_assistant': 'AI Assistant', + 'header_logout': 'Logout', // ── Dashboard ── 'dashboard_title': 'Dashboard', @@ -1598,6 +1600,7 @@ const translations: Record> = { // ── Header ── 'header_search_placeholder': 'ユーザー/注文/取引を検索...', 'header_ai_assistant': 'AIアシスタント', + 'header_logout': 'ログアウト', // ── Dashboard ── 'dashboard_title': '運営概要', diff --git a/frontend/admin-web/src/layouts/AdminLayout.tsx b/frontend/admin-web/src/layouts/AdminLayout.tsx index 7678d5b..6f7a031 100644 --- a/frontend/admin-web/src/layouts/AdminLayout.tsx +++ b/frontend/admin-web/src/layouts/AdminLayout.tsx @@ -4,6 +4,7 @@ import React, { useState } from 'react'; import { usePathname, useRouter } from 'next/navigation'; import Link from 'next/link'; import { t } from '@/i18n/locales'; +import { useAuth } from '@/lib/auth-context'; /** * D. Web管理前端 - 主布局 @@ -95,8 +96,17 @@ const navItems: NavItem[] = [ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children }) => { const pathname = usePathname(); const router = useRouter(); + const { isAuthenticated, isLoading, user, logout } = useAuth(); const [collapsed, setCollapsed] = useState(false); + // 未登录 → /login + if (!isLoading && !isAuthenticated) { + router.replace('/login'); + return null; + } + // 加载中显示空白 + if (isLoading) return null; + // Derive activeKey from current pathname const activeKey = pathname.replace(/^\//, '') || 'dashboard'; @@ -324,19 +334,30 @@ export const AdminLayout: React.FC<{ children: React.ReactNode }> = ({ children borderRadius: '50%', }} /> - {/* Admin avatar */} -
- A + {/* Admin avatar + logout */} +
+
+ {user?.name?.charAt(0)?.toUpperCase() || 'A'} +
diff --git a/frontend/admin-web/src/lib/api-client.ts b/frontend/admin-web/src/lib/api-client.ts index 9731cbb..d10782a 100644 --- a/frontend/admin-web/src/lib/api-client.ts +++ b/frontend/admin-web/src/lib/api-client.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080'; +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'https://api.gogenex.com'; class ApiClient { private client: AxiosInstance;