import type { AuthPermissionInfo, Recordable, UserInfo } from '@vben/types';
|
|
import type { AuthApi } from '#/api';
|
|
import { ref } from 'vue';
|
import { useRouter } from 'vue-router';
|
|
import { LOGIN_PATH } from '@vben/constants';
|
import { preferences } from '@vben/preferences';
|
import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores';
|
|
import { notification } from 'ant-design-vue';
|
import { defineStore } from 'pinia';
|
|
import {
|
getAuthPermissionInfoApi,
|
loginApi,
|
logoutApi,
|
register,
|
smsLogin,
|
socialLogin,
|
} from '#/api';
|
import { $t } from '#/locales';
|
import { automaticApproval, loadAuditPolicy } from '#/store/audit-policy';
|
|
export const useAuthStore = defineStore('auth', () => {
|
const accessStore = useAccessStore();
|
const userStore = useUserStore();
|
const router = useRouter();
|
|
const loginLoading = ref(false);
|
let sessionVersion = 0;
|
let logoutTask: Promise<void> | null = null;
|
|
/**
|
* 异步处理登录操作
|
* Asynchronously handle the login process
|
* @param type 登录类型
|
* @param params 登录表单数据
|
* @param onSuccess 登录成功后的回调函数
|
*/
|
async function authLogin(
|
type: 'mobile' | 'register' | 'social' | 'username',
|
params: Recordable<any>,
|
onSuccess?: () => Promise<void> | void,
|
) {
|
// 异步处理用户登录操作并获取 accessToken
|
let userInfo: null | UserInfo = null;
|
const version = ++sessionVersion;
|
try {
|
let loginResult: AuthApi.LoginResult;
|
loginLoading.value = true;
|
switch (type) {
|
case 'mobile': {
|
loginResult = await smsLogin(params as AuthApi.SmsLoginParams);
|
break;
|
}
|
case 'register': {
|
loginResult = await register(params as AuthApi.RegisterParams);
|
break;
|
}
|
case 'social': {
|
loginResult = await socialLogin(params as AuthApi.SocialLoginParams);
|
break;
|
}
|
default: {
|
loginResult = await loginApi(params);
|
}
|
}
|
const { accessToken, refreshToken } = loginResult;
|
if (version !== sessionVersion) return { userInfo: null };
|
|
// 如果成功获取到 accessToken
|
if (accessToken) {
|
accessStore.setAccessToken(accessToken);
|
accessStore.setRefreshToken(refreshToken);
|
|
// 获取用户信息并存储到 userStore、accessStore 中
|
// TODO @芋艿:清理掉 accessCodes 相关的逻辑
|
// const [fetchUserInfoResult, accessCodes] = await Promise.all([
|
// fetchUserInfo(),
|
// // getAccessCodesApi(),
|
// ]);
|
const fetchUserInfoResult = await fetchUserInfo();
|
if (version !== sessionVersion) return { userInfo: null };
|
|
userInfo = fetchUserInfoResult.user;
|
|
if (accessStore.loginExpired) {
|
accessStore.setLoginExpired(false);
|
} else {
|
// oxlint-disable-next-line no-unused-expressions
|
onSuccess
|
? await onSuccess?.()
|
: await router.push('/analytics');
|
}
|
|
if (userInfo?.nickname) {
|
notification.success({
|
description: `${$t('authentication.loginSuccessDesc')}:${userInfo?.nickname}`,
|
duration: 3,
|
message: $t('authentication.loginSuccess'),
|
});
|
}
|
}
|
} catch (error) {
|
if (version === sessionVersion) throw error;
|
return { userInfo: null };
|
} finally {
|
if (version === sessionVersion) loginLoading.value = false;
|
}
|
|
return {
|
userInfo,
|
};
|
}
|
|
function logout(redirect: boolean = true): Promise<void> {
|
if (logoutTask) return logoutTask;
|
const accessToken = accessStore.accessToken;
|
const tenant = { tenantId: accessStore.tenantId, visitTenantId: accessStore.visitTenantId };
|
const fullPath = router.currentRoute.value.fullPath;
|
++sessionVersion;
|
// 服务器注销独立执行,本地清理与跳转不等待网络。
|
// 使用退出前的 token 和租户快照,避免清理后丢失注销上下文。
|
if (accessToken) void logoutApi(accessToken, tenant).catch(() => {});
|
logoutTask = clearSessionAndNavigate(redirect, fullPath).finally(() => { logoutTask = null; });
|
return logoutTask;
|
}
|
|
async function clearSessionAndNavigate(redirect: boolean, fullPath: string) {
|
try {
|
resetAllStores();
|
} catch (error) {
|
console.error('Failed to reset business stores during logout', error);
|
}
|
// 认证清理不能依赖业务 store 的重置结果。
|
accessStore.$reset();
|
userStore.$reset();
|
accessStore.setLoginExpired(false);
|
|
// 回登录页带上当前路由地址
|
await router.replace({
|
path: LOGIN_PATH,
|
query: redirect
|
? {
|
redirect: encodeURIComponent(fullPath),
|
}
|
: {},
|
});
|
}
|
|
async function fetchUserInfo() {
|
const version = sessionVersion;
|
const accessToken = accessStore.accessToken;
|
// 加载
|
// eslint-disable-next-line no-useless-assignment
|
let authPermissionInfo: AuthPermissionInfo | null = null;
|
authPermissionInfo = await getAuthPermissionInfoApi();
|
if (version !== sessionVersion || accessToken !== accessStore.accessToken) {
|
throw new Error('登录状态已变化,忽略旧会话的用户信息');
|
}
|
await loadAuditPolicy();
|
if (version !== sessionVersion || accessToken !== accessStore.accessToken) {
|
throw new Error('登录状态已变化,忽略旧会话的用户信息');
|
}
|
// userStore
|
userStore.setUserInfo(authPermissionInfo.user);
|
userStore.setUserRoles(authPermissionInfo.roles);
|
// accessStore
|
accessStore.setAccessMenus(authPermissionInfo.menus);
|
accessStore.setAccessCodes(authPermissionInfo.permissions);
|
return authPermissionInfo;
|
}
|
|
function $reset() {
|
loginLoading.value = false;
|
automaticApproval.value = false;
|
}
|
|
return {
|
$reset,
|
authLogin,
|
fetchUserInfo,
|
loginLoading,
|
logout,
|
};
|
});
|