|
- /**
- * 用户状态管理 composable
- *
- * 管理:token、用户信息、登录状态、登录弹窗显隐
- * Token 持久化:Cookie(SSR 安全,Nuxt useCookie 自动同步服务端/客户端)
- */
- export function useUser() {
- const tokenCookie = useCookie('token', {
- maxAge: 7 * 24 * 3600,
- path: '/',
- })
-
- /**
- * 登录态用 useState 全局共享。
- * 注意:不能直接把 useCookie 的 ref 当共享状态——每次 useCookie('token') 都会返回
- * 一个独立的 ref,页面在登录前创建的 ref 不会感知到登录后的写入,
- * 会导致请求不带 token、后端返回 1009。
- */
- const token = useState('auth_token', () => tokenCookie.value || '')
-
- /** 同时写入共享状态与 Cookie */
- function setToken(value) {
- token.value = value || ''
- tokenCookie.value = value || null
- }
-
- const userInfo = useState('userInfo', () => ({
- user_id: null,
- user_name: '',
- avatar: '',
- nickname: '',
- open_id: '',
- vip_type: 0,
- balance: 0,
- }))
-
- /**
- * 只以 token 判断登录态(对齐 V2)。
- * 不能附加 userInfo.user_id 条件:刷新页面时用户信息是异步拉取的,
- * 否则页面首次渲染会被判为未登录,导致该拉的数据不拉、还误弹登录框。
- */
- const isLogin = computed(() => !!token.value)
-
- // 登录弹窗全局状态(任何组件都可以触发)
- const showLogin = useState('showLogin', () => false)
-
- /**
- * 设置登录态
- */
- function setLogin(data) {
- setToken(data.token)
- userInfo.value = {
- user_id: data.user_id,
- user_name: data.user_name || data.nickname || '',
- avatar: data.avatar || '',
- nickname: data.nickname || data.user_name || '',
- open_id: data.open_id || '',
- vip_type: data.vip_type || 0,
- balance: data.balance || 0,
- }
- // 同步写入 open_id(共享状态 + Cookie,供 useApi 读取 uid)
- if (data.open_id) {
- const openIdCookie = useCookie('open_id', { path: '/' })
- openIdCookie.value = data.open_id
- useState('auth_open_id', () => '').value = data.open_id
- }
- }
-
- /**
- * 退出登录
- */
- function logout() {
- setToken('')
- userInfo.value = {
- user_id: null,
- user_name: '',
- avatar: '',
- nickname: '',
- open_id: '',
- vip_type: 0,
- balance: 0,
- }
- // 清除 open_id
- const openIdCookie = useCookie('open_id', { path: '/' })
- openIdCookie.value = null
- useState('auth_open_id', () => '').value = ''
- }
-
- /**
- * 刷新用户信息
- */
- async function refreshUserInfo() {
- if (!token.value) return
- try {
- const { post } = useApi()
- const res = await post('/user/getuserinfo')
- if (res.code === 0 && res.data) {
- userInfo.value = {
- ...userInfo.value,
- ...res.data,
- }
- }
- } catch (e) {
- // 获取失败不中断
- }
- }
-
- /**
- * 刷新点数余额。
- * 注意:/user/getuserinfo 不返回点数,必须单独查 /point/balance,
- * 否则 header 会一直显示登录时的旧值(通常是 0)。
- */
- async function refreshBalance() {
- if (!token.value) return 0
- try {
- const { post } = useApi()
- const res = await post('/point/balance')
- const points = res?.data?.points ?? res?.data?.point_balance ?? 0
- userInfo.value = { ...userInfo.value, balance: Number(points) || 0 }
- return userInfo.value.balance
- } catch {
- return userInfo.value.balance || 0
- }
- }
-
- /**
- * 初始化:如果有 token 但无 userInfo,自动刷新
- */
- async function init() {
- if (token.value && !userInfo.value.user_id) {
- await refreshUserInfo()
- }
- }
-
- /**
- * 处理 1009 鉴权失效(由 useApi 调用)
- */
- function handleAuthError() {
- if (import.meta.client) {
- logout()
- showLogin.value = true
- }
- }
-
- // 客户端初始化
- if (import.meta.client) {
- init()
- }
-
- return {
- token,
- setToken,
- userInfo,
- isLogin,
- showLogin,
- setLogin,
- logout,
- refreshUserInfo,
- refreshBalance,
- handleAuthError,
- init,
- }
- }
|