Files
mp-pilates/packages/app/src/stores/user.ts
richarjiang 88cd8419c8 feat: 支持同卡种续卡叠加并修复过期登录态
支付成功后将次数或有效期叠加到已有会员卡,首页与详情页引导续卡;接口 401 时同步清掉本地登录态。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 14:04:11 +08:00

157 lines
4.4 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 { 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<UserProfileResponse, 'subscriptionMessageTemplates'> | null) {
cacheSubscriptionMessageTemplateConfig(profile?.subscriptionMessageTemplates)
}
export const useUserStore = defineStore('user', () => {
// State
const user = ref<UserProfileResponse | null>(null)
const stats = ref<UserStatsResponse | null>(null)
const memberships = ref<readonly MembershipWithCardType[]>([])
const token = ref<string>(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<UserProfileResponse>('/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<UserStatsResponse>('/user/stats')
} catch (err) {
console.error('Fetch stats failed:', err)
}
}
async function fetchMemberships() {
if (!isLoggedIn()) return
try {
memberships.value = await get<MembershipWithCardType[]>('/membership/my')
} catch (err) {
console.error('Fetch memberships failed:', err)
}
}
async function updateProfile(data: { nickname?: string; avatarUrl?: string }) {
const updated = await put<UserProfileResponse>('/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,
}
})