CNCEC_APP/uni_modules/uview-pro/libs/request/index.ts

302 lines
10 KiB
TypeScript
Raw Permalink Normal View History

2026-03-25 14:54:15 +08:00
import deepMerge from '../function/deepMerge';
/**
* Meta类型定义
*/
export interface RequestMeta {
toast?: boolean;
loading?: boolean;
originalData?: boolean;
[key: string]: any;
}
/**
*
*/
export interface RequestConfig {
baseUrl?: string;
header?: Record<string, any>;
method?: string;
dataType?: string;
responseType?: string;
timeout?: number;
meta?: RequestMeta;
[key: string]: any;
}
/**
*
*/
const IGNORE_REQUEST_KEYS = ['baseUrl', 'meta'];
/**
*
*/
export interface RequestInterceptor {
request?: ((options: RequestOptions) => RequestOptions | false) | null;
response?: ((response: any) => any | false) | null;
}
/**
*
*/
export interface RequestOptions {
url: string;
header: Record<string, any>;
method: 'GET' | 'POST' | 'OPTIONS' | 'HEAD' | 'PUT' | 'DELETE' | 'TRACE' | 'CONNECT';
data?: any;
dataType?: string;
responseType?: string;
params?: Record<string, any>;
complete?: (response: any) => void;
meta?: RequestMeta;
[key: string]: any;
}
export class Request {
public config: RequestConfig;
public interceptor: RequestInterceptor;
public options?: RequestOptions;
constructor() {
this.config = {
baseUrl: '', // 请求的根域名
header: {}, // 默认的请求头
method: 'POST', // 请求方式
dataType: 'json', // 设置为json返回后uni.request会对数据进行一次JSON.parse
responseType: 'text', // 此参数无需处理因为5+和支付宝小程序不支持默认为text即可
timeout: 60000,
meta: {
originalData: true, // 是否在拦截器中返回服务端的原始数据,见文档说明
toast: false, // 是否在请求出错时弹出toast
loading: false // 是否显示加载中
}
};
this.interceptor = {
request: null,
response: null
};
}
/**
* options
* - IGNORE_REQUEST_KEYS meta
* - header 使 header options.header
* - options
* - baseUrl baseUrl options.url url http URL
*/
private mergeGlobalConfigToOptions(options: RequestOptions): RequestOptions {
const mergedOptions: RequestOptions = { ...options };
for (const key of Object.keys(this.config)) {
if (IGNORE_REQUEST_KEYS.includes(key)) {
continue;
}
const cfgVal = this.config[key];
const optVal = options[key];
// 跳过未设置的全局配置
if (cfgVal === undefined) continue;
// header 需要做深合并,且以 options.header 为准覆盖同名属性
if (key === 'header') {
mergedOptions.header = deepMerge(cfgVal || {}, optVal || {});
continue;
}
// 针对 method 等枚举字符串,优先使用 options 中的值,否则使用全局配置
if (typeof cfgVal === 'string' || typeof cfgVal === 'number' || typeof cfgVal === 'boolean') {
mergedOptions[key] = optVal !== undefined ? optVal : cfgVal;
continue;
}
// 对对象类型的配置(如自定义扩展)尝试做深合并
if (typeof cfgVal === 'object' && !Array.isArray(cfgVal)) {
mergedOptions[key] = deepMerge(cfgVal || {}, optVal || {});
continue;
}
// 其他类型,若 options 未传入则使用全局配置
if (optVal === undefined) {
mergedOptions[key] = cfgVal;
}
}
// 如果存在 baseUrl并且 options.url 为相对地址,则拼接成完整 url
const baseUrl = this.config.baseUrl;
if (
baseUrl &&
mergedOptions.url &&
typeof mergedOptions.url === 'string' &&
mergedOptions.url.indexOf('http') !== 0
) {
mergedOptions.url =
baseUrl + (mergedOptions.url.indexOf('/') === 0 ? mergedOptions.url : `/${mergedOptions.url}`);
}
// 确保 url 存在,且为 string
if (!mergedOptions.url) {
mergedOptions.url = '';
}
return mergedOptions;
}
/**
*
* @param customConfig
*/
setConfig(customConfig: Partial<RequestConfig>): void {
this.config = deepMerge(this.config, customConfig);
}
/**
*
* @param options
*/
request<T = unknown>(options: RequestOptions): Promise<T> {
// 合并 meta 配置,优先级:单次请求 > 全局
const mergedMeta: RequestMeta = {
...this.config.meta,
...(options.meta || {})
};
// 让 options.meta 传递到拦截器
options.meta = mergedMeta;
options.url = options.url || '';
options.params = options.params || {};
// 将全局配置合并到本次请求 options 中(注意忽略一些特殊字段如 baseUrl/meta
options = this.mergeGlobalConfigToOptions(options);
if (this.interceptor.request && typeof this.interceptor.request === 'function') {
const interceptorRequest = this.interceptor.request(options);
if (!interceptorRequest) {
// 返回一个处于pending状态中的Promise来取消原promise避免进入then()回调
return new Promise(() => {});
}
this.options = interceptorRequest;
}
return new Promise<T>((resolve, reject) => {
options.complete = (response: any) => {
// 读取 meta 配置
const meta = options.meta || this.config.meta || {};
const originalData = meta.originalData ?? false;
// 拦截器处理加入request的配置参数
response.config = options;
if (originalData) {
// 判断是否存在拦截器
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
const resInterceptors = this.interceptor.response(response);
// 如果拦截器不返回false就将拦截器返回的内容给请求的then回调
if (resInterceptors !== false) {
resolve(resInterceptors);
} else {
// 如果拦截器返回false意味着拦截器定义者认为返回有问题直接接入catch回调
reject(response);
}
} else {
// 如果要求返回原始数据,就算没有拦截器,也返回最原始的数据
resolve(response);
}
} else {
if (response.statusCode === 200) {
if (this.interceptor.response && typeof this.interceptor.response === 'function') {
const resInterceptors = this.interceptor.response(response.data);
if (resInterceptors !== false) {
resolve(resInterceptors);
} else {
reject(response.data);
}
} else {
// 如果不是返回原始数据(originalData=false)且没有拦截器的情况下返回纯数据给then回调
resolve(response.data);
}
} else {
reject(response);
}
}
};
uni.request(options);
});
}
get<T = unknown>(
url: string,
data: any = {},
options: { header?: Record<string, any>; meta?: RequestMeta } = {}
): Promise<T> {
return this.request<T>({
method: 'GET',
url,
data,
header: options.header || {},
meta: options.meta
});
}
post<T = unknown>(
url: string,
data: any = {},
options: { header?: Record<string, any>; meta?: RequestMeta } = {}
): Promise<T> {
return this.request<T>({
url,
method: 'POST',
data,
header: options.header || {},
meta: options.meta
});
}
put<T = unknown>(
url: string,
data: any = {},
options: { header?: Record<string, any>; meta?: RequestMeta } = {}
): Promise<T> {
return this.request<T>({
url,
method: 'PUT',
data,
header: options.header || {},
meta: options.meta
});
}
delete<T = unknown>(
url: string,
data: any = {},
options: { header?: Record<string, any>; meta?: RequestMeta } = {}
): Promise<T> {
return this.request<T>({
url,
method: 'DELETE',
data,
header: options.header || {},
meta: options.meta
});
}
}
// 插件化导出,支持 app.use(http, { interceptor })
const httpInstance = new Request();
interface HttpPluginOptions {
requestConfig?: Partial<RequestConfig>;
interceptor?: RequestInterceptor;
}
// 全局导出,支持 import { httpPlugin } from 'uview-pro'
const httpPlugin = {
install(app: any, options: HttpPluginOptions = {}) {
if (options.interceptor) {
const { request, response } = options.interceptor;
if (request) httpInstance.interceptor.request = request;
if (response) httpInstance.interceptor.response = response;
}
if (options.requestConfig) {
httpInstance.setConfig(options.requestConfig);
}
app.config.globalProperties.$http = httpInstance;
}
};
// 全局导出,支持 import { http } from 'uview-pro'
export { httpInstance as http };
// 插件化导出,支持 app.use(http, { interceptor })
export default httpPlugin;