This commit is contained in:
iqudoo
2026-06-05 17:22:32 +08:00
commit eb4e8f1a04
90 changed files with 21224 additions and 0 deletions

171
src/store/user.js Normal file
View File

@@ -0,0 +1,171 @@
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();
});
},
},
});