feat: use api for manage people

- wrap the backend apis
- post people by api
- list people by api
- auto fill people form by post input api
- auto fill people form by post image api
This commit is contained in:
2025-10-25 15:50:29 +08:00
parent ddb04ff15e
commit 28de10061a
12 changed files with 968 additions and 50 deletions

163
src/apis/request.ts Normal file
View File

@@ -0,0 +1,163 @@
// 基础请求工具函数
import { API_CONFIG } from './config';
import type { HTTPValidationError } from './types';
// 请求选项接口
export interface RequestOptions {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
body?: any;
timeout?: 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;
}
}
// 基础请求函数
export async function request<T = any>(
url: string,
options: RequestOptions = {}
): Promise<T> {
const {
method = 'GET',
headers = {},
body,
timeout = API_CONFIG.TIMEOUT,
} = options;
const fullUrl = url.startsWith('http') ? url : `${API_CONFIG.BASE_URL}${url}`;
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
const requestHeaders: Record<string, string> = {
...API_CONFIG.HEADERS,
...headers,
};
let requestBody: string | FormData | undefined;
if (body instanceof FormData) {
// 对于 FormData不设置 Content-Type让浏览器自动设置
delete requestHeaders['Content-Type'];
requestBody = body;
} else if (body) {
requestBody = JSON.stringify(body);
}
const response = await fetch(fullUrl, {
method,
headers: requestHeaders,
body: requestBody,
signal: controller.signal,
});
clearTimeout(timeoutId);
if (!response.ok) {
let errorData: any;
try {
errorData = await response.json();
} catch {
errorData = { message: response.statusText };
}
throw new ApiError(
errorData.message || `HTTP ${response.status}: ${response.statusText}`,
response.status,
errorData
);
}
// 检查响应是否有内容
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
// 如果没有 JSON 内容,返回空对象
return {} as T;
}
} catch (error) {
clearTimeout(timeoutId);
if (error instanceof ApiError) {
throw error;
}
if (error instanceof Error) {
if (error.name === 'AbortError') {
throw new ApiError('请求超时', 408);
}
throw new ApiError(error.message);
}
throw new ApiError('未知错误');
}
}
// GET 请求
export function get<T = any>(url: string, params?: Record<string, any>): Promise<T> {
let fullUrl = url;
if (params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
searchParams.append(key, String(value));
}
});
const queryString = searchParams.toString();
if (queryString) {
fullUrl += (url.includes('?') ? '&' : '?') + queryString;
}
}
return request<T>(fullUrl, { method: 'GET' });
}
// POST 请求
export function post<T = any>(url: string, data?: any, options?: Partial<RequestOptions>): Promise<T> {
return request<T>(url, {
method: 'POST',
body: data,
...options,
});
}
// PUT 请求
export function put<T = any>(url: string, data?: any): Promise<T> {
return request<T>(url, {
method: 'PUT',
body: data,
});
}
// DELETE 请求
export function del<T = any>(url: string): Promise<T> {
return request<T>(url, { method: 'DELETE' });
}
// 文件上传请求
export function upload<T = any>(url: string, file: File, fieldName = 'file', options?: Partial<RequestOptions>): Promise<T> {
const formData = new FormData();
formData.append(fieldName, file);
return request<T>(url, {
method: 'POST',
body: formData,
...options,
});
}