diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 11c78a2..3bd9d95 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -1,4 +1,5 @@
import type { Metadata } from "next";
+import { AuthProvider } from "@/lib/auth-context";
import "./globals.css";
export const metadata: Metadata = {
@@ -13,7 +14,9 @@ export default function RootLayout({
}>) {
return (
-
{children}
+
+ {children}
+
);
}
diff --git a/frontend/src/app/login/page.tsx b/frontend/src/app/login/page.tsx
index 94e86bc..b56544f 100644
--- a/frontend/src/app/login/page.tsx
+++ b/frontend/src/app/login/page.tsx
@@ -1,8 +1,35 @@
-/** 登录页 — 占位页面。 */
+"use client";
-import { Lock } from "lucide-react";
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+import { Lock, Mail, Loader2 } from "lucide-react";
+import { useAuth } from "@/lib/auth-context";
+/**
+ * 登录页 — 对接后端 auth API。
+ */
export default function LoginPage() {
+ const router = useRouter();
+ const { login } = useAuth();
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const [error, setError] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+ setError("");
+ setIsLoading(true);
+ try {
+ await login(email, password);
+ router.push("/dashboard");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "登录失败");
+ } finally {
+ setIsLoading(false);
+ }
+ }
+
return (
@@ -13,7 +40,68 @@ export default function LoginPage() {
AIPortPilot
AI+ 投后管理平台
-
登录功能开发中
+
+
);
diff --git a/frontend/src/lib/auth-context.tsx b/frontend/src/lib/auth-context.tsx
new file mode 100644
index 0000000..23faacc
--- /dev/null
+++ b/frontend/src/lib/auth-context.tsx
@@ -0,0 +1,108 @@
+/** 前端认证状态管理。 */
+
+"use client";
+
+import { createContext, useContext, useEffect, useState, type ReactNode } from "react";
+
+interface AuthUser {
+ id: string;
+ email: string;
+ name: string;
+ role: string;
+ tenant_id: string;
+}
+
+interface AuthContextValue {
+ user: AuthUser | null;
+ token: string | null;
+ isLoading: boolean;
+ login: (email: string, password: string) => Promise;
+ logout: () => void;
+}
+
+const AuthContext = createContext(undefined);
+
+const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000/api/v1";
+
+/**
+ * 认证 Provider,管理登录状态和 token。
+ */
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+ const [token, setToken] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+
+ useEffect(() => {
+ const savedToken = localStorage.getItem("token");
+ if (savedToken) {
+ setToken(savedToken);
+ fetchUserInfo(savedToken)
+ .then(setUser)
+ .catch(() => {
+ localStorage.removeItem("token");
+ localStorage.removeItem("refresh_token");
+ })
+ .finally(() => setIsLoading(false));
+ } else {
+ setIsLoading(false);
+ }
+ }, []);
+
+ async function login(email: string, password: string) {
+ const response = await fetch(`${API_BASE_URL}/auth/login`, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ email, password }),
+ });
+
+ const data = await response.json();
+ if (!response.ok || data.code !== 0) {
+ throw new Error(data.detail || data.message || "登录失败");
+ }
+
+ const { access_token, refresh_token } = data.data;
+ localStorage.setItem("token", access_token);
+ localStorage.setItem("refresh_token", refresh_token);
+ setToken(access_token);
+ const userInfo = await fetchUserInfo(access_token);
+ setUser(userInfo);
+ }
+
+ function logout() {
+ localStorage.removeItem("token");
+ localStorage.removeItem("refresh_token");
+ setToken(null);
+ setUser(null);
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+/**
+ * 获取当前用户信息。
+ */
+async function fetchUserInfo(token: string): Promise {
+ const response = await fetch(`${API_BASE_URL}/auth/me`, {
+ headers: { Authorization: `Bearer ${token}` },
+ });
+ const data = await response.json();
+ if (!response.ok || data.code !== 0) {
+ throw new Error("获取用户信息失败");
+ }
+ return data.data;
+}
+
+/**
+ * 使用认证上下文。
+ */
+export function useAuth() {
+ const ctx = useContext(AuthContext);
+ if (!ctx) {
+ throw new Error("useAuth 必须在 AuthProvider 内使用");
+ }
+ return ctx;
+}