2025-08-29 18:39:55 +08:00
|
|
|
import { useCallback, useEffect, useMemo } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
AxiosInterceptorManager,
|
|
|
|
|
AxiosRequestConfig,
|
|
|
|
|
AxiosResponse,
|
|
|
|
|
isAxiosError,
|
|
|
|
|
isCancel,
|
|
|
|
|
ValidationError,
|
|
|
|
|
} from '@szyx-mobile/use-axios';
|
|
|
|
|
import {
|
|
|
|
|
RequestError,
|
|
|
|
|
RequestOptions,
|
|
|
|
|
useRequest,
|
|
|
|
|
} from '@szyx-mobile/use-request';
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { Platform } from 'react-native';
|
|
|
|
|
import { useAuth } from '@common/contexts/useAuth.ts';
|
|
|
|
|
import { useCommon } from '@common/contexts/useCommon.ts';
|
|
|
|
|
import { getEnvUrl } from '@common/env/envUtils.ts';
|
|
|
|
|
import { md5_hex } from '@common/utils/md5';
|
|
|
|
|
import { CLIENT_ID, MD5_KEY } from '@common/constants';
|
2025-09-01 09:16:12 +08:00
|
|
|
import { showErrorMessage } from '@common/ToastHelper.ts';
|
2025-08-29 18:39:55 +08:00
|
|
|
|
|
|
|
|
const SUCCESS_STATUS = /^0$/; // 请求成功的状态码
|
|
|
|
|
|
|
|
|
|
type Response<T> = {
|
|
|
|
|
status: string;
|
|
|
|
|
message: string;
|
|
|
|
|
data: T;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const useApi = <
|
|
|
|
|
S extends z.ZodTypeAny,
|
|
|
|
|
T extends z.infer<S> = unknown,
|
|
|
|
|
D = unknown,
|
|
|
|
|
>(
|
|
|
|
|
url: string,
|
|
|
|
|
method: 'GET' | 'POSTJSON' | 'POSTFORM' | 'PUT' | 'UPLOAD',
|
|
|
|
|
params?: D,
|
|
|
|
|
validationSchema?: S,
|
|
|
|
|
options?: RequestOptions,
|
|
|
|
|
config?: AxiosRequestConfig<D>,
|
|
|
|
|
): {
|
|
|
|
|
response?: T;
|
|
|
|
|
error?: RequestError<unknown, unknown>;
|
|
|
|
|
loading: boolean;
|
|
|
|
|
fetch: (config?: AxiosRequestConfig<D>) => void;
|
|
|
|
|
fetchAsync: (config?: AxiosRequestConfig<D>) => Promise<T>;
|
|
|
|
|
cancel: () => void;
|
|
|
|
|
requestInterceptors: AxiosInterceptorManager<AxiosRequestConfig<unknown>>;
|
|
|
|
|
responseInterceptors: AxiosInterceptorManager<
|
|
|
|
|
AxiosResponse<Response<T>, unknown>
|
|
|
|
|
>;
|
|
|
|
|
} => {
|
|
|
|
|
const {
|
|
|
|
|
state: { token, userInfo },
|
|
|
|
|
actions: { logout },
|
|
|
|
|
} = useAuth();
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
state: { info },
|
|
|
|
|
} = useCommon();
|
|
|
|
|
|
|
|
|
|
// 【这里增加公共参数】
|
|
|
|
|
const commonParams = useMemo(() => {
|
|
|
|
|
return {};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// 【这里添加额外的 headers】
|
|
|
|
|
const commonHeaders = useMemo(() => {
|
|
|
|
|
try {
|
|
|
|
|
const deviceInfo = JSON.parse(info.deviceInfo ?? '{}') as Record<
|
|
|
|
|
string,
|
|
|
|
|
string
|
|
|
|
|
>;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
clientId: CLIENT_ID, // 厂商唯一标识,这个值是为医网信app分配的
|
|
|
|
|
deviceType: Platform.select({ android: '0', ios: '1' }),
|
|
|
|
|
version: deviceInfo.versionName,
|
|
|
|
|
deviceId: deviceInfo.deviceId,
|
|
|
|
|
phoneModel: deviceInfo.phoneModel,
|
|
|
|
|
phoneBrand: deviceInfo.phoneBrand,
|
|
|
|
|
phoneVersion: deviceInfo.phoneVersion,
|
|
|
|
|
|
|
|
|
|
userId: userInfo?.userId ?? '',
|
|
|
|
|
sessionId: token ?? '',
|
|
|
|
|
currentClientId: '',
|
|
|
|
|
appType: '0', // 医网信是 0,冠新是 1
|
|
|
|
|
};
|
|
|
|
|
} catch {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
}, [info.deviceInfo, token, userInfo?.userId]);
|
|
|
|
|
|
|
|
|
|
// 【这里定义 Response 的 schema】
|
|
|
|
|
const responseSchema = useMemo(() => {
|
|
|
|
|
// 这里 code 和 message 字段为项目接口中相应的字段名和字段类型
|
|
|
|
|
return z.object({
|
|
|
|
|
status: z.string().regex(SUCCESS_STATUS),
|
|
|
|
|
message: z.string(),
|
|
|
|
|
data: validationSchema ?? z.unknown(),
|
|
|
|
|
});
|
|
|
|
|
}, [validationSchema]);
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
response,
|
|
|
|
|
error,
|
|
|
|
|
loading,
|
|
|
|
|
fetch,
|
|
|
|
|
fetchAsync,
|
|
|
|
|
cancel,
|
|
|
|
|
requestInterceptors,
|
|
|
|
|
responseInterceptors,
|
|
|
|
|
} = useRequest<typeof responseSchema, Response<T>, unknown>(
|
|
|
|
|
url,
|
|
|
|
|
method,
|
|
|
|
|
{ ...params, ...commonParams },
|
|
|
|
|
responseSchema,
|
|
|
|
|
options,
|
|
|
|
|
{
|
|
|
|
|
// 【这里配置 baseURL】
|
|
|
|
|
baseURL: getEnvUrl(info.env ?? 'production').url,
|
|
|
|
|
...config,
|
|
|
|
|
timeout: 30000,
|
|
|
|
|
headers: {
|
|
|
|
|
...commonHeaders,
|
|
|
|
|
...config?.headers,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const requestInterceptor = requestInterceptors.use(c => {
|
|
|
|
|
// 这几个header的参数因为是实时的,所以单独拿出来
|
|
|
|
|
const timeStamp = Date.now();
|
|
|
|
|
const signOrigin = `timeStamp=${timeStamp}#${MD5_KEY}`;
|
|
|
|
|
const sign = md5_hex(signOrigin);
|
|
|
|
|
c.headers = {
|
|
|
|
|
...c.headers,
|
|
|
|
|
timeStamp: timeStamp + '',
|
|
|
|
|
sign: sign,
|
|
|
|
|
};
|
|
|
|
|
return c;
|
|
|
|
|
});
|
|
|
|
|
return () => {
|
|
|
|
|
requestInterceptors.eject(requestInterceptor);
|
|
|
|
|
};
|
|
|
|
|
}, [requestInterceptors]);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const responseInterceptor = responseInterceptors.use(
|
|
|
|
|
r => {
|
|
|
|
|
if (options?.log) {
|
|
|
|
|
console.debug(JSON.stringify(r, null, 2));
|
|
|
|
|
}
|
|
|
|
|
return r;
|
|
|
|
|
},
|
|
|
|
|
e => {
|
|
|
|
|
if (isCancel(e)) {
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError('网络请求已取消', 'Cancel', e),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e instanceof ValidationError) {
|
|
|
|
|
console.error(
|
|
|
|
|
JSON.stringify(
|
|
|
|
|
{
|
|
|
|
|
name: e.name,
|
|
|
|
|
status: e.response.status,
|
|
|
|
|
data: e.response.data,
|
|
|
|
|
config: e.response.config,
|
|
|
|
|
headers: e.response.headers,
|
|
|
|
|
issues: e.issues,
|
|
|
|
|
url: `${e.response.config.baseURL}${e.response.config.url}`,
|
|
|
|
|
},
|
|
|
|
|
null,
|
|
|
|
|
2,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
// 退出登录逻辑
|
2025-09-01 09:16:12 +08:00
|
|
|
if (e.response.data?.status === '017x025'||e.response.data?.status === '017x013') {
|
|
|
|
|
showErrorMessage(
|
2025-08-29 18:39:55 +08:00
|
|
|
e.response.data?.message ??
|
|
|
|
|
'该账号已在其他设备登录,请重新登录!',
|
|
|
|
|
);
|
|
|
|
|
logout();
|
|
|
|
|
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError(
|
|
|
|
|
e.response.data?.message ??
|
|
|
|
|
'该账号已在其他设备登录,请重新登录!',
|
|
|
|
|
'ValidationError',
|
|
|
|
|
e,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.issues.length <= 0) {
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError('数据格式校验错误', 'ValidationError', e),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// 这里 code 为项目接口中相应的字段名
|
|
|
|
|
if (e.issues[0].path.length > 0 && e.issues[0].path[0] === 'status') {
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError(
|
|
|
|
|
e.response.data?.message ?? '', // 这里 message 为项目接口中相应的字段名
|
|
|
|
|
'ValidationError',
|
|
|
|
|
e,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError(e.issues[0].message, 'ValidationError', e),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isAxiosError(e)) {
|
|
|
|
|
console.error(
|
|
|
|
|
JSON.stringify(e, null, 2),
|
|
|
|
|
`${e.config?.baseURL}${e.config?.url}`,
|
|
|
|
|
);
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError('网络请求失败', 'AxiosError', e),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.error(
|
|
|
|
|
JSON.stringify(e, null, 2),
|
|
|
|
|
`${e.config?.baseURL}${e.config?.url}`,
|
|
|
|
|
);
|
|
|
|
|
return Promise.reject(
|
|
|
|
|
new RequestError('网络请求失败', 'OtherError', e),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
return () => {
|
|
|
|
|
responseInterceptors.eject(responseInterceptor);
|
|
|
|
|
};
|
|
|
|
|
}, [logout, options?.log, options?.tag, responseInterceptors]);
|
|
|
|
|
|
|
|
|
|
const transformedFetchAsync = useCallback(
|
|
|
|
|
async (c?: AxiosRequestConfig<D>) => {
|
|
|
|
|
try {
|
|
|
|
|
const r = await fetchAsync(c);
|
|
|
|
|
return r.data.data;
|
|
|
|
|
} catch (e) {
|
|
|
|
|
return Promise.reject(e);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[fetchAsync],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
response: response?.data.data,
|
|
|
|
|
error: error as RequestError<unknown, unknown>,
|
|
|
|
|
loading,
|
|
|
|
|
fetch,
|
|
|
|
|
fetchAsync: transformedFetchAsync,
|
|
|
|
|
cancel,
|
|
|
|
|
requestInterceptors,
|
|
|
|
|
responseInterceptors,
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export { useApi };
|