Files
gym-manage/gym-manage-uniapp/pages/memberInfo/memberInfo.vue
T

324 lines
10 KiB
Vue
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.
<template>
<view class="member-info-page">
<!-- 固定页面头部 -->
<view class="member-info-page-header">
<PageHeader title="个人中心" subtitle="" />
</view>
<!-- 滚动内容区域 -->
<view class="member-scroll-wrap">
<!-- 骨架屏 -->
<MemberCenterSkeleton v-if="!pageReady" />
<scroll-view v-else scroll-y class="member-scroll">
<view class="member-page">
<MemberInfoHeader
:user-info="userInfo"
:stats="stats"
:is-login="isLogin"
@user-info="goUserInfo"
@guest-login="handleGuestLogin"
/>
<view class="member-page__body">
<view class="member-page__sections">
<MemberInfoMemberCard
v-if="isLogin"
ref="memberCardRef"
:card-info="cardInfo"
@view-all="goMemberCard"
@renew="onRenewCard"
@purchase="goPurchaseCard"
@stored-card-tap="goRechargeStoredCard"
/>
<MemberInfoLogout v-if="isLogin" @logout="handleLogout" />
</view>
</view>
</view>
</scroll-view>
</view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar />
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
import {
getCenterPageData,
renewMemberCard,
saveMemberStore,
getDefaultStore,
loadMemberStore
} from '@/common/memberInfo/store.js'
import { getMemberId } from '@/utils/request.js'
import { login as miniappLogin, getUserInfo, getMemberDetail, getCheckInStats, getMemberBookings, updateUserInfo } from '@/api/main.js'
import { setToken, getToken, clearToken } from '@/utils/request.js'
import MemberInfoHeader from '@/components/memberInfo/MemberInfoHeader.vue'
import MemberInfoMemberCard from '@/components/memberInfo/MemberInfoMemberCard.vue'
import MemberInfoLogout from '@/components/memberInfo/MemberInfoLogout.vue'
import MemberCenterSkeleton from '@/components/Skeleton/MemberCenterSkeleton.vue'
import TabBar from '@/components/TabBar.vue'
import PageHeader from '@/components/index/PageHeader.vue'
const userInfo = ref({})
const stats = ref({})
const cardInfo = ref({})
const loading = ref(false)
const pageReady = ref(false)
const hasActiveCard = ref(false)
const isLogin = ref(false)
const memberCardRef = ref(null)
onMounted(() => {
const token = uni.getStorageSync('token')
const loginMember = uni.getStorageSync('loginMemberInfo')
if (token || loginMember) {
isLogin.value = true
}
})
// 处理访客登录点击(跳转到登录页面)
function handleGuestLogin() {
console.log('[memberInfo] 用户点击登录区域')
uni.navigateTo({
url: '/pages/memberInfo/login'
})
}
async function fetchMemberInfo() {
if (loading.value) return
loading.value = true
console.log('[memberInfo] fetchMemberInfo 开始执行')
try {
// 先从 loginMemberInfo 获取用户信息
const loginMember = uni.getStorageSync('loginMemberInfo')
if (loginMember?.userInfo) {
console.log('[memberInfo] 从loginMemberInfo获取用户信息:', loginMember.userInfo)
const apiData = loginMember.userInfo
// 更新userInfo
userInfo.value = {
id: apiData.id,
name: apiData.nickname || apiData.name,
phone: apiData.phone,
avatar: apiData.avatar,
memberLevel: apiData.memberLevel || '普通会员'
}
}
// 获取积分信息
try {
const detailRes = await getMemberDetail()
const detailData = detailRes.data || {}
stats.value = {
...stats.value,
pointsBalance: detailData.pointsBalance || 0
}
} catch (e) {
console.error('[memberInfo] 获取积分信息失败:', e)
}
// 获取签到统计
try {
const statsRes = await getCheckInStats()
console.log('[memberInfo] getCheckInStats 返回:', JSON.stringify(statsRes, null, 2))
const statsData = statsRes.data || {}
stats.value.checkInCount = statsData.totalCount || 0
} catch (e) {
console.error('[memberInfo] 获取签到统计失败:', e)
stats.value.checkInCount = 0
}
// 获取报课次数
try {
const memberIdForBooking = getMemberId()
console.log('[memberInfo] getMemberId 结果:', memberIdForBooking)
if (memberIdForBooking) {
const bookingsRes = await getMemberBookings(memberIdForBooking)
console.log('[memberInfo] getMemberBookings 原始响应:', JSON.stringify(bookingsRes))
// 兼容多种后端响应格式(数组 / data数组 / data.content / content / data.data.content
let bookings = []
const raw = bookingsRes || {}
if (Array.isArray(raw)) {
bookings = raw
} else if (Array.isArray(raw.data)) {
bookings = raw.data
} else if (raw.data && Array.isArray(raw.data.content)) {
bookings = raw.data.content
} else if (Array.isArray(raw.content)) {
bookings = raw.content
} else if (raw.data && typeof raw.data === 'object' && raw.data.data && Array.isArray(raw.data.data)) {
bookings = raw.data.data
} else if (raw.data && typeof raw.data === 'object' && raw.data.data && Array.isArray(raw.data.data.content)) {
bookings = raw.data.data.content
}
stats.value.courseCount = bookings.length
console.log('[memberInfo] 解析后 courseCount:', stats.value.courseCount)
} else {
stats.value.courseCount = 0
console.warn('[memberInfo] getMemberId 返回 null/undefined')
}
} catch (e) {
console.error('[memberInfo] 获取报课次数失败:', e)
stats.value.courseCount = 0
}
// 将获取到的数据保存到store,供其他页面(如memberCenter)使用
const store = loadMemberStore()
store.stats = {
...store.stats,
checkInCount: stats.value.checkInCount ?? 0,
courseCount: stats.value.courseCount ?? 0,
pointsBalance: stats.value.pointsBalance ?? 0
}
saveMemberStore(store)
// 刷新store中的业务数据
refreshFromStore()
pageReady.value = true
console.log('[memberInfo] fetchMemberInfo 执行完成')
} catch (err) {
console.error('[memberInfo] 获取会员信息失败:', err)
pageReady.value = true
} finally {
loading.value = false
}
}
function refreshFromStore() {
const store = loadMemberStore()
const pageData = getCenterPageData(store)
console.log('[memberInfo] refreshFromStore - pageData:', JSON.stringify(pageData))
stats.value = pageData.stats
cardInfo.value = pageData.cardInfo
console.log('[memberInfo] refreshFromStore - userInfo.value:', JSON.stringify(userInfo.value))
}
function goUserInfo() {
navigateToPage(PAGE.USER_INFO)
}
function goMemberCard() {
navigateToPage(PAGE.MEMBER_CARD)
}
function goPurchaseCard() {
navigateToPage(PAGE.PURCHASE_CARD)
}
function goRechargeStoredCard() {
navigateToPage(PAGE.RECHARGE_STORED_CARD)
}
function onRenewCard() {
uni.showModal({
title: '续费会员卡',
content: '确认续费 90 天?',
success: (res) => {
if (!res.confirm) return
uni.showToast({ title: '续费成功', icon: 'success' })
}
})
}
function handleLogout() {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
// 清除token
clearToken()
// 重置登录状态
isLogin.value = false
// 重置store为默认状态
const defaultStore = getDefaultStore()
saveMemberStore(defaultStore)
// 重置页面数据
userInfo.value = {}
stats.value = {}
cardInfo.value = {}
hasActiveCard.value = false
refreshFromStore()
uni.showToast({ title: '已退出', icon: 'success' })
}
}
})
}
refreshFromStore()
onShow(() => {
console.log('[memberInfo] onShow 被调用')
// 检查登录状态
const token = getToken()
const isLoginStorage = uni.getStorageSync('isLogin')
if (token || isLoginStorage) {
isLogin.value = true
} else {
isLogin.value = false
}
// 强制刷新数据(忽略 loading 防重入)
loading.value = false
fetchMemberInfo()
// 刷新子组件数据
if (memberCardRef.value && typeof memberCardRef.value.loadMemberCard === 'function') {
memberCardRef.value.loadMemberCard()
}
})
</script>
<style lang="scss">
@import '@/common/style/base.css';
@import '@/common/style/memberInfo/member-info-component-reset.css';
@import '@/common/style/memberInfo/member-info-tap.css';
@import '@/common/style/memberInfo/member-info-page.css';
@import '@/common/style/memberInfo/member-info-header.css';
@import '@/common/style/memberInfo/member-info-settings.css';
@import '@/common/style/memberInfo/member-info-logout.css';
/* 页面布局:scroll-view + tabbar */
.member-info-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
background: #F5F7FA;
}
/* 固定页面头部 */
.member-info-page-header {
flex-shrink: 0;
position: relative;
z-index: 10;
background: #F5F7FA;
}
.member-scroll-wrap {
flex: 1;
overflow: hidden;
}
.member-scroll {
height: 100%;
overflow-y: auto;
}
/* 固定 TabBar */
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
</style>