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 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 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') syncSubscriptionTemplates(user.value) return user.value } catch (err) { console.error('Fetch profile failed:', err) } } async function fetchStats() { if (!isLoggedIn()) return try { stats.value = await get('/user/stats') } catch (err) { console.error('Fetch stats failed:', err) } } async function fetchMemberships() { if (!isLoggedIn()) return try { memberships.value = await get('/membership/my') } catch (err) { console.error('Fetch memberships failed:', err) } } 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() { token.value = '' user.value = null stats.value = null memberships.value = [] resetSubscriptionMessageTemplateCache() } function logout() { authLogout() clearSession() } setUnauthorizedHandler(clearSession) return { user, stats, memberships, token, loggedIn, hasProfile, isAdmin, activeMemberships, hasValidMembership, renewalHint, inviteShareEligible, login, loginWithSetup, fetchProfile, fetchStats, fetchMemberships, updateProfile, setProfile, checkAuth, logout, } })