import { defineStore } from 'pinia' import { login } from '@/api/auth' import { getRolePermissions } from '@/api/system/permission' import { ref } from 'vue' import axios from 'axios' // JWT 解码函数(手写实现,不依赖外部库) function parseJWT(token: string): any { if (!token) return null try { const base64Url = token.split('.')[1] if (!base64Url) return null const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/') const jsonPayload = decodeURIComponent( atob(base64) .split('') .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ) return JSON.parse(jsonPayload) } catch (e) { console.error('JWT parse error:', e) return null } } // 获取 Token 过期时间戳 function getTokenExp(token: string): number | null { const decoded = parseJWT(token) return decoded?.exp ? decoded.exp * 1000 : null // 转换为毫秒 } export const useUserStore = defineStore('user', () => { // 1. State: 初始化时优先从 localStorage 获取,防止刷新丢失 const token = ref(localStorage.getItem('access_token') || localStorage.getItem('token') || '') const refreshToken = ref(localStorage.getItem('refresh_token') || '') const role = ref(localStorage.getItem('role') || '') const username = ref(localStorage.getItem('username') || '') const permissions = ref(JSON.parse(localStorage.getItem('permissions') || '[]')) // 兼容旧版 localStorage key if (localStorage.getItem('token') && !localStorage.getItem('access_token')) { localStorage.setItem('access_token', localStorage.getItem('token') || '') } // 2. Actions // 登录逻辑 const handleLogin = async (loginForm: any) => { const res = await login(loginForm) // [调试日志] 查看实际返回的数据结构 console.log('Login API Response:', res) // ============================================================ // [关键修复] 兼容 Axios 拦截器的不同处理方式 // 如果拦截器已经返回了 response.data,那么 res 本身就是数据对象 // ============================================================ const data = res.data || res // 安全检查:确保 data 存在且包含 access_token if (!data || !data.access_token) { console.error('Login Error: 响应数据中缺少 access_token', data) throw new Error('登录失败: 响应数据异常') } // 更新 Pinia 状态 (内存) token.value = data.access_token refreshToken.value = data.refresh_token || '' // 处理用户信息 (确保后端返回结构中有 user 字段) if (data.user) { const rawRole = data.user.role || 'user' role.value = rawRole.toUpperCase() // 角色统一转换为大写 username.value = data.user.username || '用户' // 持久化存储用户信息 localStorage.setItem('role', role.value) localStorage.setItem('username', username.value) } // 持久化存储双 Token localStorage.setItem('access_token', data.access_token) if (data.refresh_token) { localStorage.setItem('refresh_token', data.refresh_token) } // [Dify] 登录成功,重新初始化 Dify(Token 变化时 Dify 会开辟新会话,解决会话串号问题) if (typeof window.initDifyChatbot === 'function') { window.initDifyChatbot() } // 登录成功后,根据角色获取权限 if (role.value) { try { const permRes = await getRolePermissions(role.value) const permData = permRes.data || permRes // 合并 menus 和 elements 两个数组 const allPerms = [ ...(permData.menus || []), ...(permData.elements || []) ] permissions.value = allPerms localStorage.setItem('permissions', JSON.stringify(allPerms)) } catch (error) { console.error('获取权限失败:', error) permissions.value = [] localStorage.setItem('permissions', '[]') } } return true // 返回 true 表示登录成功 } // 更新 Access Token(无感刷新时调用) const setToken = (newToken: string) => { token.value = newToken localStorage.setItem('access_token', newToken) // [Dify] Token 刷新后,重新初始化 Dify 以更新用户会话 if (typeof window.initDifyChatbot === 'function') { window.initDifyChatbot() } } // 退出逻辑 const logout = () => { // 1. 清空 Pinia 状态 (内存) token.value = '' refreshToken.value = '' role.value = '' username.value = '' permissions.value = [] // 2. 清空 LocalStorage (硬盘) localStorage.removeItem('access_token') // [Dify] 退出登录时,彻底销毁桌面上的 Dify 聊天窗口,防止信息泄露或报错 document.querySelectorAll('[id^="dify-chatbot-"]').forEach(el => el.remove()) // 清空其他本地存储 localStorage.removeItem('refresh_token') localStorage.removeItem('token') localStorage.removeItem('role') localStorage.removeItem('username') localStorage.removeItem('permissions') } // 刷新用户权限(不重新登录) const refreshUserPermissions = async () => { if (!token.value || !role.value) { console.warn('无法刷新权限:用户未登录') return } try { const permRes = await getRolePermissions(role.value) const permData = permRes.data || permRes // 合并 menus 和 elements 两个数组 const allPerms = [ ...(permData.menus || []), ...(permData.elements || []) ] permissions.value = allPerms localStorage.setItem('permissions', JSON.stringify(allPerms)) console.log('用户权限已刷新') } catch (error) { console.error('刷新权限失败:', error) // 可选:保留原有权限 } } // 检查 refresh_token 是否即将过期(30分钟) const isRefreshTokenExpiringSoon = (): boolean => { if (!refreshToken.value) return true const expTime = getTokenExp(refreshToken.value) if (!expTime) return true const now = Date.now() const thirtyMinutes = 30 * 60 * 1000 return expTime - now < thirtyMinutes } // 3. Getters / Helpers // 判断当前用户是否拥有某些角色 const hasRole = (roles: string[]) => { return roles.includes(role.value) } // 判断当前用户是否拥有某个权限(菜单或元素) const hasPermission = (code: string) => { // 超级管理员拥有所有权限 if (role.value && role.value.toUpperCase() === 'SUPER_ADMIN') { return true } return permissions.value.includes(code) } return { token, refreshToken, role, username, permissions, handleLogin, setToken, logout, refreshUserPermissions, isRefreshTokenExpiringSoon, hasRole, hasPermission } })