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 Admin
+
+
管理后台登录
+
+
+
+
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;