import { ref } from "vue"; import { defineStore } from "pinia"; import { apiRequest } from "@/iview/utils/request"; import { toAny } from "@/iview/utils/toany"; import { on } from "@/iview/utils/emitter"; import config from "@/config/index"; const generateKey = (key) => { return `${config.APP_CACHE_PROFIX}_${key}`; }; const getStorageValue = (key) => { return Promise.resolve().then(() => { key = generateKey(key); const value = localStorage.getItem(key); return toAny(value, {}).value; }); }; const setStorageValue = (key, value) => { return Promise.resolve().then(() => { key = generateKey(key); localStorage.setItem( key, toAny({ key, value }, "") ); }); }; const removeStorageValue = (key) => { return Promise.resolve().then(() => { key = generateKey(key); localStorage.removeItem(key); }); }; const API_LOGIN = "api.system.admin.info.auth.login"; const API_PROFILE = "api.system.admin.info.auth.profile"; const API_REFRESH_TOKEN = `refreshToken`; const KEY_SIDEBAR_COLLAPSE = "state_collapseSidebar"; const KEY_LAST_USERNAME = "state_lastUsername"; const KEY_REFRESH_TOKEN = "state_refreshToken"; const KEY_USER_TOKEN = "state_userToken"; const KEY_USER_INFO = "state_userInfo"; /** 防止 103 / 刷新失败 连续触发时重复跳转 */ let sessionExpiryHandling = false; export const useUserStore = defineStore("user", { state: () => ({ initialized: ref(false), collapseSidebar: ref(false), lastUsername: ref(null), refreshToken: ref(null), userToken: ref(null), userInfo: ref(null), }), getters: { isLoggedIn: (state) => !!state.userToken, isInitialized: (state) => state.initialized, isCollapseSidebar: (state) => state.collapseSidebar, isSuper: (state) => state.userInfo?.optSuper === 1, username: (state) => state.userInfo?.username, nickname: (state) => state.userInfo?.nickname, }, actions: { async init() { const collapseSidebar = await getStorageValue(KEY_SIDEBAR_COLLAPSE); const lastUsername = await getStorageValue(KEY_LAST_USERNAME); const refreshToken = await getStorageValue(KEY_REFRESH_TOKEN); const userToken = await getStorageValue(KEY_USER_TOKEN); const userInfo = await getStorageValue(KEY_USER_INFO); this.initialized = true; this.collapseSidebar = collapseSidebar === "true"; this.lastUsername = lastUsername; this.refreshToken = refreshToken; this.userToken = userToken; this.userInfo = userInfo; }, // 登录 async login(username, password) { const data = await apiRequest(API_LOGIN, { username, password, }); this.userToken = data.token; this.refreshToken = data.refreshToken; this.lastUsername = username; await setStorageValue(KEY_LAST_USERNAME, username); await setStorageValue(KEY_REFRESH_TOKEN, data.refreshToken); await setStorageValue(KEY_USER_TOKEN, data.token); await this.getUserProfile(); return { success: true }; }, // 刷新Token async refreshUserToken() { if (!this.refreshToken) { throw new Error("No refresh token available"); } try { const data = await apiRequest(API_REFRESH_TOKEN, { refreshToken: this.refreshToken, }); this.userToken = data.token; this.refreshToken = data.refreshToken; await setStorageValue(KEY_USER_TOKEN, data.token); await setStorageValue(KEY_REFRESH_TOKEN, data.refreshToken); return { success: true, token: data.token, refreshToken: data.refreshToken }; } catch (error) { this.refreshToken = null; await removeStorageValue(KEY_REFRESH_TOKEN); throw error; } }, // 获取用户信息 async getUserProfile() { const userInfo = await apiRequest(API_PROFILE, {}); this.userInfo = userInfo; await setStorageValue(KEY_USER_INFO, userInfo); return { success: true, data: userInfo }; }, // 退出登录 async logout() { this.userToken = ""; this.refreshToken = ""; this.userInfo = {}; await removeStorageValue(KEY_USER_INFO); await removeStorageValue(KEY_USER_TOKEN); await removeStorageValue(KEY_REFRESH_TOKEN); }, async saveCollapseSidebar(collapse) { this.collapseSidebar = !!collapse; await setStorageValue(KEY_SIDEBAR_COLLAPSE, this.collapseSidebar ? "true" : "false"); }, /** 登录失效:清状态并跳到登录(用动态 import router,避免与 request 循环依赖且保证能拿到路由器实例) */ async handleSessionExpired() { if (sessionExpiryHandling) return; sessionExpiryHandling = true; try { await this.logout(); const { default: router } = await import("@/config/router"); const current = router.currentRoute.value; if (current.path === "/login") return; await router.replace({ path: "/login", query: { redirect: current.fullPath }, }); } finally { sessionExpiryHandling = false; } }, initTokenExpirationListener() { on("auth:token-expired", () => { void this.handleSessionExpired(); }); }, }, });