import { useInviteStore } from './invite' import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { UserProfileResponse, UserStatsResponse, MembershipWithCardType, } from '@mp-pilates/shared' import { UserRole, MembershipStatus, getMembershipRenewalHint, } from '@mp-pilates/shared' import { wxLogin, isLoggedIn, logout as authLogout } from '../utils/auth' import { setUnauthorizedHandler } from '../utils/session' import { get, put } from '../utils/request' import { ROUTES } from '../utils/routes' import { cacheSubscriptionMessageTemplateConfig, resetSubscriptionMessageTemplateCache } from '../utils/wechat-subscription' function syncSubscriptionTemplates(profile?: Pick | null) { cacheSubscriptionMessageTemplateConfig(profile?.subscriptionMessageTemplates) } export const useUserStore = defineStore('user', () => { // State const user = ref(null) const stats = ref(null) const memberships = ref([]) const membershipsLoading = ref(false) const membershipsLoaded = ref(false) const membershipsError = ref(false) const statsLoading = ref(false) const statsError = ref(false) let membershipRequestId = 0 let statsRequestId = 0 const token = ref(uni.getStorageSync('token') as string || '') // Getters // loggedIn: 是否已认证(token 存在),与 user 数据是否已加载无关 const loggedIn = computed(() => !!token.value) // hasProfile: user 数据是否已加载(用于 UI 展示判断) const hasProfile = computed(() => !!user.value) const isAdmin = computed(() => user.value?.role === UserRole.ADMIN) const activeMemberships = computed(() => memberships.value.filter((m) => m.status === MembershipStatus.ACTIVE), ) const hasValidMembership = computed(() => activeMemberships.value.length > 0) const renewalHint = computed(() => getMembershipRenewalHint(memberships.value)) const inviteShareEligible = computed(() => !!user.value?.inviteShareEligible) // Actions async function login() { try { const result = await wxLogin() token.value = result.token user.value = result.user await useInviteStore().refresh().catch(() => {}) syncSubscriptionTemplates(result.user) return { user: result.user, isNewUser: result.isNewUser } } catch (err) { console.error('Login failed:', err) throw err } } /** * Login, redirect new users to complete profile, and fetch post-login data. * Returns isNewUser so callers know whether navigation was intercepted. */ async function loginWithSetup(): Promise<{ isNewUser: boolean }> { const { isNewUser } = await login() if (isNewUser) { uni.navigateTo({ url: ROUTES.PROFILE_INFO_FIRST_LOGIN }) return { isNewUser: true } } await Promise.all([fetchProfile(), fetchMemberships()]) return { isNewUser: false } } async function fetchProfile() { if (!isLoggedIn()) return try { user.value = await get('/user/profile') await useInviteStore().refresh().catch(() => {}) syncSubscriptionTemplates(user.value) return user.value } catch (err) { console.error('Fetch profile failed:', err) } } async function fetchStats(): Promise { if (!isLoggedIn()) return false const id = ++statsRequestId const session = token.value statsLoading.value = true statsError.value = false try { const result = await get('/user/stats') if (id !== statsRequestId || session !== token.value) return false stats.value = result return true } catch { if (id === statsRequestId && session === token.value) statsError.value = true return false } finally { if (id === statsRequestId && session === token.value) statsLoading.value = false } } async function fetchMemberships(): Promise { if (!isLoggedIn()) return false const id = ++membershipRequestId const session = token.value membershipsLoading.value = true membershipsError.value = false try { const result = await get('/membership/my') if (id !== membershipRequestId || session !== token.value) return false memberships.value = [...result] membershipsLoaded.value = true return true } catch { if (id === membershipRequestId && session === token.value) membershipsError.value = true return false } finally { if (id === membershipRequestId && session === token.value) membershipsLoading.value = false } } async function updateProfile(data: { nickname?: string; avatarUrl?: string }) { const updated = await put('/user/profile', data) user.value = updated syncSubscriptionTemplates(updated) return updated } function setProfile(profile: UserProfileResponse) { user.value = profile syncSubscriptionTemplates(profile) } function checkAuth() { if (isLoggedIn()) { fetchProfile() fetchMemberships() } } function clearSession() { membershipRequestId++ statsRequestId++ membershipsLoading.value = false membershipsLoaded.value = false membershipsError.value = false statsLoading.value = false statsError.value = false token.value = '' useInviteStore().reset() user.value = null stats.value = null memberships.value = [] resetSubscriptionMessageTemplateCache() } function logout() { authLogout() clearSession() } setUnauthorizedHandler(clearSession) return { membershipsLoading, membershipsLoaded, membershipsError, statsLoading, statsError, user, stats, memberships, token, loggedIn, hasProfile, isAdmin, activeMemberships, hasValidMembership, renewalHint, inviteShareEligible, login, loginWithSetup, fetchProfile, fetchStats, fetchMemberships, updateProfile, setProfile, checkAuth, logout, } })