upload project source code

This commit is contained in:
2026-04-30 18:49:43 +08:00
commit 9b394ba682
2277 changed files with 660945 additions and 0 deletions

View 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;