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