feat: update environment variables for development and production; add user store and authentication client
- Updated VITE_API_URL in .env.development to point to local server. - Retained VITE_API_URL in .env.production for production use. - Added user store for managing user profile and authentication state. - Created authentication client for handling user login and token management. - Introduced safeClient utility for making API requests with error handling. - Updated various components and views to utilize new user store and authentication logic. - Enhanced UI styles for better visual consistency across the application.
This commit is contained in:
124
src/api/index.ts
Normal file
124
src/api/index.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import type { App } from "@capp/eden";
|
||||
import type { WatchSource } from "vue";
|
||||
import { treaty } from "@elysiajs/eden";
|
||||
import { toastController } from "@ionic/vue";
|
||||
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 && res.status === 418) {
|
||||
if (!options.silent) {
|
||||
const toast = await toastController.create({
|
||||
message: i18n.global.t((res.error as any).value.code, {
|
||||
...(res.error as any).value.context,
|
||||
}),
|
||||
duration: 3000,
|
||||
position: "bottom",
|
||||
color: "danger",
|
||||
});
|
||||
await toast.present();
|
||||
}
|
||||
|
||||
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>>;
|
||||
}
|
||||
12
src/api/types.ts
Normal file
12
src/api/types.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { Treaty } from "@elysiajs/eden";
|
||||
import type { client } from ".";
|
||||
|
||||
export type TreatyQuery<T> = T extends (...args: any[]) => any
|
||||
? NonNullable<NonNullable<Parameters<T>[0]>["query"]>
|
||||
: never;
|
||||
|
||||
export type TreatyBody<T> = T extends (...args: any[]) => any
|
||||
? NonNullable<Parameters<T>[0]>
|
||||
: never;
|
||||
|
||||
export type UserProfileData = Treaty.Data<typeof client.api.user.profile.get>;
|
||||
Reference in New Issue
Block a user