upload project source code
This commit is contained in:
352
前端源码/uni-app/utils/request.ts
Normal file
352
前端源码/uni-app/utils/request.ts
Normal file
@@ -0,0 +1,352 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user