284 lines
9.1 KiB
Vue
284 lines
9.1 KiB
Vue
<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">
|
||
<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,
|
||
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 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)
|
||
|
||
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 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()
|
||
})
|
||
</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>
|