feat: 新用户默认昵称及个人中心加载状态修复
- 新用户注册时随机生成普拉提主题默认昵称(16 个可选) - 修复 App 启动后个人中心首次进入不展示用户信息的 bug - loggedIn 改为仅依赖 token 是否存在 - 新增 hasProfile 判断用户数据是否已加载 - 未加载时显示骨架屏而非空白 - 抽离随机函数为可注入依赖,消除 Math.random 测试耦合
This commit is contained in:
@@ -16,8 +16,8 @@
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- Logged in state -->
|
||||
<view v-else class="user-card__user">
|
||||
<!-- Logged in + profile loaded -->
|
||||
<view v-else-if="loggedIn && hasProfile" class="user-card__user">
|
||||
<view class="user-card__avatar-wrap">
|
||||
<image
|
||||
class="user-card__avatar-img"
|
||||
@@ -36,10 +36,23 @@
|
||||
<text v-if="maskedPhone" class="user-card__phone">{{ maskedPhone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Logged in but profile still loading -->
|
||||
<view v-else class="user-card__loading">
|
||||
<view class="user-card__avatar-wrap">
|
||||
<view class="user-card__avatar-skeleton" />
|
||||
</view>
|
||||
<view class="user-card__info">
|
||||
<view class="user-card__name-row">
|
||||
<view class="user-card__nickname-skeleton" />
|
||||
</view>
|
||||
<view class="user-card__phone-skeleton" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- Stats row: shown only when logged in -->
|
||||
<view v-if="loggedIn" class="user-card__stats">
|
||||
<!-- Stats row: shown only when profile is loaded -->
|
||||
<view v-if="loggedIn && hasProfile" class="user-card__stats">
|
||||
<view class="user-card__stat-item">
|
||||
<text class="user-card__stat-value">{{ stats?.totalBookings ?? 0 }}</text>
|
||||
<text class="user-card__stat-label">总训练(次)</text>
|
||||
@@ -65,6 +78,7 @@ import { MembershipStatus } from '@mp-pilates/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
loggedIn: boolean
|
||||
hasProfile: boolean
|
||||
user: UserProfileResponse | null
|
||||
stats: UserStatsResponse | null
|
||||
memberships?: readonly MembershipWithCardType[]
|
||||
@@ -235,6 +249,35 @@ function handleLogin() {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
// ── Loading state ──
|
||||
&__loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
&__avatar-skeleton {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&__nickname-skeleton {
|
||||
width: 160rpx;
|
||||
height: 36rpx;
|
||||
border-radius: $radius-sm;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&__phone-skeleton {
|
||||
width: 120rpx;
|
||||
height: 26rpx;
|
||||
border-radius: $radius-sm;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
// ── Stats row ──
|
||||
&__stats {
|
||||
display: flex;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<!-- User card -->
|
||||
<UserCard
|
||||
:logged-in="loggedIn"
|
||||
:has-profile="hasProfile"
|
||||
:user="user"
|
||||
:stats="stats"
|
||||
:memberships="memberships"
|
||||
@@ -35,7 +36,7 @@ import UserCard from '../../components/UserCard.vue'
|
||||
import ProfileMenu from '../../components/ProfileMenu.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { loggedIn, user, stats, memberships, isAdmin } = storeToRefs(userStore)
|
||||
const { loggedIn, hasProfile, user, stats, memberships, isAdmin } = storeToRefs(userStore)
|
||||
|
||||
const loginLoading = ref(false)
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ export const useUserStore = defineStore('user', () => {
|
||||
const token = ref<string>(uni.getStorageSync('token') as string || '')
|
||||
|
||||
// Getters
|
||||
const loggedIn = computed(() => !!token.value && !!user.value)
|
||||
// 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),
|
||||
@@ -91,6 +94,7 @@ export const useUserStore = defineStore('user', () => {
|
||||
memberships,
|
||||
token,
|
||||
loggedIn,
|
||||
hasProfile,
|
||||
isAdmin,
|
||||
activeMemberships,
|
||||
hasValidMembership,
|
||||
|
||||
Reference in New Issue
Block a user