/** * 维护后端真实登录态,前端只保存 DevOps API token,不保存任何第三方密钥。 */ import { computed, ref } from 'vue' import { defineStore } from 'pinia' import { devopsApi } from '../services/devopsApi' import { setAuthToken } from '../services/http' import type { AuthenticatedUser, ChangePasswordPayload, LoginPayload, } from '../types/devops' const STORAGE_KEY = 'devops-platform-session' type StoredSession = { token: string expiresAt: string user: AuthenticatedUser } export const useSessionStore = defineStore('session', () => { const restored = loadStoredSession() const operator = ref(restored?.user.account ?? '') const displayName = ref(restored?.user.displayName ?? '') const role = ref(restored?.user.role ?? '') const token = ref(restored?.token ?? '') const expiresAt = ref(restored?.expiresAt ?? '') const currentUser = ref(restored?.user ?? null) const bootstrapped = ref(false) setAuthToken(token.value) const isAuthenticated = computed(() => Boolean(token.value && currentUser.value)) const mustChangePassword = computed(() => Boolean(currentUser.value?.mustChangePassword), ) const roleLabel = computed(() => { if (role.value === 'super_admin') { return '超级管理员' } if (role.value === 'member') { return '普通成员' } return '未登录' }) async function bootstrap(): Promise { if (bootstrapped.value) { return isAuthenticated.value } bootstrapped.value = true if (!token.value) { return false } try { const user = await devopsApi.getCurrentUser() applyUser(user) persist() return true } catch { clearSession() return false } } async function login(payload: LoginPayload) { const result = await devopsApi.login(payload) token.value = result.token expiresAt.value = result.expiresAt setAuthToken(result.token) applyUser(result.user) persist() bootstrapped.value = true return result.user } async function changePassword(payload: ChangePasswordPayload) { const user = await devopsApi.changePassword(payload) applyUser(user) persist() return user } async function logout() { if (token.value) { try { await devopsApi.logout() } catch { // 退出以清理本地登录态为准,后端当前无状态 token 无需强依赖 logout 成功。 } } clearSession() } function applyUser(user: AuthenticatedUser) { currentUser.value = user operator.value = user.account displayName.value = user.displayName role.value = user.role } function persist() { if (!token.value || !currentUser.value) { localStorage.removeItem(STORAGE_KEY) return } const stored: StoredSession = { token: token.value, expiresAt: expiresAt.value, user: currentUser.value, } localStorage.setItem(STORAGE_KEY, JSON.stringify(stored)) } function clearSession() { operator.value = '' displayName.value = '' role.value = '' token.value = '' expiresAt.value = '' currentUser.value = null setAuthToken() localStorage.removeItem(STORAGE_KEY) } return { operator, displayName, role, roleLabel, token, expiresAt, currentUser, isAuthenticated, mustChangePassword, bootstrap, login, changePassword, logout, } }) function loadStoredSession(): StoredSession | null { try { const rawValue = localStorage.getItem(STORAGE_KEY) if (!rawValue) { return null } const parsed = JSON.parse(rawValue) as StoredSession if (!parsed.token || !parsed.user?.account) { return null } return parsed } catch { localStorage.removeItem(STORAGE_KEY) return null } }