import { ApiError } from '@/types'; const API_BASE = import.meta.env.VITE_API_BASE || ''; function toCamelCase(str: string): string { return str.replace(/_([a-z])/g, (_, letter: string) => letter.toUpperCase()); } function toSnakeCase(str: string): string { return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`); } function transformKeys(obj: unknown, transformer: (key: string) => string): T { if (Array.isArray(obj)) { return obj.map((item) => transformKeys(item, transformer)) as T; } if (obj !== null && typeof obj === 'object') { const result: Record = {}; for (const [key, value] of Object.entries(obj as Record)) { result[transformer(key)] = transformKeys(value, transformer); } return result as T; } return obj as T; } export function fromApi(data: unknown): T { return transformKeys(data, toCamelCase); } export function toApi(data: unknown): T { return transformKeys(data, toSnakeCase); } export async function request( method: string, path: string, body?: unknown, ): Promise { const url = `${API_BASE}${path}`; const options: RequestInit = { method, headers: { 'Content-Type': 'application/json' }, }; if (body !== undefined) { options.body = JSON.stringify(toApi(body)); } const response = await fetch(url, options); if (!response.ok) { let message = `请求失败 (${response.status})`; let code: string | undefined; try { const errorData = await response.json(); if (typeof errorData === 'object' && errorData !== null) { // 提取结构化错误响应 if ('error' in errorData && typeof errorData.error === 'string') { message = errorData.error; } else if ('message' in errorData && typeof errorData.message === 'string') { message = errorData.message; } // 提取错误码 if ('code' in errorData && typeof errorData.code === 'string') { code = errorData.code; } } } catch { // ignore JSON parse error } throw new ApiError(response.status, message, code); } if (response.status === 204) { return undefined as T; } const data = await response.json(); return fromApi(data); }