user.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. /**
  2. * 用户状态管理模块
  3. *
  4. * 提供用户相关的状态管理
  5. *
  6. * ## 主要功能
  7. *
  8. * - 用户登录状态管理
  9. * - 用户信息存储
  10. * - 访问令牌和刷新令牌管理
  11. * - 登出清理逻辑
  12. *
  13. * @module store/modules/user
  14. */
  15. import { defineStore } from 'pinia'
  16. import { ref, computed } from 'vue'
  17. import { LanguageEnum } from '@/enums/appEnum'
  18. import { router } from '@/router'
  19. import { useWorktabStore } from './worktab'
  20. import { AppRouteRecord } from '@/types/router'
  21. import { setPageTitle } from '@/utils/router'
  22. import { resetRouterState } from '@/router/guards/beforeEach'
  23. import { useMenuStore } from './menu'
  24. import { StorageConfig } from '@/utils/storage/storage-config'
  25. import { fetchLogout } from '@/api/auth'
  26. /**
  27. * 用户状态管理
  28. * 管理用户登录状态、个人信息、令牌等
  29. */
  30. export const useUserStore = defineStore(
  31. 'userStore',
  32. () => {
  33. // 登录状态
  34. const isLogin = ref(false)
  35. // 用户信息
  36. const info = ref<Api.Auth.UserInfo>({} as Api.Auth.UserInfo)
  37. // 访问令牌
  38. const accessToken = ref('')
  39. // 刷新令牌
  40. const refreshToken = ref('')
  41. /** 界面语言(與 vue-i18n、Element Plus 語系同步) */
  42. const language = ref<LanguageEnum>(LanguageEnum.ZH)
  43. /** 全局搜索最近访问记录(ArtGlobalSearch) */
  44. const searchHistory = ref<AppRouteRecord[]>([])
  45. /** 锁屏状态(ArtScreenLock) */
  46. const isLock = ref(false)
  47. /** 锁屏密码(AES 加密串) */
  48. const lockPassword = ref('')
  49. // 计算属性:获取用户信息
  50. const getUserInfo = computed(() => info.value)
  51. /**
  52. * 设置用户信息
  53. * @param newInfo 新的用户信息
  54. */
  55. const setUserInfo = (newInfo: Partial<Api.Auth.UserInfo> & Record<string, any>) => {
  56. info.value = {
  57. ...(info.value || {}),
  58. ...(newInfo || {}),
  59. buttons: newInfo?.buttons ?? info.value?.buttons ?? [],
  60. userName: newInfo?.userName ?? newInfo?.username ?? info.value?.userName ?? ''
  61. } as Api.Auth.UserInfo
  62. }
  63. /**
  64. * 设置登录状态
  65. * @param status 登录状态
  66. */
  67. const setLoginStatus = (status: boolean) => {
  68. isLogin.value = status
  69. }
  70. /**
  71. * 设置令牌
  72. * @param newAccessToken 访问令牌
  73. * @param newRefreshToken 刷新令牌(可选)
  74. */
  75. const setToken = (newAccessToken: string, newRefreshToken?: string) => {
  76. accessToken.value = newAccessToken
  77. if (newRefreshToken) {
  78. refreshToken.value = newRefreshToken
  79. }
  80. }
  81. const setLanguage = (lang: LanguageEnum) => {
  82. language.value = lang
  83. }
  84. const setSearchHistory = (list: AppRouteRecord[]) => {
  85. searchHistory.value = list
  86. }
  87. const setLockStatus = (locked: boolean) => {
  88. isLock.value = locked
  89. }
  90. const setLockPassword = (pwd: string) => {
  91. lockPassword.value = pwd
  92. }
  93. /**
  94. * 退出登录
  95. * 通知后端撤销 Redis 会话,然后清空所有用户相关状态并跳转到登录页
  96. */
  97. const logOut = async () => {
  98. // 先通知后端撤销 Redis 会话(不等待结果,失败也不阻塞退出)
  99. if (accessToken.value) {
  100. fetchLogout().catch(() => {})
  101. }
  102. // 保存当前用户 ID,用于下次登录时判断是否为同一用户
  103. const currentUserId = info.value.userId
  104. if (currentUserId) {
  105. localStorage.setItem(StorageConfig.LAST_USER_ID_KEY, String(currentUserId))
  106. }
  107. // 清空用户信息
  108. info.value = {} as Api.Auth.UserInfo
  109. // 重置登录状态
  110. isLogin.value = false
  111. // 清空访问令牌
  112. accessToken.value = ''
  113. // 清空刷新令牌
  114. refreshToken.value = ''
  115. searchHistory.value = []
  116. isLock.value = false
  117. lockPassword.value = ''
  118. // 移除iframe路由缓存
  119. sessionStorage.removeItem('iframeRoutes')
  120. // 清空主页路径
  121. useMenuStore().setHomePath('')
  122. // 重置路由状态
  123. resetRouterState(500)
  124. // 跳转到登录页,携带当前路由作为 redirect 参数
  125. const currentRoute = router.currentRoute.value
  126. const redirect = currentRoute.path !== '/login' ? currentRoute.fullPath : undefined
  127. router.push({
  128. name: 'Login',
  129. query: redirect ? { redirect } : undefined
  130. })
  131. }
  132. /**
  133. * 检查并清理工作台标签页
  134. * 如果不是同一用户登录,清空工作台标签页
  135. * 应在登录成功后调用
  136. */
  137. const checkAndClearWorktabs = () => {
  138. const lastUserId = localStorage.getItem(StorageConfig.LAST_USER_ID_KEY)
  139. const currentUserId = info.value.userId
  140. // 无法获取当前用户 ID,跳过检查
  141. if (!currentUserId) return
  142. // 首次登录或缓存已清除,保留现有标签页
  143. if (!lastUserId) {
  144. return
  145. }
  146. // 不同用户登录,清空工作台标签页
  147. if (String(currentUserId) !== lastUserId) {
  148. const worktabStore = useWorktabStore()
  149. worktabStore.opened = []
  150. worktabStore.keepAliveExclude = []
  151. }
  152. // 清除临时存储
  153. localStorage.removeItem(StorageConfig.LAST_USER_ID_KEY)
  154. }
  155. return {
  156. isLogin,
  157. info,
  158. accessToken,
  159. refreshToken,
  160. language,
  161. searchHistory,
  162. isLock,
  163. lockPassword,
  164. getUserInfo,
  165. setUserInfo,
  166. setLoginStatus,
  167. setToken,
  168. setLanguage,
  169. setSearchHistory,
  170. setLockStatus,
  171. setLockPassword,
  172. logOut,
  173. checkAndClearWorktabs
  174. }
  175. },
  176. {
  177. persist: {
  178. key: 'user',
  179. storage: localStorage
  180. }
  181. }
  182. )