/** * 用户状态管理 Store * * 管理用户认证状态和个人信息,提供: * - 登录/注册/登出操作 * - Token 自动管理(存储 + 刷新) * - 用户信息缓存与更新 * - 持久化存储(通过 pinia-plugin-persistedstate,H5 用 localStorage,小程序用 uni 存储) * * 对应后端 API:/api/v1/auth/* * 接口文档见:docs/api/frontend/auth.md */ import { defineStore } from 'pinia' import { ref, computed } from 'vue' import authApi from '@/api/auth' import { useWebSocketStore } from '@/store/websocket' import { saveToken, getToken, getRefreshToken, removeToken, saveUserInfo, getUserInfo, removeUserInfo, clearAll } from '@/utils/storage' /** * 用户 Store(Composition API 风格) * * state: * - token: Access Token * - userInfo: 用户信息对象 * * getters: * - isLoggedIn: 是否已登录 * - username: 用户名快捷访问 * - roles: 用户角色列表 * * actions: * - login: 用户登录 * - register: 用户注册 * - logout: 退出登录 * - getProfile: 获取/刷新用户信息 * - updateProfile: 更新个人信息 * - changePassword: 修改密码 * - refreshUserToken: 刷新 Token */ export const useUserStore = defineStore('user', () => { // ==================== State ==================== /** Access Token,用于 API 认证 */ const token = ref(getToken() || '') /** 用户信息对象,与后端 dto.UserInfo 结构一致 */ const userInfo = ref(getUserInfo() || null) // ==================== Getters ==================== /** 是否已登录(Token 存在视为已登录) */ const isLoggedIn = computed(() => !!token.value) /** 用户名快捷访问 */ const username = computed(() => userInfo.value?.username || '') /** 用户角色列表 */ const roles = computed(() => userInfo.value?.roles || []) // ==================== Actions ==================== /** * 处理登录/注册成功后的通用逻辑 * 保存 Token 和用户信息到 Store 和本地存储 * * @param {Object} data - 后端返回的登录响应 { token, refresh_token, expires_in, user } */ const _handleAuthSuccess = (data) => { token.value = data.token userInfo.value = data.user saveToken(data.token, data.refresh_token, data.expires_in) saveUserInfo(data.user) } /** * 用户登录 * * @param {Object} loginData - { account, password } * @returns {Promise} 登录响应数据 */ const login = async (loginData) => { const res = await authApi.login(loginData) _handleAuthSuccess(res.data) return res.data } /** * 用户注册(注册成功后自动登录) * * @param {Object} registerData - { username, email, password, nickname? } * @returns {Promise} 注册响应数据 */ const register = async (registerData) => { const res = await authApi.register(registerData) _handleAuthSuccess(res.data) return res.data } const applyOAuthLogin = (data) => { _handleAuthSuccess(data) return data } /** * 退出登录 * 1. 调用后端 API 从 Redis 删除 Token(使 Token 立即失效) * 2. 清除本地存储的 Token 和用户信息 * 3. 重置 Store 状态 */ const logout = async () => { const wsStore = useWebSocketStore() wsStore.disconnect() try { await authApi.logout() } catch (e) { console.warn('登出 API 调用失败,仍然清除本地状态', e) } // 清理 chat store 中的私人视图态(语音已播放缓存按 userId 隔离存在 storage, // 此处只重置运行时 state,下次登录会按新 userId 加载对应缓存) try { const { useChatStore } = await import('@/store/chat') useChatStore().resetVoicePlayedState() } catch (_) { /* ignore */ } // 清空通知中心缓存,避免账号切换后看到前一个用户的通知 try { const { useNotifyStore } = await import('@/store/notify') useNotifyStore().reset() } catch (_) { /* ignore */ } token.value = '' userInfo.value = null clearAll() } /** * 获取/刷新用户信息 * 从后端获取最新的用户资料并更新 Store 和本地缓存 * * @returns {Promise} 用户信息 */ const getProfile = async () => { const res = await authApi.getProfile() userInfo.value = res.data saveUserInfo(res.data) return res.data } /** * 更新个人信息 * * @param {Object} data - 需要更新的字段 { nickname?, avatar?, gender?, phone? } * @returns {Promise} 更新后的用户信息 */ const updateProfile = async (data) => { const res = await authApi.updateProfile(data) userInfo.value = res.data saveUserInfo(res.data) return res.data } /** * 修改密码 * * @param {Object} data - { old_password, new_password } * @returns {Promise} */ const changePassword = async (data) => { return await authApi.changePassword(data) } /** * 刷新 Token * 使用 Refresh Token 获取新的 Access Token * 新 Token 会覆盖 Redis 中的旧值(实现单设备登录) * * @returns {Promise} 刷新是否成功 */ const refreshUserToken = async () => { const refreshTokenValue = getRefreshToken() if (!refreshTokenValue) return false try { const res = await authApi.refreshToken(refreshTokenValue) _handleAuthSuccess(res.data) return true } catch (e) { // Refresh Token 也过期或无效,需要重新登录 token.value = '' userInfo.value = null clearAll() return false } } return { // state token, userInfo, // getters isLoggedIn, username, roles, // actions login, register, applyOAuthLogin, logout, getProfile, updateProfile, changePassword, refreshUserToken } }, { /** * Pinia 持久化配置 * H5 环境使用 localStorage,小程序/App 使用 uni 存储 * 仅持久化 token 和 userInfo,避免存储过多无关数据 */ persist: { key: 'echo-user-store', storage: { getItem: (key) => uni.getStorageSync(key), setItem: (key, value) => uni.setStorageSync(key, value) }, paths: ['token', 'userInfo'] } })