Files
----/前端源码/uni-app/utils/request.ts

353 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<T = any> {
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('<!DOCTYPE')) {
throw new Error('服务器返回了 HTML 页面');
}
// 尝试解析 JSON
if (cleanData.startsWith('{') || cleanData.startsWith('[')) {
return JSON.parse(cleanData);
}
return cleanData;
} catch (e) {
console.error('transformResponse JSON 解析失败:', e);
// 返回原始数据,让后续处理
return data;
}
}
return data;
}],
});
// 请求拦截器
axiosInstance.interceptors.request.use(
(config) => {
// 获取 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('<!DOCTYPE')) {
console.error('收到 HTML 响应:', cleanData.substring(0, 200));
return Promise.reject(new Error('服务器返回了 HTML 页面'));
}
// 如果是 JSON 字符串但未被解析,尝试解析
if (cleanData.startsWith('{') || cleanData.startsWith('[')) {
try {
response.data = JSON.parse(cleanData);
} catch (e) {
console.error('响应拦截器 JSON 解析失败:', e);
return Promise.reject(new Error('服务器返回了无效的 JSON 格式'));
}
}
}
return response;
},
(error) => {
if (enableDebugLog) {
console.error('响应拦截器错误:', error);
}
return Promise.reject(error);
}
);
// 封装请求方法
const request = <T = any>(config: RequestConfig): Promise<T> => {
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<T> & { 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<T = any>(
url: string,
params?: object,
options?: Partial<RequestConfig>
): Promise<T> {
return request<T>({ url, method: 'GET', params, ...options });
},
post<T = any>(
url: string,
data?: object,
options?: Partial<RequestConfig>
): Promise<T> {
return request<T>({ url, method: 'POST', data, ...options });
},
put<T = any>(
url: string,
data?: object,
options?: Partial<RequestConfig>
): Promise<T> {
return request<T>({ url, method: 'PUT', data, ...options });
},
delete<T = any>(
url: string,
params?: object,
options?: Partial<RequestConfig>
): Promise<T> {
return request<T>({ url, method: 'DELETE', params, ...options });
},
};
export default http;