feat: 实现前端认证系统,WebSocket 连接携带 JWT token

- 新增 AuthContext/AuthProvider:登录/注册状态管理,JWT 自动刷新
- 新增 AuthPage 组件:登录/注册表单,支持模式切换和前端校验
- 新增 api.ts:封装 auth REST API 客户端(register/login/refresh/logout)
- WebSocket 连接时拼接 ?token=<jwt>,重连自动携带
- useVisionSession 接受 accessToken 参数并透传
- App.tsx 包裹 AuthProvider,未登录时显示登录页
- storage.ts 新增 token/user 的 localStorage 存储
- i18n 新增中/英/日三语 auth 翻译
- App.css 新增 auth 页面和用户 badge 样式
This commit is contained in:
2026-06-14 18:38:45 +08:00
parent a1ae7740f1
commit 8de956719c
12 changed files with 808 additions and 12 deletions

106
frontend/src/lib/api.ts Normal file
View File

@@ -0,0 +1,106 @@
// ============================================================
// HTTP API 客户端
// 职责:封装 REST API 请求auth、conversations 等)
// ============================================================
const API_BASE = "/api";
interface ApiResponse<T> {
data?: T;
error?: { code: string; message: string };
status: number;
}
async function request<T>(
path: string,
options: RequestInit = {}
): Promise<ApiResponse<T>> {
const url = `${API_BASE}${path}`;
const headers: Record<string, string> = {
"Content-Type": "application/json",
...(options.headers as Record<string, string>),
};
try {
const res = await fetch(url, { ...options, headers });
const status = res.status;
if (res.status === 204) {
return { status };
}
const body = await res.json();
if (!res.ok) {
return {
error: { code: body.error || "UNKNOWN", message: body.message || "请求失败" },
status,
};
}
return { data: body as T, status };
} catch (err) {
return {
error: { code: "NETWORK_ERROR", message: "网络连接失败,请检查网络" },
status: 0,
};
}
}
function authHeaders(accessToken: string): Record<string, string> {
return { Authorization: `Bearer ${accessToken}` };
}
// ---- Auth API ----
export interface AuthUser {
id: string;
username: string;
created_at: string;
}
export interface AuthResponse {
user: AuthUser;
access_token: string;
refresh_token: string;
}
export async function register(
username: string,
password: string
): Promise<ApiResponse<AuthResponse>> {
return request<AuthResponse>("/auth/register", {
method: "POST",
body: JSON.stringify({ username, password }),
});
}
export async function login(
username: string,
password: string
): Promise<ApiResponse<AuthResponse>> {
return request<AuthResponse>("/auth/login", {
method: "POST",
body: JSON.stringify({ username, password }),
});
}
export async function refreshToken(
refresh_token: string
): Promise<ApiResponse<AuthResponse>> {
return request<AuthResponse>("/auth/refresh", {
method: "POST",
body: JSON.stringify({ refresh_token }),
});
}
export async function logout(
accessToken: string,
refreshToken: string
): Promise<ApiResponse<{ message: string }>> {
return request<{ message: string }>("/auth/logout", {
method: "POST",
headers: authHeaders(accessToken),
body: JSON.stringify({ refresh_token: refreshToken }),
});
}