216 lines
6.1 KiB
TypeScript
216 lines
6.1 KiB
TypeScript
// app/api/client.ts
|
|
import { mockData, type MockApiResponse } from './mock';
|
|
|
|
/**
|
|
* API响应类型
|
|
*/
|
|
export type ApiResponse<T> = {
|
|
data?: T;
|
|
error?: string;
|
|
status: number;
|
|
headers?: Record<string, string>; // 添加对响应头的支持
|
|
};
|
|
|
|
export type QueryParams = Record<string, string | number | boolean | undefined>;
|
|
|
|
// 获取 API 基础 URL
|
|
// const API_BASE_URL = '172.18.0.100:3000';
|
|
// const API_BASE_URL = '172.16.0.119:9000/admin';
|
|
const API_BASE_URL = 'http://nas.7bm.co:3000';
|
|
|
|
// 是否使用模拟数据(开发环境使用)
|
|
const USE_MOCK_DATA = false; // 设置为true使用模拟数据,避免API连接问题
|
|
|
|
/**
|
|
* 构建完整的 API URL
|
|
*/
|
|
function buildUrl(endpoint: string, params?: QueryParams): string {
|
|
let fullUrl;
|
|
|
|
// 检查endpoint是否已经是完整URL
|
|
if (endpoint.startsWith('http')) {
|
|
fullUrl = endpoint;
|
|
} else {
|
|
// 确保API_BASE_URL格式正确
|
|
const baseUrl = API_BASE_URL.endsWith('/') ? API_BASE_URL.slice(0, -1) : API_BASE_URL;
|
|
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
fullUrl = `${baseUrl}${path}`;
|
|
}
|
|
|
|
try {
|
|
// 创建URL对象
|
|
const url = new URL(fullUrl);
|
|
|
|
// 添加查询参数
|
|
if (params) {
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
if (value !== undefined) {
|
|
url.searchParams.append(key, String(value));
|
|
}
|
|
});
|
|
}
|
|
|
|
return url.toString();
|
|
} catch (error) {
|
|
console.error('URL构建错误:', fullUrl, error);
|
|
throw new Error(`无法构建URL: ${fullUrl}, 错误: ${error}`);
|
|
}
|
|
}
|
|
|
|
// 超时控制
|
|
const fetchWithTimeout = async (url: string, options: RequestInit, timeout = 5000) => {
|
|
const controller = new AbortController();
|
|
const id = setTimeout(() => controller.abort(), timeout);
|
|
|
|
try {
|
|
console.log(`📦 API 端点: ${url}`);
|
|
const response = await fetch(url, {
|
|
...options,
|
|
signal: controller.signal
|
|
});
|
|
clearTimeout(id);
|
|
return response;
|
|
} catch (error) {
|
|
clearTimeout(id);
|
|
console.error(`📦 API请求失败: ${error}`);
|
|
|
|
// 检查是否是网络连接问题
|
|
if (error instanceof TypeError && error.message.includes('fetch failed')) {
|
|
console.error('网络连接错误,请检查API_BASE_URL配置是否正确');
|
|
}
|
|
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 获取模拟响应数据
|
|
*/
|
|
function getMockResponse<T>(endpoint: string): ApiResponse<T> {
|
|
console.log(`[开发模式] 使用模拟数据: ${endpoint}`);
|
|
|
|
// 移除开头的斜杠以便于匹配
|
|
const path = endpoint.startsWith('/') ? endpoint.substring(1) : endpoint;
|
|
|
|
// 查找匹配的模拟数据
|
|
for (const mockPath in mockData) {
|
|
const normalizedMockPath = mockPath.startsWith('/') ? mockPath.substring(1) : mockPath;
|
|
if (path === normalizedMockPath || path.startsWith(normalizedMockPath + '/')) {
|
|
// 如果是详情查询 (如 /evaluation_points/1)
|
|
if (path.includes('/') && path !== normalizedMockPath) {
|
|
const id = parseInt(path.split('/')[1]);
|
|
const mockDataItem = mockData[mockPath as keyof typeof mockData] as MockApiResponse<unknown>;
|
|
|
|
if (Array.isArray(mockDataItem.data)) {
|
|
const item = mockDataItem.data.find((item: {id: number}) => item.id === id);
|
|
|
|
if (item) {
|
|
return {
|
|
data: {
|
|
code: 0,
|
|
msg: "成功",
|
|
data: item
|
|
} as unknown as T,
|
|
status: 200
|
|
};
|
|
}
|
|
}
|
|
|
|
return { error: '未找到数据', status: 404 };
|
|
}
|
|
|
|
// 返回列表数据
|
|
return {
|
|
data: mockData[mockPath as keyof typeof mockData] as unknown as T,
|
|
status: 200
|
|
};
|
|
}
|
|
}
|
|
|
|
return { error: '没有匹配的模拟数据', status: 404 };
|
|
}
|
|
|
|
/**
|
|
* 通用 API 请求函数
|
|
*/
|
|
export async function apiRequest<T>(
|
|
endpoint: string,
|
|
options: RequestInit = {},
|
|
params?: QueryParams
|
|
): Promise<ApiResponse<T>> {
|
|
// 如果使用模拟数据,直接返回模拟响应
|
|
if (USE_MOCK_DATA) {
|
|
return getMockResponse<T>(endpoint);
|
|
}
|
|
|
|
try {
|
|
// 构建 URL
|
|
const url = buildUrl(endpoint, params);
|
|
|
|
// 设置默认请求头
|
|
const headers = new Headers(options.headers || {});
|
|
if (!headers.has('Content-Type') && options.method !== 'GET') {
|
|
headers.set('Content-Type', 'application/json');
|
|
}
|
|
if (!headers.has('Accept')) {
|
|
headers.set('Accept', 'application/json');
|
|
}
|
|
|
|
// 发送请求,5秒超时
|
|
const response = await fetchWithTimeout(url, {
|
|
...options,
|
|
headers
|
|
}, 10000);
|
|
|
|
// 解析响应
|
|
let data = null;
|
|
const contentType = response.headers.get('content-type');
|
|
if (contentType && contentType.includes('application/json') && response.status !== 204) {
|
|
data = await response.json();
|
|
}
|
|
|
|
// 收集响应头信息
|
|
const responseHeaders: Record<string, string> = {};
|
|
response.headers.forEach((value, key) => {
|
|
responseHeaders[key] = value;
|
|
});
|
|
|
|
// 检查API返回的状态码
|
|
if (data && 'code' in data && data.code !== 0) {
|
|
console.error(`API请求失败: ${data.msg || '未知错误'} - ${url}`);
|
|
return {
|
|
error: data.msg || '请求失败',
|
|
status: response.status,
|
|
headers: responseHeaders
|
|
};
|
|
}
|
|
|
|
if (!response.ok) {
|
|
console.error(`HTTP请求失败: ${response.status} - ${url}`);
|
|
return {
|
|
error: data?.msg || `请求失败: ${response.status}`,
|
|
status: response.status,
|
|
headers: responseHeaders
|
|
};
|
|
}
|
|
|
|
return {
|
|
data,
|
|
status: response.status,
|
|
headers: responseHeaders
|
|
};
|
|
} catch (error) {
|
|
console.error('API请求失败:', error);
|
|
|
|
// 如果超时或网络错误,使用模拟数据(仅开发环境)
|
|
if (process.env.NODE_ENV !== 'production') {
|
|
console.warn('自动使用模拟数据作为回退');
|
|
return getMockResponse<T>(endpoint);
|
|
}
|
|
|
|
return {
|
|
error: error instanceof Error ? error.message : '未知错误',
|
|
status: 500
|
|
};
|
|
}
|
|
} |