2024-01-05 12:09:12 +08:00
|
|
|
|
import router from '@/router';
|
2024-05-13 19:55:43 +08:00
|
|
|
|
import { clearUser, getClientId, getRefreshToken, getToken, saveRefreshToken, saveToken } from '@/common/utils/storage';
|
2024-01-05 12:09:12 +08:00
|
|
|
|
import { templateResolve } from '@/common/utils/string';
|
2023-12-11 01:00:09 +08:00
|
|
|
|
import { ElMessage } from 'element-plus';
|
2025-04-26 17:37:09 +08:00
|
|
|
|
import { createFetch, UseFetchReturn } from '@vueuse/core';
|
2024-01-05 12:09:12 +08:00
|
|
|
|
import Api from '@/common/Api';
|
|
|
|
|
|
import { Result, ResultEnum } from '@/common/request';
|
|
|
|
|
|
import config from '@/common/config';
|
2025-04-26 17:37:09 +08:00
|
|
|
|
import { ref, unref } from 'vue';
|
2024-01-05 12:09:12 +08:00
|
|
|
|
import { URL_401 } from '@/router/staticRouter';
|
2024-05-13 19:55:43 +08:00
|
|
|
|
import openApi from '@/common/openApi';
|
2024-11-20 22:43:53 +08:00
|
|
|
|
import { useThemeConfig } from '@/store/themeConfig';
|
2023-12-11 01:00:09 +08:00
|
|
|
|
|
|
|
|
|
|
const baseUrl: string = config.baseApiUrl;
|
|
|
|
|
|
|
|
|
|
|
|
const useCustomFetch = createFetch({
|
|
|
|
|
|
baseUrl: baseUrl,
|
|
|
|
|
|
combination: 'chain',
|
|
|
|
|
|
options: {
|
|
|
|
|
|
immediate: false,
|
2024-03-07 17:26:11 +08:00
|
|
|
|
timeout: 600000,
|
2023-12-11 01:00:09 +08:00
|
|
|
|
// beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
|
|
|
|
|
|
async beforeFetch({ options }) {
|
|
|
|
|
|
const token = getToken();
|
|
|
|
|
|
|
|
|
|
|
|
const headers = new Headers(options.headers || {});
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
headers.set('Authorization', token);
|
|
|
|
|
|
headers.set('ClientId', getClientId());
|
|
|
|
|
|
}
|
2024-11-20 22:43:53 +08:00
|
|
|
|
|
|
|
|
|
|
const themeConfig = useThemeConfig().themeConfig;
|
|
|
|
|
|
|
2023-12-26 22:31:51 +08:00
|
|
|
|
headers.set('Content-Type', 'application/json');
|
2024-11-20 22:43:53 +08:00
|
|
|
|
headers.set('Accept-Language', themeConfig?.globalI18n);
|
2023-12-11 01:00:09 +08:00
|
|
|
|
options.headers = headers;
|
|
|
|
|
|
|
|
|
|
|
|
return { options };
|
|
|
|
|
|
},
|
|
|
|
|
|
async afterFetch(ctx) {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
ctx.data = await ctx.response.json();
|
2023-12-11 01:00:09 +08:00
|
|
|
|
return ctx;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
interface EsReq {
|
|
|
|
|
|
esProxyReq: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface RequestOptions extends RequestInit, EsReq {}
|
|
|
|
|
|
|
|
|
|
|
|
export function useApiFetch<T>(api: Api, params: any = null, reqOptions?: RequestOptions) {
|
2023-12-11 01:00:09 +08:00
|
|
|
|
const uaf = useCustomFetch<T>(api.url, {
|
2024-08-22 00:43:39 +00:00
|
|
|
|
async beforeFetch({ url, options }) {
|
2023-12-11 01:00:09 +08:00
|
|
|
|
options.method = api.method;
|
|
|
|
|
|
if (!params) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let paramsValue = unref(params);
|
|
|
|
|
|
|
|
|
|
|
|
let apiUrl = url;
|
|
|
|
|
|
// 简单判断该url是否是restful风格
|
|
|
|
|
|
if (apiUrl.indexOf('{') != -1) {
|
|
|
|
|
|
apiUrl = templateResolve(apiUrl, paramsValue);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-03-02 19:08:19 +08:00
|
|
|
|
if (api.beforeHandler) {
|
2024-08-22 00:43:39 +00:00
|
|
|
|
paramsValue = await api.beforeHandler(paramsValue);
|
2024-03-02 19:08:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-12-11 01:00:09 +08:00
|
|
|
|
if (paramsValue) {
|
|
|
|
|
|
const method = options.method?.toLowerCase();
|
|
|
|
|
|
// post和put使用json格式传参
|
|
|
|
|
|
if (method === 'post' || method === 'put') {
|
|
|
|
|
|
options.body = JSON.stringify(paramsValue);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const searchParam = new URLSearchParams();
|
|
|
|
|
|
Object.keys(paramsValue).forEach((key) => {
|
|
|
|
|
|
const val = paramsValue[key];
|
|
|
|
|
|
if (val) {
|
|
|
|
|
|
searchParam.append(key, val);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
apiUrl = `${apiUrl}?${searchParam.toString()}`;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
url: apiUrl,
|
|
|
|
|
|
options: {
|
|
|
|
|
|
...options,
|
|
|
|
|
|
...reqOptions,
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
},
|
2025-05-21 04:42:30 +00:00
|
|
|
|
onFetchError: (ctx) => {
|
|
|
|
|
|
if (reqOptions?.esProxyReq) {
|
|
|
|
|
|
uaf.data = { value: JSON.parse(ctx.data) };
|
|
|
|
|
|
return Promise.resolve(uaf.data);
|
|
|
|
|
|
}
|
|
|
|
|
|
return ctx;
|
|
|
|
|
|
},
|
|
|
|
|
|
}) as any;
|
2023-12-11 01:00:09 +08:00
|
|
|
|
|
2025-04-26 17:37:09 +08:00
|
|
|
|
// 统一处理后的返回结果,如果直接使用uaf.data,则数据会出现由{code: x, data: {}} -> data 的变化导致某些结果绑定报错
|
|
|
|
|
|
const data = ref<T | null>(null);
|
2023-12-11 01:00:09 +08:00
|
|
|
|
return {
|
|
|
|
|
|
execute: async function () {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
await execCustomFetch(uaf, reqOptions);
|
2025-04-26 17:37:09 +08:00
|
|
|
|
data.value = uaf.data.value;
|
2023-12-11 01:00:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
isFetching: uaf.isFetching,
|
2025-04-26 17:37:09 +08:00
|
|
|
|
data: data,
|
2023-12-11 01:00:09 +08:00
|
|
|
|
abort: uaf.abort,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
let refreshingToken = false;
|
|
|
|
|
|
let queue: any[] = [];
|
|
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
async function execCustomFetch(uaf: UseFetchReturn<any>, reqOptions?: RequestOptions) {
|
2024-05-13 19:55:43 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await uaf.execute(true);
|
|
|
|
|
|
} catch (e: any) {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
if (!reqOptions?.esProxyReq) {
|
|
|
|
|
|
const rejectPromise = Promise.reject(e);
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
if (e?.name == 'AbortError') {
|
|
|
|
|
|
console.log('请求已取消');
|
|
|
|
|
|
return rejectPromise;
|
|
|
|
|
|
}
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
const respStatus = uaf.response.value?.status;
|
|
|
|
|
|
if (respStatus == 404) {
|
|
|
|
|
|
ElMessage.error('url not found');
|
|
|
|
|
|
return rejectPromise;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (respStatus == 500) {
|
|
|
|
|
|
ElMessage.error('server error');
|
|
|
|
|
|
return rejectPromise;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.error(e);
|
|
|
|
|
|
ElMessage.error('network error');
|
2024-05-13 19:55:43 +08:00
|
|
|
|
return rejectPromise;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
const result: Result & { error: any; status: number } = uaf.data.value as any;
|
2024-05-13 19:55:43 +08:00
|
|
|
|
if (!result) {
|
2025-04-26 17:37:09 +08:00
|
|
|
|
ElMessage.error('network request failed');
|
2024-05-13 19:55:43 +08:00
|
|
|
|
return Promise.reject(result);
|
|
|
|
|
|
}
|
2025-05-21 04:42:30 +00:00
|
|
|
|
// es代理请求
|
|
|
|
|
|
if (reqOptions?.esProxyReq) {
|
|
|
|
|
|
uaf.data.value = result;
|
|
|
|
|
|
return Promise.resolve(result);
|
|
|
|
|
|
}
|
2024-05-13 19:55:43 +08:00
|
|
|
|
|
|
|
|
|
|
const resultCode = result.code;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果返回为成功结果,则将结果的data赋值给响应式data
|
|
|
|
|
|
if (resultCode === ResultEnum.SUCCESS) {
|
|
|
|
|
|
uaf.data.value = result.data;
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果是accessToken失效,则使用refreshToken刷新token
|
|
|
|
|
|
if (resultCode == ResultEnum.ACCESS_TOKEN_INVALID) {
|
|
|
|
|
|
if (refreshingToken) {
|
|
|
|
|
|
// 请求加入队列等待, 防止并发多次请求refreshToken
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
|
queue.push(() => {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
resolve(execCustomFetch(uaf, reqOptions));
|
2024-05-13 19:55:43 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
refreshingToken = true;
|
|
|
|
|
|
const res = await openApi.refreshToken({ refresh_token: getRefreshToken() });
|
|
|
|
|
|
saveToken(res.token);
|
|
|
|
|
|
saveRefreshToken(res.refresh_token);
|
|
|
|
|
|
// 重新缓存后端用户权限code
|
|
|
|
|
|
await openApi.getPermissions();
|
|
|
|
|
|
|
|
|
|
|
|
// 执行accessToken失效的请求
|
|
|
|
|
|
queue.forEach((resolve: any) => {
|
|
|
|
|
|
resolve();
|
|
|
|
|
|
});
|
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
|
clearUser();
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
refreshingToken = false;
|
|
|
|
|
|
queue = [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-21 04:42:30 +00:00
|
|
|
|
await execCustomFetch(uaf, reqOptions);
|
2024-05-13 19:55:43 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果提示没有权限,则跳转至无权限页面
|
|
|
|
|
|
if (resultCode === ResultEnum.NO_PERMISSION) {
|
2025-05-21 04:42:30 +00:00
|
|
|
|
await router.push({
|
2024-05-13 19:55:43 +08:00
|
|
|
|
path: URL_401,
|
|
|
|
|
|
});
|
|
|
|
|
|
return Promise.reject(result);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果返回的code不为成功,则会返回对应的错误msg,则直接统一通知即可。忽略登录超时或没有权限的提示(直接跳转至401页面)
|
|
|
|
|
|
if (result.msg && resultCode != ResultEnum.NO_PERMISSION) {
|
|
|
|
|
|
ElMessage.error(result.msg);
|
|
|
|
|
|
uaf.error.value = new Error(result.msg);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return Promise.reject(result);
|
|
|
|
|
|
}
|