/** * API 客户端 */ const API_BASE = process.env.NEXT_PUBLIC_API_URL || ''; export interface KLine { date: string; open: number; high: number; low: number; close: number; volume: number; amount?: number; pct_chg?: number; volume_ratio?: number; // 量比 turnover_rate?: number; // 换手率 } export interface GameStartResponse { code: string | null; real_code: string; real_name: string; start_date: string; klines: KLine[]; total_candles: number; initial_price: number; initial_index: number; } export interface HealthResponse { status: string; database: string; stocks_count: number; } export class ApiError extends Error { status: number; data: any; constructor(message: string, status: number, data: any) { super(message); this.name = 'ApiError'; this.status = status; this.data = data; } } async function fetchAPI(path: string, options?: RequestInit): Promise { const url = `${API_BASE}${path}`; const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options?.headers, }, }); if (!response.ok) { const errorData = await response.json().catch(() => ({ detail: 'Unknown error' })); // 尝试提取错误信息 let message = 'API request failed'; if (typeof errorData.detail === 'string') { message = errorData.detail; } else if (errorData.detail && errorData.detail.message) { message = errorData.detail.message; } else if (errorData.error) { message = errorData.error; } throw new ApiError(message, response.status, errorData); } return response.json(); } export interface IndexData { date: string; close: number; } export interface AuthResponse { token: string; user_id: number; username: string; } export interface UserInfoResponse { user_id: number; username: string; vip_expire_at: string | null; is_vip: boolean; } export interface VipStatusResponse { is_vip: boolean; vip_expire_at: string | null; } export interface AdminUserItem { user_id: number; username: string; is_vip: boolean; vip_expire_at: string | null; created_at: string; has_payment: boolean; // 是否有支付记录 last_login: string | null; // 最近登录时间 } export interface CreatePaymentResponse { order_id: string; price: number; type: number; } export interface AdminOrderItem { order_id: string; amount: number; pay_type: number; status: string; created_at: string; paid_at: string | null; months: number; } export const api = { health: () => fetchAPI('/api/health'), startGame: (mode = 'random', market?: string, token?: string) => { const params = new URLSearchParams({ mode }); if (market) params.append('market', market); const headers: Record = {}; if (token) headers['Authorization'] = `Bearer ${token}`; return fetchAPI(`/api/game/start?${params}`, { headers }); }, getKline: (code: string, start?: string, end?: string) => { const params = new URLSearchParams({ code }); if (start) params.append('start', start); if (end) params.append('end', end); return fetchAPI(`/api/kline?${params}`); }, getHS300Index: (start?: string, end?: string) => { const params = new URLSearchParams(); if (start) params.append('start', start); if (end) params.append('end', end); return fetchAPI(`/api/index/hs300?${params}`); }, register: (username: string, password: string) => fetchAPI('/api/v1/auth/register', { method: 'POST', body: JSON.stringify({ username, password }), }), login: (username: string, password: string) => fetchAPI('/api/v1/auth/login', { method: 'POST', body: JSON.stringify({ username, password }), }), me: (token: string) => fetchAPI('/api/v1/auth/me', { headers: { Authorization: `Bearer ${token}` }, }), vipStatus: (token: string) => fetchAPI('/api/v1/vip/status', { headers: { Authorization: `Bearer ${token}` }, }), logout: (token: string) => fetchAPI<{ status: string }>('/api/v1/auth/logout', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, }), getUsageToday: (token: string) => fetchAPI<{ used: number; limit: number | null; remaining: number | null; is_vip: boolean }>( '/api/v1/usage/today', { headers: { Authorization: `Bearer ${token}` } } ), createPayment: (type: number, months: number, token: string) => fetchAPI('/api/v1/payment/create', { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ type, months }), }), async checkPaymentStatus(orderId: string): Promise<{ code: number; data: { status: number } }> { return fetchAPI(`/api/v1/payment/check/${orderId}`); }, // 管理员接口 adminGetUsers: (token: string) => fetchAPI('/api/v1/admin/users', { headers: { Authorization: `Bearer ${token}` }, }), adminUpdatePassword: (userId: number, newPassword: string, token: string) => fetchAPI<{ status: string }>(`/api/v1/admin/user/${userId}/password`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ new_password: newPassword }), }), adminUpdateVip: (userId: number, months: number, token: string) => fetchAPI<{ status: string }>(`/api/v1/admin/user/${userId}/vip`, { method: 'POST', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ months }), }), adminDeleteUser: (userId: number, token: string) => fetchAPI<{ status: string }>(`/api/v1/admin/user/${userId}`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}` }, }), adminUpdateOrderStatus: (orderId: string, status: string, token: string) => fetchAPI<{ status: string }>(`/api/v1/admin/order/${orderId}/status`, { method: 'PUT', headers: { Authorization: `Bearer ${token}` }, body: JSON.stringify({ status }), }), adminUserOrders: (userId: number, token: string) => fetchAPI(`/api/v1/admin/user/${userId}/orders`, { headers: { Authorization: `Bearer ${token}` }, }), };