feat: 完善个人中心
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
:class="{ 'profile-menu__item--admin': item.isAdmin }"
|
||||
hover-class="profile-menu__item--hover"
|
||||
hover-stay-time="150"
|
||||
@tap="navigate(item.path!)"
|
||||
@tap="handleTap(item)"
|
||||
>
|
||||
<view class="profile-menu__icon-wrap" :class="{ 'profile-menu__icon-wrap--admin': item.isAdmin }">
|
||||
<text class="profile-menu__icon">{{ item.icon }}</text>
|
||||
@@ -19,6 +19,7 @@
|
||||
<text class="profile-menu__title" :class="{ 'profile-menu__title--admin': item.isAdmin }">
|
||||
{{ item.title }}
|
||||
</text>
|
||||
<text v-if="item.badge" class="profile-menu__badge">{{ item.badge }}</text>
|
||||
<text class="profile-menu__arrow">›</text>
|
||||
</view>
|
||||
</template>
|
||||
@@ -35,10 +36,20 @@ interface MenuItem {
|
||||
title?: string
|
||||
path?: string
|
||||
isAdmin?: boolean
|
||||
badge?: string
|
||||
action?: 'clear' | 'about'
|
||||
requireAuth?: boolean
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
isAdmin: boolean
|
||||
requireAuth?: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'clear-cache'): void
|
||||
(e: 'about'): void
|
||||
(e: 'require-login'): void
|
||||
}>()
|
||||
|
||||
const menuItems = computed<MenuItem[]>(() => {
|
||||
@@ -49,6 +60,7 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
icon: '💳',
|
||||
title: '我的会员卡',
|
||||
path: '/pages/profile/membership',
|
||||
requireAuth: true,
|
||||
},
|
||||
{
|
||||
key: 'bookings',
|
||||
@@ -56,6 +68,7 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
icon: '📅',
|
||||
title: '我的预约',
|
||||
path: '/pages/profile/bookings',
|
||||
requireAuth: true,
|
||||
},
|
||||
{
|
||||
key: 'info',
|
||||
@@ -63,11 +76,30 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
icon: '👤',
|
||||
title: '个人信息',
|
||||
path: '/pages/profile/info',
|
||||
requireAuth: true,
|
||||
},
|
||||
{
|
||||
key: 'sep1',
|
||||
type: 'separator',
|
||||
},
|
||||
{
|
||||
key: 'clear',
|
||||
type: 'item',
|
||||
icon: '🗑️',
|
||||
title: '清除缓存',
|
||||
action: 'clear',
|
||||
},
|
||||
{
|
||||
key: 'about',
|
||||
type: 'item',
|
||||
icon: 'ℹ️',
|
||||
title: '关于我们',
|
||||
action: 'about',
|
||||
},
|
||||
]
|
||||
|
||||
if (props.isAdmin) {
|
||||
items.push({ key: 'sep', type: 'separator' })
|
||||
items.push({ key: 'sep2', type: 'separator' })
|
||||
items.push({
|
||||
key: 'admin',
|
||||
type: 'item',
|
||||
@@ -75,14 +107,25 @@ const menuItems = computed<MenuItem[]>(() => {
|
||||
title: '管理中心',
|
||||
path: '/pages/admin/index',
|
||||
isAdmin: true,
|
||||
requireAuth: true,
|
||||
})
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
function navigate(path: string) {
|
||||
uni.navigateTo({ url: path })
|
||||
function handleTap(item: MenuItem) {
|
||||
if (item.requireAuth && !props.requireAuth) {
|
||||
emit('require-login')
|
||||
return
|
||||
}
|
||||
if (item.action === 'clear') {
|
||||
emit('clear-cache')
|
||||
} else if (item.action === 'about') {
|
||||
emit('about')
|
||||
} else if (item.path) {
|
||||
uni.navigateTo({ url: item.path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -90,7 +133,7 @@ function navigate(path: string) {
|
||||
.profile-menu {
|
||||
background: $bg-card;
|
||||
border-radius: $radius-lg;
|
||||
margin: $spacing-md $spacing-lg 0;
|
||||
margin: $spacing-lg $spacing-lg 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__separator {
|
||||
@@ -111,11 +154,11 @@ function navigate(path: string) {
|
||||
}
|
||||
|
||||
&--hover {
|
||||
background: #f9f9f9;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
&--admin {
|
||||
// Admin row gets a subtle accent tint
|
||||
background: rgba($accent-color, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +166,7 @@ function navigate(path: string) {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: $radius-sm;
|
||||
background: rgba($brand-color, 0.06);
|
||||
background: rgba($brand-color, 0.08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -144,7 +187,6 @@ function navigate(path: string) {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
color: $text-primary;
|
||||
font-weight: 400;
|
||||
|
||||
&--admin {
|
||||
color: $accent-color;
|
||||
@@ -152,6 +194,15 @@ function navigate(path: string) {
|
||||
}
|
||||
}
|
||||
|
||||
&__badge {
|
||||
font-size: 22rpx;
|
||||
color: #ffffff;
|
||||
background: $error-color;
|
||||
border-radius: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
margin-right: $spacing-sm;
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
font-size: 36rpx;
|
||||
color: $text-hint;
|
||||
|
||||
@@ -1,21 +1,10 @@
|
||||
<template>
|
||||
<view class="studio-info">
|
||||
<!-- Horizontal photo strip -->
|
||||
<scroll-view
|
||||
v-if="studioInfo?.photos?.length"
|
||||
scroll-x
|
||||
class="photo-strip"
|
||||
:show-scrollbar="false"
|
||||
>
|
||||
<scroll-view v-if="studioInfo?.photos?.length" scroll-x class="photo-strip" :show-scrollbar="false">
|
||||
<view class="photo-strip-inner">
|
||||
<image
|
||||
v-for="(photo, idx) in studioInfo.photos"
|
||||
:key="idx"
|
||||
class="strip-photo"
|
||||
:src="photo"
|
||||
mode="aspectFill"
|
||||
@tap="previewPhoto(idx)"
|
||||
/>
|
||||
<image v-for="(photo, idx) in studioInfo.photos" :key="idx" class="strip-photo" :src="photo" mode="aspectFill"
|
||||
@tap="previewPhoto(idx)" />
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
@@ -24,7 +13,7 @@
|
||||
<view class="location-left" @tap="handleAddressTap">
|
||||
<text class="location-icon">📍</text>
|
||||
<text class="location-text">
|
||||
{{ studioInfo?.address || '地址加载中…' }}
|
||||
{{ studioInfo?.address || '深圳市宝安区西乡街道财富港 D 座 1203D' }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="phone-btn" @tap="handlePhoneTap">
|
||||
@@ -129,7 +118,7 @@ function handlePhoneTap() {
|
||||
|
||||
.location-left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@@ -138,7 +127,6 @@ function handlePhoneTap() {
|
||||
.location-icon {
|
||||
font-size: 28rpx;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.location-text {
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
<template>
|
||||
<view class="training-stats">
|
||||
<view class="training-stats__item">
|
||||
<text class="training-stats__value">{{ stats?.monthBookings ?? 0 }}</text>
|
||||
<text class="training-stats__unit">次</text>
|
||||
<text class="training-stats__label">本月训练</text>
|
||||
</view>
|
||||
|
||||
<view class="training-stats__divider" />
|
||||
|
||||
<view class="training-stats__item">
|
||||
<text class="training-stats__value">{{ stats?.monthDays ?? 0 }}</text>
|
||||
<text class="training-stats__unit">天</text>
|
||||
<text class="training-stats__label">训练天数</text>
|
||||
</view>
|
||||
|
||||
<view class="training-stats__divider" />
|
||||
|
||||
<view class="training-stats__item">
|
||||
<text class="training-stats__value">{{ stats?.monthHours ?? 0 }}</text>
|
||||
<text class="training-stats__unit">小时</text>
|
||||
<text class="training-stats__label">训练时长</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { UserStatsResponse } from '@mp-pilates/shared'
|
||||
|
||||
defineProps<{
|
||||
stats: UserStatsResponse | null
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.training-stats {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: $bg-card;
|
||||
border-radius: $radius-lg;
|
||||
margin: 0 $spacing-lg;
|
||||
padding: $spacing-md 0;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
|
||||
|
||||
// Pull card up to overlap the dark header
|
||||
margin-top: -$spacing-xl;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&__item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-sm 0;
|
||||
}
|
||||
|
||||
&__divider {
|
||||
width: 1rpx;
|
||||
background: $border-color;
|
||||
margin: $spacing-xs 0;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 48rpx;
|
||||
font-weight: 700;
|
||||
color: $brand-color;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__unit {
|
||||
font-size: 22rpx;
|
||||
color: $text-secondary;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 24rpx;
|
||||
color: $text-hint;
|
||||
margin-top: $spacing-xs;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,33 +1,58 @@
|
||||
<template>
|
||||
<view class="user-card">
|
||||
<!-- Not logged in state -->
|
||||
<view v-if="!loggedIn" class="user-card__guest">
|
||||
<view class="user-card__guest-avatar">
|
||||
<image class="user-card__avatar-img" src="/static/default-avatar.png" mode="aspectFill" />
|
||||
<!-- Header: gradient background -->
|
||||
<view class="user-card__header">
|
||||
<!-- Not logged in state -->
|
||||
<view v-if="!loggedIn" class="user-card__guest">
|
||||
<view class="user-card__avatar-wrap">
|
||||
<image class="user-card__avatar-img" src="/static/default-avatar.png" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="user-card__guest-info">
|
||||
<text class="user-card__guest-title">Hi,欢迎来到普拉提</text>
|
||||
<text class="user-card__guest-sub">登录后查看个人数据</text>
|
||||
</view>
|
||||
<button class="user-card__login-btn" :loading="loading" @tap="handleLogin">
|
||||
微信登录
|
||||
</button>
|
||||
</view>
|
||||
<view class="user-card__guest-info">
|
||||
<text class="user-card__guest-title">Hi,欢迎来到普拉提</text>
|
||||
<text class="user-card__guest-sub">登录后查看个人数据</text>
|
||||
|
||||
<!-- Logged in state -->
|
||||
<view v-else class="user-card__user">
|
||||
<view class="user-card__avatar-wrap">
|
||||
<image
|
||||
class="user-card__avatar-img"
|
||||
:src="avatarSrc"
|
||||
mode="aspectFill"
|
||||
@error="onAvatarError"
|
||||
/>
|
||||
<view class="user-card__vip-badge" v-if="vipLevel">
|
||||
<text class="user-card__vip-text">{{ vipLevel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="user-card__info">
|
||||
<view class="user-card__name-row">
|
||||
<text class="user-card__nickname">{{ user!.nickname }}</text>
|
||||
</view>
|
||||
<text v-if="maskedPhone" class="user-card__phone">{{ maskedPhone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<button class="user-card__login-btn" :loading="loading" @tap="handleLogin">
|
||||
微信登录
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- Logged in state -->
|
||||
<view v-else class="user-card__user">
|
||||
<view class="user-card__avatar-wrap">
|
||||
<image
|
||||
class="user-card__avatar-img"
|
||||
:src="avatarSrc"
|
||||
mode="aspectFill"
|
||||
@error="onAvatarError"
|
||||
/>
|
||||
<!-- Stats row: shown only when logged in -->
|
||||
<view v-if="loggedIn" 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>
|
||||
</view>
|
||||
<view class="user-card__info">
|
||||
<text class="user-card__nickname">{{ user!.nickname }}</text>
|
||||
<text v-if="maskedPhone" class="user-card__phone">{{ maskedPhone }}</text>
|
||||
<text class="user-card__joined">注册于 {{ joinedDate }}</text>
|
||||
<view class="user-card__stat-divider" />
|
||||
<view class="user-card__stat-item">
|
||||
<text class="user-card__stat-value">{{ stats?.monthBookings ?? 0 }}</text>
|
||||
<text class="user-card__stat-label">本月(次)</text>
|
||||
</view>
|
||||
<view class="user-card__stat-divider" />
|
||||
<view class="user-card__stat-item">
|
||||
<text class="user-card__stat-value">{{ remainingSessions }}</text>
|
||||
<text class="user-card__stat-label">剩余课时(节)</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -35,12 +60,14 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import type { UserProfileResponse } from '@mp-pilates/shared'
|
||||
import { formatDate } from '../utils/format'
|
||||
import type { UserProfileResponse, UserStatsResponse, MembershipWithCardType } from '@mp-pilates/shared'
|
||||
import { MembershipStatus } from '@mp-pilates/shared'
|
||||
|
||||
const props = defineProps<{
|
||||
loggedIn: boolean
|
||||
user: UserProfileResponse | null
|
||||
stats: UserStatsResponse | null
|
||||
memberships?: readonly MembershipWithCardType[]
|
||||
loading?: boolean
|
||||
}>()
|
||||
|
||||
@@ -60,15 +87,29 @@ const avatarSrc = computed(() => {
|
||||
const maskedPhone = computed(() => {
|
||||
const phone = props.user?.phone
|
||||
if (!phone) return null
|
||||
// Mask middle 4 digits: 138****8888
|
||||
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2')
|
||||
})
|
||||
|
||||
const joinedDate = computed(() => {
|
||||
if (!props.user?.createdAt) return ''
|
||||
return formatDate(props.user.createdAt)
|
||||
// Derive VIP level from active memberships count
|
||||
const activeMemberships = computed(() =>
|
||||
props.memberships?.filter((m) => m.status === MembershipStatus.ACTIVE) ?? [],
|
||||
)
|
||||
|
||||
const vipLevel = computed(() => {
|
||||
const count = activeMemberships.value.length
|
||||
if (count >= 3) return 'VIP3'
|
||||
if (count >= 2) return 'VIP2'
|
||||
if (count >= 1) return 'VIP1'
|
||||
return null
|
||||
})
|
||||
|
||||
// Sum remaining sessions from all active time-based memberships
|
||||
const remainingSessions = computed(() =>
|
||||
activeMemberships.value
|
||||
.filter((m) => m.cardType.type === 'TIMES')
|
||||
.reduce((sum, m) => sum + m.remainingCount, 0),
|
||||
)
|
||||
|
||||
function onAvatarError() {
|
||||
avatarFailed.value = true
|
||||
}
|
||||
@@ -80,19 +121,21 @@ function handleLogin() {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-card {
|
||||
background: $brand-color;
|
||||
padding: 80rpx $spacing-lg $spacing-xl;
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #a855f7 50%, #ec4899 100%);
|
||||
border-radius: 0 0 40rpx 40rpx;
|
||||
overflow: hidden;
|
||||
|
||||
&__header {
|
||||
padding: 60rpx $spacing-lg $spacing-lg;
|
||||
}
|
||||
|
||||
// ── Guest state ──
|
||||
&__guest {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
&__guest-avatar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__guest-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
@@ -108,12 +151,12 @@ function handleLogin() {
|
||||
|
||||
&__guest-sub {
|
||||
font-size: 24rpx;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
|
||||
&__login-btn {
|
||||
flex-shrink: 0;
|
||||
background: $accent-color;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
color: #ffffff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 500;
|
||||
@@ -129,6 +172,7 @@ function handleLogin() {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Logged-in user ──
|
||||
&__user {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -136,18 +180,35 @@ function handleLogin() {
|
||||
}
|
||||
|
||||
&__avatar-wrap {
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
&__avatar-img {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 4rpx solid rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
&__vip-badge {
|
||||
position: absolute;
|
||||
bottom: -6rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, #fbbf24, #f59e0b);
|
||||
border-radius: 20rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
border: 2rpx solid #ffffff;
|
||||
}
|
||||
|
||||
&__vip-text {
|
||||
font-size: 18rpx;
|
||||
font-weight: 700;
|
||||
color: #7c2d12;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__info {
|
||||
@@ -157,6 +218,12 @@ function handleLogin() {
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
&__name-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
|
||||
&__nickname {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
@@ -168,9 +235,43 @@ function handleLogin() {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
&__joined {
|
||||
// ── Stats row ──
|
||||
&__stats {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
margin: 0 $spacing-lg $spacing-lg;
|
||||
border-radius: $radius-lg;
|
||||
padding: $spacing-md 0;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
&__stat-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-xs 0;
|
||||
}
|
||||
|
||||
&__stat-divider {
|
||||
width: 1rpx;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
margin: $spacing-xs 0;
|
||||
}
|
||||
|
||||
&__stat-value {
|
||||
font-size: 44rpx;
|
||||
font-weight: 700;
|
||||
color: #ffffff;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
&__stat-label {
|
||||
font-size: 22rpx;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,46 +1,51 @@
|
||||
<template>
|
||||
<view class="profile-page">
|
||||
<!-- User card: always visible -->
|
||||
<!-- User card -->
|
||||
<UserCard
|
||||
:logged-in="loggedIn"
|
||||
:user="user"
|
||||
:stats="stats"
|
||||
:memberships="memberships"
|
||||
:loading="loginLoading"
|
||||
@login="handleLogin"
|
||||
/>
|
||||
|
||||
<!-- Logged-in content -->
|
||||
<template v-if="loggedIn">
|
||||
<!-- Training stats: overlaps bottom of UserCard -->
|
||||
<TrainingStats :stats="stats" />
|
||||
<!-- Menu section: always visible -->
|
||||
<ProfileMenu
|
||||
:is-admin="isAdmin"
|
||||
:require-auth="loggedIn"
|
||||
@clear-cache="handleClearCache"
|
||||
@about="handleAbout"
|
||||
@require-login="handleLogin"
|
||||
/>
|
||||
|
||||
<!-- Menu section -->
|
||||
<ProfileMenu :is-admin="isAdmin" />
|
||||
|
||||
<!-- Logout button -->
|
||||
<view class="profile-page__logout-wrap">
|
||||
<button class="profile-page__logout-btn" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</template>
|
||||
<!-- Logout button: only when logged in -->
|
||||
<view v-if="loggedIn" class="profile-page__logout-wrap">
|
||||
<button class="profile-page__logout-btn" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useUserStore } from '../../stores/user'
|
||||
import UserCard from '../../components/UserCard.vue'
|
||||
import TrainingStats from '../../components/TrainingStats.vue'
|
||||
import ProfileMenu from '../../components/ProfileMenu.vue'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const { loggedIn, user, stats, isAdmin } = userStore
|
||||
const { loggedIn, user, stats, memberships, isAdmin } = storeToRefs(userStore)
|
||||
|
||||
const loginLoading = ref(false)
|
||||
|
||||
onShow(async () => {
|
||||
if (loggedIn) {
|
||||
await Promise.all([userStore.fetchProfile(), userStore.fetchStats()])
|
||||
if (loggedIn.value) {
|
||||
await Promise.all([
|
||||
userStore.fetchProfile(),
|
||||
userStore.fetchStats(),
|
||||
userStore.fetchMemberships(),
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
@@ -49,8 +54,11 @@ async function handleLogin() {
|
||||
loginLoading.value = true
|
||||
try {
|
||||
await userStore.login()
|
||||
// After login, fetch stats immediately
|
||||
await Promise.all([userStore.fetchProfile(), userStore.fetchStats()])
|
||||
await Promise.all([
|
||||
userStore.fetchProfile(),
|
||||
userStore.fetchStats(),
|
||||
userStore.fetchMemberships(),
|
||||
])
|
||||
} catch {
|
||||
uni.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
@@ -71,6 +79,27 @@ function handleLogout() {
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleClearCache() {
|
||||
uni.showModal({
|
||||
title: '清除缓存',
|
||||
content: '确定要清除本地缓存数据吗?',
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
uni.clearStorageSync()
|
||||
uni.showToast({ title: '缓存已清除', icon: 'success' })
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleAbout() {
|
||||
uni.showModal({
|
||||
title: '关于我们',
|
||||
content: 'Focus Core 普拉提工作室\n版本 1.0.0\n\n专注核心,遇见更好的自己',
|
||||
showCancel: false,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -78,11 +107,8 @@ function handleLogout() {
|
||||
min-height: 100vh;
|
||||
background: $bg-page;
|
||||
|
||||
// Content area below the dark header card
|
||||
// UserCard has its own dark bg, content sits on $bg-page
|
||||
|
||||
&__logout-wrap {
|
||||
margin: $spacing-xl $spacing-lg $spacing-lg;
|
||||
margin: $spacing-xl $spacing-lg $spacing-xl;
|
||||
}
|
||||
|
||||
&__logout-btn {
|
||||
|
||||
Reference in New Issue
Block a user