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:
2026-01-17 17:23:38 +07:00
parent 1239935b57
commit 7ec2522fa0
22 changed files with 353 additions and 68 deletions

124
src/api/index.ts Normal file
View 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>>;
}