128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
import type { App } from "@capp/eden";
|
||
import type { WatchSource } from "vue";
|
||
import { treaty } from "@elysiajs/eden";
|
||
import { i18n } from "@/locales";
|
||
|
||
const baseURL = import.meta.env.DEV ? window.location.origin : import.meta.env.VITE_API_URL;
|
||
|
||
export const client = treaty<App>(baseURL, {
|
||
fetch: {
|
||
credentials: "include",
|
||
},
|
||
headers() {
|
||
const token = localStorage.getItem("user-token") || "";
|
||
const headers = new Headers();
|
||
headers.append("Content-Type", "application/json");
|
||
headers.append("Authorization", token ? `Bearer ${token}` : "");
|
||
return headers;
|
||
},
|
||
});
|
||
|
||
export interface SafeClientOptions {
|
||
silent?: boolean;
|
||
immediate?: boolean;
|
||
watchSource?: WatchSource; // 用于监听的响应式数据源
|
||
}
|
||
|
||
export interface SafeClientReturn<T, E> {
|
||
data: Ref<T | null>;
|
||
error: Ref<E | null>;
|
||
isPending: Ref<boolean>;
|
||
execute: () => Promise<void>;
|
||
onFetchResponse: (callback: (data: T, error: E) => void) => void;
|
||
stopWatching?: () => void;
|
||
}
|
||
|
||
export type RequestPromise<T, E> = Promise<{ data: T; error: E; status?: number; response?: Response }>;
|
||
|
||
export function safeClient<T, E>(
|
||
requestPromise: (() => RequestPromise<T, E>) | RequestPromise<T, E>,
|
||
options: SafeClientOptions = {},
|
||
): SafeClientReturn<T, E> & Promise<SafeClientReturn<T, E>> {
|
||
const { immediate = true, watchSource } = options;
|
||
const data = ref<T | null>(null);
|
||
const error = ref<E | null>(null);
|
||
const isPending = ref(false);
|
||
let responseCallback: ((data: T, error: E) => void) | null = null;
|
||
let stopWatcher: (() => void) | undefined;
|
||
|
||
const execute = async () => {
|
||
isPending.value = true;
|
||
let request: () => RequestPromise<T, E>;
|
||
if (typeof requestPromise !== "function") {
|
||
request = () => Promise.resolve(requestPromise);
|
||
}
|
||
else {
|
||
request = requestPromise;
|
||
}
|
||
|
||
const res = await request().finally(() => {
|
||
isPending.value = false;
|
||
});
|
||
|
||
if (res.error) {
|
||
if (!options.silent && res.status === 418) {
|
||
showToast(i18n.global.t((res.error as any).value.code, {
|
||
...(res.error as any).value.context,
|
||
}));
|
||
}
|
||
else if (res.status === 401) {
|
||
setTimeout(() => {
|
||
showToast("登录状态已过期,2秒后将跳转到登录页面,请重新登录!");
|
||
localStorage.removeItem("user-token");
|
||
window.location.reload();
|
||
}, 2000);
|
||
}
|
||
else if (!options.silent) {
|
||
showToast((res.error as any).message || i18n.global.t("network_error"));
|
||
}
|
||
|
||
throw res.error;
|
||
}
|
||
data.value = res.data;
|
||
error.value = res.error;
|
||
|
||
// 调用注册的回调函数
|
||
if (responseCallback) {
|
||
responseCallback(res.data, res.error);
|
||
}
|
||
};
|
||
|
||
function onFetchResponse(callback: (data: T, error: E) => void) {
|
||
responseCallback = callback;
|
||
}
|
||
|
||
function stopWatching() {
|
||
if (stopWatcher) {
|
||
stopWatcher();
|
||
stopWatcher = undefined;
|
||
}
|
||
}
|
||
|
||
// 如果提供了 watchSource,则监听其变化
|
||
if (watchSource) {
|
||
stopWatcher = watch(
|
||
watchSource,
|
||
() => {
|
||
execute();
|
||
},
|
||
{ immediate: false }, // 不立即执行,避免与 immediate 选项冲突
|
||
);
|
||
}
|
||
|
||
const result: SafeClientReturn<T, E> = {
|
||
data: data as Ref<T | null>,
|
||
error: error as Ref<E | null>,
|
||
isPending,
|
||
execute,
|
||
onFetchResponse,
|
||
stopWatching,
|
||
};
|
||
|
||
const promise = immediate ? execute().then(() => result) : Promise.resolve(result);
|
||
|
||
Object.assign(promise, result);
|
||
|
||
return promise as SafeClientReturn<T, E> & Promise<SafeClientReturn<T, E>>;
|
||
}
|