Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 

164 righe
4.1 KiB

  1. /**
  2. * 用户状态管理 composable
  3. *
  4. * 管理:token、用户信息、登录状态、登录弹窗显隐
  5. * Token 持久化:Cookie(SSR 安全,Nuxt useCookie 自动同步服务端/客户端)
  6. */
  7. export function useUser() {
  8. const tokenCookie = useCookie('token', {
  9. maxAge: 7 * 24 * 3600,
  10. path: '/',
  11. })
  12. /**
  13. * 登录态用 useState 全局共享。
  14. * 注意:不能直接把 useCookie 的 ref 当共享状态——每次 useCookie('token') 都会返回
  15. * 一个独立的 ref,页面在登录前创建的 ref 不会感知到登录后的写入,
  16. * 会导致请求不带 token、后端返回 1009。
  17. */
  18. const token = useState('auth_token', () => tokenCookie.value || '')
  19. /** 同时写入共享状态与 Cookie */
  20. function setToken(value) {
  21. token.value = value || ''
  22. tokenCookie.value = value || null
  23. }
  24. const userInfo = useState('userInfo', () => ({
  25. user_id: null,
  26. user_name: '',
  27. avatar: '',
  28. nickname: '',
  29. open_id: '',
  30. vip_type: 0,
  31. balance: 0,
  32. }))
  33. /**
  34. * 只以 token 判断登录态(对齐 V2)。
  35. * 不能附加 userInfo.user_id 条件:刷新页面时用户信息是异步拉取的,
  36. * 否则页面首次渲染会被判为未登录,导致该拉的数据不拉、还误弹登录框。
  37. */
  38. const isLogin = computed(() => !!token.value)
  39. // 登录弹窗全局状态(任何组件都可以触发)
  40. const showLogin = useState('showLogin', () => false)
  41. /**
  42. * 设置登录态
  43. */
  44. function setLogin(data) {
  45. setToken(data.token)
  46. userInfo.value = {
  47. user_id: data.user_id,
  48. user_name: data.user_name || data.nickname || '',
  49. avatar: data.avatar || '',
  50. nickname: data.nickname || data.user_name || '',
  51. open_id: data.open_id || '',
  52. vip_type: data.vip_type || 0,
  53. balance: data.balance || 0,
  54. }
  55. // 同步写入 open_id(共享状态 + Cookie,供 useApi 读取 uid)
  56. if (data.open_id) {
  57. const openIdCookie = useCookie('open_id', { path: '/' })
  58. openIdCookie.value = data.open_id
  59. useState('auth_open_id', () => '').value = data.open_id
  60. }
  61. }
  62. /**
  63. * 退出登录
  64. */
  65. function logout() {
  66. setToken('')
  67. userInfo.value = {
  68. user_id: null,
  69. user_name: '',
  70. avatar: '',
  71. nickname: '',
  72. open_id: '',
  73. vip_type: 0,
  74. balance: 0,
  75. }
  76. // 清除 open_id
  77. const openIdCookie = useCookie('open_id', { path: '/' })
  78. openIdCookie.value = null
  79. useState('auth_open_id', () => '').value = ''
  80. }
  81. /**
  82. * 刷新用户信息
  83. */
  84. async function refreshUserInfo() {
  85. if (!token.value) return
  86. try {
  87. const { post } = useApi()
  88. const res = await post('/user/getuserinfo')
  89. if (res.code === 0 && res.data) {
  90. userInfo.value = {
  91. ...userInfo.value,
  92. ...res.data,
  93. }
  94. }
  95. } catch (e) {
  96. // 获取失败不中断
  97. }
  98. }
  99. /**
  100. * 刷新点数余额。
  101. * 注意:/user/getuserinfo 不返回点数,必须单独查 /point/balance,
  102. * 否则 header 会一直显示登录时的旧值(通常是 0)。
  103. */
  104. async function refreshBalance() {
  105. if (!token.value) return 0
  106. try {
  107. const { post } = useApi()
  108. const res = await post('/point/balance')
  109. const points = res?.data?.points ?? res?.data?.point_balance ?? 0
  110. userInfo.value = { ...userInfo.value, balance: Number(points) || 0 }
  111. return userInfo.value.balance
  112. } catch {
  113. return userInfo.value.balance || 0
  114. }
  115. }
  116. /**
  117. * 初始化:如果有 token 但无 userInfo,自动刷新
  118. */
  119. async function init() {
  120. if (token.value && !userInfo.value.user_id) {
  121. await refreshUserInfo()
  122. }
  123. }
  124. /**
  125. * 处理 1009 鉴权失效(由 useApi 调用)
  126. */
  127. function handleAuthError() {
  128. if (import.meta.client) {
  129. logout()
  130. showLogin.value = true
  131. }
  132. }
  133. // 客户端初始化
  134. if (import.meta.client) {
  135. init()
  136. }
  137. return {
  138. token,
  139. setToken,
  140. userInfo,
  141. isLogin,
  142. showLogin,
  143. setLogin,
  144. logout,
  145. refreshUserInfo,
  146. refreshBalance,
  147. handleAuthError,
  148. init,
  149. }
  150. }