Spaces:
Running
Running
| /** | |
| * 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<T>(path: string, options?: RequestInit): Promise<T> { | |
| 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<HealthResponse>('/api/health'), | |
| startGame: (mode = 'random', market?: string, token?: string) => { | |
| const params = new URLSearchParams({ mode }); | |
| if (market) params.append('market', market); | |
| const headers: Record<string, string> = {}; | |
| if (token) headers['Authorization'] = `Bearer ${token}`; | |
| return fetchAPI<GameStartResponse>(`/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<KLine[]>(`/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<IndexData[]>(`/api/index/hs300?${params}`); | |
| }, | |
| register: (username: string, password: string) => | |
| fetchAPI<AuthResponse>('/api/v1/auth/register', { | |
| method: 'POST', | |
| body: JSON.stringify({ username, password }), | |
| }), | |
| login: (username: string, password: string) => | |
| fetchAPI<AuthResponse>('/api/v1/auth/login', { | |
| method: 'POST', | |
| body: JSON.stringify({ username, password }), | |
| }), | |
| me: (token: string) => | |
| fetchAPI<UserInfoResponse>('/api/v1/auth/me', { | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }), | |
| vipStatus: (token: string) => | |
| fetchAPI<VipStatusResponse>('/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<CreatePaymentResponse>('/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<AdminUserItem[]>('/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<AdminOrderItem[]>(`/api/v1/admin/user/${userId}/orders`, { | |
| headers: { Authorization: `Bearer ${token}` }, | |
| }), | |
| }; | |