import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { notification } from 'ant-design-vue' import { loginApi, refreshApi, profileApi } from '@/api/auth' import { TOKEN_KEY } from '@/api/request' const USER_KEY = 'agent_admin_user' const EXPIRE_KEY = 'agent_admin_expire' // 用户会话 store:登录 / 登出 / 会话校验 / 静默续签 export const useUserStore = defineStore('user', () => { const token = ref(localStorage.getItem(TOKEN_KEY) || '') const user = ref(safeParse(localStorage.getItem(USER_KEY))) const expireAt = ref(Number(localStorage.getItem(EXPIRE_KEY) || 0)) const isAuthenticated = computed(() => !!token.value) function safeParse(s) { try { return s ? JSON.parse(s) : null } catch { return null } } // 登录:成功后落 localStorage 并返回,失败抛错由登录页展示(含 shake 动效) async function login({ username, password, captcha }) { const res = await loginApi({ username, password, captcha }) const d = res.data token.value = d.token user.value = { nick_name: d.nick_name, role_name: d.role_name, role: d.role } expireAt.value = d.expire_at || 0 localStorage.setItem(TOKEN_KEY, d.token) localStorage.setItem(USER_KEY, JSON.stringify(user.value)) localStorage.setItem(EXPIRE_KEY, String(expireAt.value)) notification.success({ message: '登录成功', description: `欢迎回来,${d.nick_name}` }) return d } // 会话校验:启动时调 profile,token 失效会走 401 全局跳转 async function checkSession() { if (!token.value) return false try { const res = await profileApi() user.value = { ...user.value, ...res.data } return true } catch { return false } } // 静默续签:剩余有效期 < 24h 时换新 token(AdminLayout 定时驱动) async function maybeRefresh() { if (!token.value || !expireAt.value) return const remain = expireAt.value - Math.floor(Date.now() / 1000) if (remain > 24 * 3600 || remain <= 0) return try { const res = await refreshApi() token.value = res.data.token expireAt.value = res.data.expire_at || 0 localStorage.setItem(TOKEN_KEY, token.value) localStorage.setItem(EXPIRE_KEY, String(expireAt.value)) } catch { /* 续签失败不打扰用户,到期自然走 401 */ } } function logout() { token.value = '' user.value = null expireAt.value = 0 localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(USER_KEY) localStorage.removeItem(EXPIRE_KEY) } return { token, user, expireAt, isAuthenticated, login, logout, checkSession, maybeRefresh } })