chore: add frontend/src/services/apiClient.ts
This commit is contained in:
@@ -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<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<ApiResponse<T>> {
|
||||
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<T>(endpoint: string, params?: Record<string, string | number>): Promise<ApiResponse<T>> {
|
||||
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<T>(url, { method: 'GET' });
|
||||
}
|
||||
|
||||
// API: POST 请求 - 用于创建资源
|
||||
async post<T>(endpoint: string, data?: unknown): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'POST',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// API: PUT 请求 - 用于全量更新资源
|
||||
async put<T>(endpoint: string, data?: unknown): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, {
|
||||
method: 'PUT',
|
||||
body: data ? JSON.stringify(data) : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// API: DELETE 请求 - 用于删除资源
|
||||
async delete<T>(endpoint: string): Promise<ApiResponse<T>> {
|
||||
return this.request<T>(endpoint, { method: 'DELETE' });
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例 - 全局共享一个 ApiClient 实例
|
||||
export const apiClient = new ApiClient(API_BASE_URL);
|
||||
Reference in New Issue
Block a user