diff --git a/frontend/src/services/apiClient.ts b/frontend/src/services/apiClient.ts new file mode 100644 index 0000000..cf8e318 --- /dev/null +++ b/frontend/src/services/apiClient.ts @@ -0,0 +1,100 @@ +/** + * API 客户端 - ApiClient + * 功能:封装 fetch API,提供统一的 HTTP 请求处理、错误捕获和响应解析 + * 设计模式:类封装 + 泛型支持,确保类型安全 + * 使用方式:通过导出单例 apiClient 调用,避免重复实例化 + */ +import type { ApiResponse } from '../types'; + +// 直接使用后端 URL,避免 Vite 代理问题 +// 生产环境应改为环境变量 VITE_API_BASE_URL +const API_BASE_URL = 'http://localhost:3001/api'; + +class ApiClient { + private baseUrl: string; + + constructor(baseUrl: string) { + this.baseUrl = baseUrl; + } + + /** + * 核心请求方法 - 处理 URL 拼接、请求配置、响应解析和错误捕获 + * @param endpoint - API 路径(如 '/accounts') + * @param options - fetch 配置对象,支持 method、headers、body 等 + * @returns 标准化的 ApiResponse 对象 + */ + private async request( + endpoint: string, + options: RequestInit = {} + ): Promise> { + const url = `${this.baseUrl}${endpoint}`; + + // 默认设置 Content-Type 为 application/json,允许调用方覆盖 + const config: RequestInit = { + headers: { + 'Content-Type': 'application/json', + ...options.headers, + }, + ...options, + }; + + try { + const response = await fetch(url, config); + const data = await response.json(); + + // 根据 HTTP 状态码判断请求是否成功 + if (!response.ok) { + throw new Error(data.message || 'Request failed'); + } + + return data; + } catch (error) { + // 统一错误日志输出,方便调试 + console.error('API Error:', error); + throw error; + } + } + + /** + * GET 请求 - 支持 URL 查询参数拼接 + * @param endpoint - API 路径 + * @param params - 查询参数对象,自动忽略 undefined/null 值 + */ + async get(endpoint: string, params?: Record): Promise> { + let url = endpoint; + if (params) { + const searchParams = new URLSearchParams(); + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + searchParams.append(key, String(value)); + } + }); + url = `${endpoint}?${searchParams.toString()}`; + } + return this.request(url, { method: 'GET' }); + } + + // API: POST 请求 - 用于创建资源 + async post(endpoint: string, data?: unknown): Promise> { + return this.request(endpoint, { + method: 'POST', + body: data ? JSON.stringify(data) : undefined, + }); + } + + // API: PUT 请求 - 用于全量更新资源 + async put(endpoint: string, data?: unknown): Promise> { + return this.request(endpoint, { + method: 'PUT', + body: data ? JSON.stringify(data) : undefined, + }); + } + + // API: DELETE 请求 - 用于删除资源 + async delete(endpoint: string): Promise> { + return this.request(endpoint, { method: 'DELETE' }); + } +} + +// 导出单例 - 全局共享一个 ApiClient 实例 +export const apiClient = new ApiClient(API_BASE_URL);