This commit is contained in:
Timi
2026-07-30 18:28:25 +08:00
parent 8b24804b22
commit f0d1090932
21 changed files with 915 additions and 268 deletions
+180 -64
View File
@@ -1,79 +1,195 @@
import { LoginResponse, LoginToken, LoginUser, UserToken } from "../types";
import {defineStore} from "pinia";
import {computed, reactive, readonly, ref} from "vue";
import {CaptchaData, LoginRequest, LoginResponse, LoginUser} from "../types";
import Cooker from "../utils/Cooker";
import Storage from "../utils/Storage";
import UserAPI from "../api/UserAPI";
import {UserAPI} from "../api";
const loginUser = reactive<LoginUser>({
token: undefined,
user: undefined
});
function isLogged(): boolean {
return !!loginUser.user;
/** 用户 Store 配置项 */
interface UserStoreOptions {
/** localStorage 储存 key,默认 "loginUser" */
storageKey?: string;
}
async function login4Token(token: LoginToken): Promise<LoginResponse | null> {
loginUser.token = token;
// 未过期
try {
const resp = await UserAPI.login4Token();
await updateToken(resp);
return resp;
} catch (e) {
await logout();
/** 访问控制匹配模式 */
export type AccessMatchMode = "and" | "or";
let _storageKey = "loginUser";
function normalizeAccessList(list?: string[]): string[] {
if (!list?.length) {
return [];
}
return null;
return list
.map(item => item?.trim())
.filter((item): item is string => !!item);
}
async function login4Storage(): Promise<LoginResponse | null> {
if (!loginUser.user && (loginUser.token || Storage.has("token"))) {
// 未登录,储存有令牌
const token = Storage.getJSON("token") as UserToken;
if (new Date().getTime() < token.expireAt) {
return await login4Token(token);
function matchAccess(sourceList?: string[], targetList?: string | string[], mode: AccessMatchMode = "or"): boolean {
const normalizedTargetList = normalizeAccessList(Array.isArray(targetList) ? targetList : [targetList || ""]);
if (!normalizedTargetList.length) {
return true;
}
const sourceSet = new Set(normalizeAccessList(sourceList));
if (!sourceSet.size) {
return false;
}
if (mode === "and") {
return normalizedTargetList.every(item => sourceSet.has(item));
}
return normalizedTargetList.some(item => sourceSet.has(item));
}
/**
* 配置用户 Store,应在使用 useUserStore 之前调用
* @param options 配置项
*/
function configureUserStore(options: UserStoreOptions): void {
if (options.storageKey) {
_storageKey = options.storageKey;
}
}
const useUserStore = defineStore("user", () => {
type LoginUserState = LoginUser & Record<string, unknown>;
const loginUser = reactive<LoginUserState>({});
const isRestored = ref(false);
const token = computed(() => loginUser.token ?? "");
const isLoggedIn = computed(() => isValidLoginUser(loginUser));
function resetLoginUser(nextUser?: LoginUserState): void {
Object.keys(loginUser).forEach((key) => {
delete loginUser[key];
});
if (nextUser) {
Object.assign(loginUser, nextUser);
}
}
return null;
}
async function updateToken(loginResponse: LoginResponse): Promise<void> {
loginUser.token = {
id: loginResponse.id,
value: loginResponse.token,
expireAt: loginResponse.expireAt
};
loginUser.user = await UserAPI.view(loginResponse.id);
Storage.setJSON("token", {
value: loginUser.token.value,
expireAt: loginUser.token.expireAt
} as UserToken);
}
async function logout(): Promise<void> {
await UserAPI.logout();
loginUser.token = undefined;
loginUser.user = undefined;
Storage.remove("token");
}
async function reloadProfile() {
if (loginUser.token && loginUser.token.id) {
loginUser.user = await UserAPI.view(loginUser.token.id);
function isValidLoginUser(user?: LoginUserState | null): user is LoginResponse {
return !!user?.token && !!user.expireAt && new Date().getTime() < user.expireAt;
}
}
const userStore = {
loginUser,
isLogged,
login4Token,
login4Storage,
updateToken,
logout,
reloadProfile
};
function isLogged(): boolean {
return isLoggedIn.value;
}
function getLoginUser<T extends LoginUser = LoginUser>(): T | null {
if (!isLogged()) {
return null;
}
return loginUser as T;
}
function getRoleList(): string[] {
return normalizeAccessList(loginUser.user?.roleList);
}
function getPermissionList(): string[] {
return normalizeAccessList(loginUser.user?.permissionList);
}
function hasRoles(roleList: string | string[], mode: AccessMatchMode = "or"): boolean {
return matchAccess(getRoleList(), roleList, mode);
}
function hasPermissions(permissionList: string | string[], mode: AccessMatchMode = "or"): boolean {
return matchAccess(getPermissionList(), permissionList, mode);
}
async function login<T extends LoginUser = LoginUser>(req: CaptchaData<LoginRequest>): Promise<T> {
const result = await UserAPI.login(req);
const nextUser = result as unknown as T;
await updateLoginUser(nextUser);
return nextUser;
}
async function loginByToken<T extends LoginUser = LoginUser>(token: string): Promise<T | null> {
loginUser.token = token;
// 未过期
try {
const result = await UserAPI.loginByToken();
if (!result) {
return null;
}
const nextUser = result as unknown as T;
await updateLoginUser(nextUser);
return nextUser;
} catch (e) {
console.error(e);
clearLoginUser();
}
return null;
}
async function loginByStorage<T extends LoginUser = LoginUser>(): Promise<T | null> {
if (loginUser.token) {
isRestored.value = true;
return getLoginUser<T>();
}
if (!Storage.has(_storageKey)) {
isRestored.value = true;
return null;
}
// 未登录,储存有令牌。
const storageUser = Storage.getJSON(_storageKey) as T;
if (isValidLoginUser(storageUser)) {
const result = await loginByToken<T>(storageUser.token);
isRestored.value = true;
return result;
}
clearLoginUser();
isRestored.value = true;
return null;
}
async function updateLoginUser<T extends LoginUser = LoginUser>(user: T): Promise<void> {
resetLoginUser(user as LoginUserState);
Storage.setJSON(_storageKey, user);
}
function clearLoginUser(): void {
resetLoginUser();
Cooker.remove("Token");
Storage.remove(_storageKey);
}
async function logout(): Promise<void> {
try {
await UserAPI.logout();
} finally {
clearLoginUser();
}
}
return {
token,
isLoggedIn,
isRestored,
getRoleList,
getPermissionList,
hasRoles,
hasPermissions,
isLogged,
loginUser: readonly(loginUser),
getLoginUser,
login,
loginByToken,
loginByStorage,
updateLoginUser,
clearLoginUser,
logout
};
});
export {
userStore
useUserStore,
configureUserStore
};