import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'; // 基础配置 export const BASE_URL = 'https://yifan.action-ai.cn/api/v1/yifan'; // export const BASE_URL = 'http://192.168.1.24:8001/api/v1/yifan'; // export const BASE_URL = 'http://localhost:8001/api/v1/yifan'; const TIMEOUT = 60000; // 60秒超时 // 开发模式配置(简化版本,避免 TypeScript 类型问题) const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost'; const enableDebugLog = isDev; // 开发环境启用详细日志 // 导入登录相关工具 import { navigateToLogin, clearToken } from './auth'; import { showLoading as uniShowLoading, hideLoading as uniHideLoading, showToast as uniShowToast } from './uni-compat'; // 响应数据类型 interface ApiResponse { code: number; msg: string; data: T; } // 请求配置类型 export interface RequestConfig { url: string; method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; data?: object; params?: object; headers?: object; showLoading?: boolean; showError?: boolean; timeout?: number; /** 为 false 时,业务/HTTP 鉴权类错误不执行 clearToken + 跳转登录(用于仅更新资料等场景) */ clearAuthOnError?: boolean; } // 创建 axios 实例 const axiosInstance = axios.create({ baseURL: BASE_URL, timeout: TIMEOUT, headers: { 'Content-Type': 'application/json', }, // 禁用自动 JSON 转换,我们将手动处理以避免解析错误 transformResponse: [(data) => { // 如果数据为空,直接返回 if (!data) return data; // 如果已经是对象,直接返回 if (typeof data === 'object') return data; // 如果是字符串,手动解析 if (typeof data === 'string') { try { // 清理 BOM 和其他不可见字符 const cleanData = data.trim().replace(/^\uFEFF/, ''); // 检查是否是 HTML if (cleanData.startsWith('<') || cleanData.startsWith(' { // 获取 token const token = localStorage.getItem('token'); if (token) { try { // 尝试解析 JSON(如果 token 是 JSON 字符串格式存储的) const parsedToken = JSON.parse(token); config.headers.Authorization = `Bearer ${parsedToken}`; } catch (e) { // 如果不是 JSON,直接使用原始值 config.headers.Authorization = `Bearer ${token}`; } } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器(统一处理) axiosInstance.interceptors.response.use( (response: AxiosResponse) => { // transformResponse 已经处理了 JSON 解析 // 这里只做最后的检查 if (typeof response.data === 'string') { const cleanData = response.data.trim(); // 如果是 HTML,拒绝 if (cleanData.startsWith('<') || cleanData.startsWith(' { if (enableDebugLog) { console.error('响应拦截器错误:', error); } return Promise.reject(error); } ); // 封装请求方法 const request = (config: RequestConfig): Promise => { const { url, method = 'GET', data, params, headers = {}, showLoading = false, showError = true, timeout = TIMEOUT, clearAuthOnError = true, } = config; // 显示加载 if (showLoading) { uniShowLoading({ title: '加载中...', mask: true }); } const axiosConfig: AxiosRequestConfig = { url, method, data, params, headers, timeout, }; return axiosInstance .request(axiosConfig) .then((res: AxiosResponse) => { if (showLoading) { uniHideLoading(); } // axios 已经通过响应拦截器处理了数据 // 这里 res.data 应该已经是解析后的对象 if (res.data === null || res.data === undefined) { console.warn('响应数据为空'); return null as T; } const response = res.data as ApiResponse & { status_code?: number; success?: boolean }; // 业务状态码检查 if ( response.code === undefined || response.code === 200 || response.code === 0 || response.success === true ) { return response.data !== undefined ? response.data : (response as unknown as T); } else { // 检查是否是 token / 认证相关错误(无论当前是否有 token,都统一跳转登录) const msg = response.msg || ''; const bizCode = response.code; const statusCode = (response as any).status_code; if ( clearAuthOnError && (bizCode === 401 || bizCode === 10401 || // 业务约定:10401 代表认证失败 bizCode === 10001 || statusCode === 401 || msg.includes('token') || msg.includes('登录') || msg.includes('认证失败')) ) { clearToken(); setTimeout(() => { navigateToLogin(); }, 500); } if (showError) { uniShowToast({ title: response.msg || '请求失败', icon: 'none' }); } const err: any = new Error(response.msg || '请求失败'); err.msg = response.msg || '请求失败'; err.code = response.code; throw err; } }) .catch((error) => { if (showLoading) { uniHideLoading(); } let msg = '请求失败'; // 记录详细错误信息用于调试 console.error('请求错误详情:', { url: config.url, method: config.method, error: error.message, response: error.response?.data, status: error.response?.status, }); if (error.response) { // 服务器返回了错误状态码 const { status, data } = error.response; const dataMessage = typeof data === 'string' ? data : data?.msg || data?.message || data?.detail; switch (status) { case 401: msg = dataMessage || '未授权,请重新登录'; if (clearAuthOnError) { clearToken(); setTimeout(() => { navigateToLogin(); }, 500); } break; case 403: msg = dataMessage || '拒绝访问'; if ( clearAuthOnError && (data?.code === 401 || data?.code === 10401 || data?.msg?.includes('token') || data?.msg?.includes('登录') || data?.msg?.includes('认证失败')) ) { clearToken(); setTimeout(() => { navigateToLogin(); }, 500); } break; case 404: msg = dataMessage || '请求地址不存在'; break; case 500: msg = dataMessage || '服务器内部错误'; break; case 502: msg = '网关错误'; break; case 503: msg = '服务暂时不可用'; break; case 504: msg = '网关超时'; break; default: msg = dataMessage || `请求失败 (${status})`; } } else if (error.request) { // 请求已发出但没有收到响应 msg = '网络错误,请检查网络连接'; } else { // 其他错误(包括 JSON 解析错误) msg = error.message || '请求失败'; // 特殊处理 JSON 解析错误 if (msg.includes('JSON') || msg.includes('parse')) { msg = '服务器响应格式错误'; } } if (showError) { uniShowToast({ title: msg, icon: 'none' }); } const err: any = new Error(msg); err.msg = msg; err.originalError = error; throw err; }); }; // 导出便捷方法 export const http = { get( url: string, params?: object, options?: Partial ): Promise { return request({ url, method: 'GET', params, ...options }); }, post( url: string, data?: object, options?: Partial ): Promise { return request({ url, method: 'POST', data, ...options }); }, put( url: string, data?: object, options?: Partial ): Promise { return request({ url, method: 'PUT', data, ...options }); }, delete( url: string, params?: object, options?: Partial ): Promise { return request({ url, method: 'DELETE', params, ...options }); }, }; export default http;