152 lines
4.3 KiB
TypeScript
152 lines
4.3 KiB
TypeScript
import { createAlova, Method, StatesHook } from "alova";
|
||
import fetchAdapter from "alova/fetch";
|
||
import VueHook from "alova/vue";
|
||
import { replaceOriginUrl } from "../bschSsoRedirect";
|
||
import config from "@/assets/config/host.config";
|
||
import Utils from "..";
|
||
/* ============= 类型定义 ============= */
|
||
export interface RequestHooks {
|
||
/** 前置票据/通用头注入 */
|
||
injectHeaders?: (method: Method) => void;
|
||
/** 加载开始 */
|
||
onLoadingStart?: () => void;
|
||
/** 加载结束 */
|
||
onLoadingEnd?: () => void;
|
||
/** 业务错误提示(code !== 0)*/
|
||
onBizError?: (msg: string) => void;
|
||
/** http 错误提示(status !== 200)*/
|
||
onHttpError?: (msg: string) => void;
|
||
}
|
||
|
||
/** 和后端约定的通用响应格式 */
|
||
export interface BschResponse<T = any> {
|
||
code: number;
|
||
msg?: string;
|
||
data: T;
|
||
urlToRedirectTo?: string;
|
||
}
|
||
|
||
/* ============= 默认空实现 ============= */
|
||
const defHooks: Required<RequestHooks> = {
|
||
injectHeaders: () => {},
|
||
onLoadingStart: () => {},
|
||
onLoadingEnd: () => {},
|
||
onBizError: () => {},
|
||
onHttpError: () => {},
|
||
};
|
||
|
||
/* ============= 错误处理(原 axios 逻辑平移) ============= */
|
||
function errorHandle<T>(res: BschResponse<T>, status: number): T {
|
||
// http 层错误
|
||
if (status !== 200) {
|
||
let msg = "网络错误";
|
||
switch (status) {
|
||
case 401:
|
||
case 403:
|
||
break;
|
||
case 404:
|
||
msg = "请求的资源不存在!";
|
||
break;
|
||
default:
|
||
msg = res?.msg || "内部服务错误!";
|
||
}
|
||
throw new Error(msg);
|
||
}
|
||
|
||
// 业务层错误
|
||
switch (res.code) {
|
||
case 0:
|
||
return res.data ?? (null as T);
|
||
case 302:
|
||
console.log(res);
|
||
window.location.href = res.urlToRedirectTo!;
|
||
// window.location.href = replaceOriginUrl(res.urlToRedirectTo!, config.serviceHost);
|
||
throw new Error("redirect");
|
||
case 4:
|
||
throw new Error(res.msg || "参数错误!");
|
||
default:
|
||
throw new Error(res.msg || "业务错误");
|
||
}
|
||
}
|
||
|
||
/* ============= 工厂函数 ============= */
|
||
export function createBschAlova(baseURL: string, hooks: RequestHooks = {}, statesHook: StatesHook<any> = VueHook) {
|
||
const { injectHeaders, onLoadingStart, onLoadingEnd, onBizError, onHttpError } = {
|
||
...defHooks,
|
||
...hooks,
|
||
};
|
||
|
||
let reqCount = 0;
|
||
let timer: ReturnType<typeof setTimeout>;
|
||
|
||
/* ---- 请求拦截 ---- */
|
||
function beforeRequest(method: Method) {
|
||
reqCount++;
|
||
if (reqCount === 1) timer = setTimeout(() => onLoadingStart(), 800);
|
||
|
||
Promise.resolve().then(() => {
|
||
if (reqCount === 0) {
|
||
clearTimeout(timer);
|
||
onLoadingEnd();
|
||
}
|
||
});
|
||
// 1. 用户注入票据/角色等头
|
||
injectHeaders(method);
|
||
|
||
method.config.credentials = "include";
|
||
// 2. 通用头
|
||
method.config.headers = Object.assign(method.config.headers || {}, {
|
||
yschapirequest: "1",
|
||
timeout: 20000,
|
||
yschreferer: Utils.strToHexCharCode(window.location.href),
|
||
});
|
||
}
|
||
|
||
/* ---- 响应拦截 ---- */
|
||
async function onSuccess(response: Response) {
|
||
reqCount--;
|
||
if (reqCount === 0) {
|
||
clearTimeout(timer);
|
||
onLoadingEnd();
|
||
}
|
||
const res: BschResponse = await response.json();
|
||
try {
|
||
return errorHandle(res, response.status);
|
||
} catch (e: any) {
|
||
console.log(e);
|
||
|
||
onBizError(e.message);
|
||
return Promise.reject(e);
|
||
}
|
||
}
|
||
|
||
async function onError(err: any) {
|
||
reqCount = 0;
|
||
clearTimeout(timer);
|
||
onLoadingEnd();
|
||
|
||
const msg = err?.message || "网络请求出错";
|
||
onHttpError(msg);
|
||
return Promise.reject(err);
|
||
}
|
||
|
||
async function onComplete(method: Method) {
|
||
if (method.fromCache) {
|
||
reqCount--;
|
||
if (reqCount === 0) {
|
||
clearTimeout(timer);
|
||
onLoadingEnd();
|
||
}
|
||
}
|
||
}
|
||
|
||
return createAlova({
|
||
statesHook,
|
||
requestAdapter: fetchAdapter(),
|
||
baseURL,
|
||
cacheFor: null,
|
||
beforeRequest,
|
||
responded: { onSuccess, onError, onComplete },
|
||
});
|
||
}
|