2026-02-26 14:44:36 +08:00

209 lines
5.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
import { getUsdcBalance, formatUsdcBalance, getUserInfo } from '@/api/user'
import { getUserWsUrl } from '@/api/request'
import { UserSdk, type BalanceData } from '../../sdk/userSocket'
export interface UserInfo {
/** 用户 IDAPI 可能返回 id 或 ID */
id?: number | string
ID?: number
headerImg?: string
nickName?: string
userName?: string
[key: string]: unknown
}
const STORAGE_KEY = 'poly-user'
function loadStored(): { token: string; user: UserInfo } | null {
try {
const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return null
return JSON.parse(raw) as { token: string; user: UserInfo }
} catch {
return null
}
}
function saveToStorage(token: string, user: UserInfo) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify({ token, user }))
} catch {
//
}
}
function clearStorage() {
try {
localStorage.removeItem(STORAGE_KEY)
} catch {
//
}
}
export const useUserStore = defineStore('user', () => {
const stored = loadStored()
const token = ref<string>(stored?.token ?? '')
const user = ref<UserInfo | null>(stored?.user ?? null)
const isLoggedIn = computed(() => !!token.value && !!user.value)
const avatarUrl = computed(() => user.value?.headerImg ?? '')
/** 钱包余额显示,如 "0.00",可从接口或 UserSocket 推送更新 */
const balance = ref<string>('0.00')
let userSdkRef: UserSdk | null = null
// 若从 storage 恢复登录态,自动连接 UserSocket
if (stored?.token && stored?.user) {
// 延迟到 nextTick 后连接,避免 store 未完全初始化
Promise.resolve().then(() => connectUserSocket())
}
function connectUserSocket() {
if (!token.value) return
disconnectUserSocket()
const sdk = new UserSdk({
url: getUserWsUrl(),
token: token.value,
autoReconnect: true,
reconnectInterval: 2000,
})
sdk.onBalanceUpdate((data: BalanceData) => {
const avail = data.available ?? data.amount
if (avail != null) {
balance.value = formatUsdcBalance(String(avail))
}
})
sdk.onConnect(() => {
console.log('[UserStore] UserSocket 已连接')
})
sdk.onDisconnect(() => {
console.log('[UserStore] UserSocket 已断开')
})
sdk.onError((e) => {
console.error('[UserStore] UserSocket 错误:', e)
})
userSdkRef = sdk
sdk.connect()
}
function disconnectUserSocket() {
if (userSdkRef) {
userSdkRef.disconnect()
userSdkRef = null
}
}
function setUser(loginData: { token?: string; user?: UserInfo }) {
const t = loginData.token ?? ''
const raw = loginData.user ?? null
token.value = t
if (raw) {
const rawId = raw.ID ?? raw.id
const numId =
typeof rawId === 'number'
? rawId
: rawId != null
? parseInt(String(rawId), 10)
: undefined
user.value = {
...raw,
id: rawId ?? numId,
ID: Number.isFinite(numId) ? numId : raw.ID,
}
} else {
user.value = null
}
if (t && user.value) {
saveToStorage(t, user.value)
connectUserSocket()
} else {
clearStorage()
disconnectUserSocket()
}
}
function logout() {
disconnectUserSocket()
token.value = ''
user.value = null
clearStorage()
}
/** 鉴权请求头x-token 与 x-user-id未登录时返回 undefined */
function getAuthHeaders(): Record<string, string> | undefined {
if (!token.value) return undefined
const uid = user.value?.id ?? user.value?.ID
return {
'x-token': token.value,
...(uid != null && uid !== '' && { 'x-user-id': String(uid) }),
}
}
/** 请求 USDC 余额需已登录amount/available 除以 1000000 后更新余额显示 */
async function fetchUsdcBalance() {
const headers = getAuthHeaders()
if (!headers) return
try {
const res = await getUsdcBalance(headers)
if (res.code === 0 && res.data) {
balance.value = formatUsdcBalance(res.data.available)
}
} catch (e) {
console.error('[fetchUsdcBalance] 请求失败:', e)
}
}
/** 请求用户信息(需已登录),更新 store 中的 user */
async function fetchUserInfo() {
const headers = getAuthHeaders()
if (!headers) return
try {
const res = await getUserInfo(headers)
console.log('[fetchUserInfo] 接口响应:', JSON.stringify(res, null, 2))
const data = res.data as Record<string, unknown> | undefined
// 接口返回 data.userInfo 或 data.user取实际用户对象若仍含 userInfo 则再取一层
let u = (data?.userInfo ?? data?.user ?? data) as Record<string, unknown>
if (u?.userInfo && (u.ID == null && u.id == null)) {
u = u.userInfo as Record<string, unknown>
}
if ((res.code === 0 || res.code === 200) && u) {
const rawId = u.ID ?? u.id
const numId =
typeof rawId === 'number'
? rawId
: rawId != null
? parseInt(String(rawId), 10)
: undefined
user.value = {
...u,
userName: (u.userName ?? u.username) as string | undefined,
nickName: (u.nickName ?? u.nickname) as string | undefined,
headerImg: (u.headerImg ?? u.avatar ?? u.avatarUrl) as string | undefined,
id: (rawId ?? numId) as number | string | undefined,
ID: Number.isFinite(numId) ? numId : undefined,
} as UserInfo
if (token.value && user.value) saveToStorage(token.value, user.value)
}
} catch (e) {
console.error('[fetchUserInfo] 请求失败:', e)
}
}
return {
token,
user,
isLoggedIn,
avatarUrl,
balance,
setUser,
logout,
getAuthHeaders,
fetchUsdcBalance,
fetchUserInfo,
connectUserSocket,
disconnectUserSocket,
}
})