// app/api/client.ts import { mockData, type MockApiResponse } from './mock'; /** * API响应类型 */ export type ApiResponse = { data?: T; error?: string; status: number; headers?: Record; // 添加对响应头的支持 }; export type QueryParams = Record; // 获取 API 基础 URL // const API_BASE_URL = '172.18.0.100:3000'; // const API_BASE_URL = '172.16.0.119:9000/admin'; export 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(endpoint: string): ApiResponse { 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; 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( endpoint: string, options: RequestInit = {}, params?: QueryParams ): Promise> { // 如果使用模拟数据,直接返回模拟响应 if (USE_MOCK_DATA) { return getMockResponse(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'); } // 针对 PostgREST 的额外处理 if (endpoint.includes('evaluation_point_groups') && (options.method === 'POST' || options.method === 'PATCH')) { console.log('使用 PostgREST 特定配置处理请求'); // 确保请求体是有效的 JSON 对象 if (options.body && typeof options.body === 'string') { try { JSON.parse(options.body); // 验证 JSON 是否有效 } catch (e) { console.error('请求体不是有效的 JSON:', options.body); throw new Error('请求体必须是有效的 JSON'); } } } console.log(`发送 ${options.method || 'GET'} 请求到: ${url}`); if (options.body) { console.log(`请求体: ${options.body}`); } // 发送请求,10秒超时 const response = await fetchWithTimeout(url, { ...options, headers }, 10000); // 解析响应 let data = null; let responseText = ''; try { const contentType = response.headers.get('content-type'); responseText = await response.text(); if (contentType && contentType.includes('application/json') && response.status !== 204 && responseText) { try { data = JSON.parse(responseText); } catch (e) { console.error('响应不是有效的 JSON:', responseText); throw new Error('服务器返回无效的 JSON 响应'); } } } catch (e) { console.error('解析响应失败:', e); console.log('原始响应:', responseText); } // 收集响应头信息 const responseHeaders: Record = {}; response.headers.forEach((value, key) => { responseHeaders[key] = value; }); // 打印响应信息 // console.log(`响应状态: ${response.status} ${response.statusText}`); // console.log(`响应头:`, responseHeaders); // console.log(`响应体:`, data || responseText); // 检查 PostgREST 特定错误 if (!response.ok) { if (response.status === 400) { console.error('PostgREST 错误 - 无效请求:', data || responseText); return { error: '无效的请求格式,请检查数据格式是否正确', status: response.status, headers: responseHeaders }; } else if (response.status === 401) { console.error('PostgREST 错误 - 未授权:', data || responseText); return { error: '未授权,请检查认证信息', status: response.status, headers: responseHeaders }; } else if (response.status === 403) { console.error('PostgREST 错误 - 禁止访问:', data || responseText); return { error: '没有权限执行此操作', status: response.status, headers: responseHeaders }; } else if (response.status === 404) { console.error('PostgREST 错误 - 资源不存在:', data || responseText); return { error: '请求的资源不存在', status: response.status, headers: responseHeaders }; } else { console.error(`HTTP请求失败: ${response.status} - ${url}`, data || responseText); return { error: data?.msg || `请求失败: ${response.status}`, status: response.status, headers: responseHeaders }; } } // 检查API返回的状态码 if (data && 'code' in data && data.code !== 0) { console.error(`API请求失败: ${data.msg || '未知错误'} - ${url}`); return { error: data.msg || '请求失败', 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(endpoint); } return { error: error instanceof Error ? error.message : '未知错误', status: 500 }; } }