167 lines
4.5 KiB
TypeScript
167 lines
4.5 KiB
TypeScript
// app/stores/auth.ts - 用户登录状态管理
|
|
import { defineStore } from "pinia";
|
|
|
|
import { authClient } from "@/utils/auth";
|
|
|
|
/** 当前登录用户信息,apiToken 用于 OCS 油猴脚本配置 */
|
|
interface AuthUser {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
/** 油猴脚本使用此 token 在 body 中传递身份 */
|
|
apiToken?: string | null;
|
|
}
|
|
|
|
export const useAuthStore = defineStore("auth", () => {
|
|
const user = ref<AuthUser | null>(null);
|
|
const loading = ref(false);
|
|
const error = ref("");
|
|
/** session 是否已完成首次加载,用于区分"未登录"和"尚未检查" */
|
|
const initialized = ref(false);
|
|
/** 已登录且 session 已初始化完成 */
|
|
const isOnline = computed(() => initialized.value && user.value !== null);
|
|
|
|
// 用于对并发调用去重,避免同一时刻发起多次 get-session 请求
|
|
let _fetchPromise: Promise<void> | null = null;
|
|
|
|
/** 从服务端拉取当前 session;已在请求中时复用同一 Promise,已初始化时直接跳过 */
|
|
const fetchSession = async () => {
|
|
if (initialized.value) return;
|
|
if (_fetchPromise) return _fetchPromise;
|
|
|
|
_fetchPromise = (async () => {
|
|
loading.value = true;
|
|
error.value = "";
|
|
try {
|
|
const session = await authClient.getSession();
|
|
user.value = session.data?.user
|
|
? {
|
|
id: session.data.user.id,
|
|
name: session.data.user.name,
|
|
email: session.data.user.email,
|
|
apiToken: (session.data.user as { apiToken?: string | null })
|
|
.apiToken
|
|
}
|
|
: null;
|
|
} catch {
|
|
user.value = null;
|
|
} finally {
|
|
loading.value = false;
|
|
initialized.value = true;
|
|
_fetchPromise = null;
|
|
}
|
|
})();
|
|
|
|
return _fetchPromise;
|
|
};
|
|
|
|
/** 从 better-auth 返回的 user 对象中提取并写入 store */
|
|
const _setUserFromResult = (u: {
|
|
id: string;
|
|
name: string;
|
|
email: string;
|
|
apiToken?: string | null;
|
|
}) => {
|
|
user.value = {
|
|
id: u.id,
|
|
name: u.name,
|
|
email: u.email,
|
|
apiToken: u.apiToken ?? null
|
|
};
|
|
initialized.value = true;
|
|
};
|
|
|
|
/** 邮箱密码登录 */
|
|
const signIn = async (email: string, password: string) => {
|
|
loading.value = true;
|
|
error.value = "";
|
|
try {
|
|
const result = await authClient.signIn.email({ email, password });
|
|
if (result.error) {
|
|
error.value = "邮箱或密码错误";
|
|
return false;
|
|
}
|
|
// 直接使用登录响应中的用户数据,无需重复请求 get-session
|
|
if (result.data?.user) {
|
|
_setUserFromResult(
|
|
result.data.user as Parameters<typeof _setUserFromResult>[0]
|
|
);
|
|
}
|
|
return true;
|
|
} catch {
|
|
error.value = "登录失败,请重试";
|
|
return false;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
/** 注册新账号 */
|
|
const signUp = async (name: string, email: string, password: string) => {
|
|
loading.value = true;
|
|
error.value = "";
|
|
try {
|
|
const result = await authClient.signUp.email({ name, email, password });
|
|
if (result.error) {
|
|
error.value = "注册失败,邮箱可能已被使用";
|
|
return false;
|
|
}
|
|
if (result.data?.user) {
|
|
_setUserFromResult(
|
|
result.data.user as Parameters<typeof _setUserFromResult>[0]
|
|
);
|
|
}
|
|
return true;
|
|
} catch {
|
|
error.value = "注册失败,请重试";
|
|
return false;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
/** 退出登录 */
|
|
const signOut = async () => {
|
|
await authClient.signOut();
|
|
user.value = null;
|
|
initialized.value = false;
|
|
};
|
|
|
|
/** 刷新当前用户的 apiToken;成功后同步更新 store */
|
|
const refreshApiToken = async (): Promise<boolean> => {
|
|
loading.value = true;
|
|
error.value = "";
|
|
try {
|
|
const res = await $fetch<{
|
|
code: number;
|
|
msg: string;
|
|
data: { apiToken: string } | null;
|
|
}>("/api/user/refresh-token", { method: "POST" });
|
|
if (res.code === 0 && res.data?.apiToken && user.value) {
|
|
user.value = { ...user.value, apiToken: res.data.apiToken };
|
|
return true;
|
|
}
|
|
error.value = res.msg || "刷新失败";
|
|
return false;
|
|
} catch {
|
|
error.value = "刷新失败,请重试";
|
|
return false;
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
return {
|
|
user,
|
|
loading,
|
|
error,
|
|
initialized,
|
|
isOnline,
|
|
fetchSession,
|
|
signIn,
|
|
signUp,
|
|
signOut,
|
|
refreshApiToken
|
|
};
|
|
});
|