Files
gym-manage/gym-manage-uniapp/pages/memberInfo/memberCenter.vue
T
2026-06-28 14:49:21 +08:00

990 lines
51 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>
<scroll-view scroll-y class="scroll-container theme-light">
<view class="member-page">
<!-- Header: 用户信息区 -->
<view class="profile-header">
<view class="profile-header__toolbar" :style="toolbarStyle">
<view class="profile-header__nav">
<view class="profile-header__nav-left">
<image
class="profile-header__icon-bell"
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/bell.png"
mode="aspectFit"
/>
<image
class="profile-header__icon-settings"
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/settings.png"
mode="aspectFit"
/>
</view>
<text class="profile-header__title">个人中心</text>
<view class="profile-header__nav-right" :style="navRightStyle"></view>
</view>
</view>
<view class="profile-header__toolbar-spacer" :style="toolbarSpacerStyle"></view>
<view class="profile-header__hero">
<view class="profile-header__inner">
<view class="profile-header__user" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goUserInfo">
<view class="profile-header__user-inner">
<view class="profile-header__avatar-wrap">
<view class="profile-header__avatar-ring">
<image
class="profile-header__avatar"
:key="userInfo.avatar"
:src="displayAvatar"
mode="aspectFill"
/>
</view>
<view class="profile-header__avatar-badge">
<image
class="profile-header__avatar-badge-icon"
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/camera.png"
mode="aspectFit"
/>
</view>
</view>
<view class="profile-header__user-meta">
<view class="profile-header__user-meta-inner">
<text class="profile-header__name">{{ userInfo.name }}</text>
<text class="profile-header__phone">{{ userInfo.phone }}</text>
<view class="profile-header__badge">
<image
class="profile-header__badge-icon"
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/crown0.png"
mode="aspectFit"
/>
<text class="profile-header__level">{{ userInfo.memberLevel }}</text>
</view>
</view>
</view>
</view>
</view>
<view class="profile-header__stats">
<view class="profile-header__stats-inner">
<view class="profile-header__stat">
<view class="profile-header__stat-inner">
<text class="profile-header__stat-value">{{ stats.checkInCount }}</text>
<text class="profile-header__stat-label">累计签到</text>
</view>
</view>
<view class="profile-header__stat-divider"></view>
<view class="profile-header__stat">
<view class="profile-header__stat-inner">
<text class="profile-header__stat-value">{{ stats.trainingHours }}</text>
<text class="profile-header__stat-label">训练时长</text>
</view>
</view>
<view class="profile-header__stat-divider"></view>
<view class="profile-header__stat">
<view class="profile-header__stat-inner">
<text class="profile-header__stat-value">{{ stats.pointsBalance }}</text>
<text class="profile-header__stat-label">累计积分</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="member-page__body">
<view class="member-page__sections">
<!-- 快过期会员卡提醒 -->
<view class="expiring-cards-section" v-if="expiringCards.length > 0">
<view class="expiring-cards__inner">
<view class="expiring-cards__header">
<view class="expiring-cards__header-inner">
<view class="expiring-cards__title-row">
<image class="expiring-cards__warn-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/clock1.png" mode="aspectFit" />
<text class="expiring-cards__title">会员卡即将到期</text>
</view>
<view class="expiring-cards__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goMemberCard">
<text class="expiring-cards__link-text">查看全部</text>
<image class="expiring-cards__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright12.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="expiring-cards__list">
<view
v-for="(card, index) in expiringCards"
:key="card.id || index"
class="expiring-card-item"
hover-class="mi-tap-row--hover"
:hover-stay-time="150"
@tap="goMemberCard"
>
<view class="expiring-card-item__inner">
<view class="expiring-card-item__icon-wrap">
<image class="expiring-card-item__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/ticket.png" mode="aspectFit" />
</view>
<view class="expiring-card-item__info">
<text class="expiring-card-item__name">{{ card.name }}</text>
<text class="expiring-card-item__validity">{{ card.validity }}</text>
</view>
<view class="expiring-card-item__days">
<text class="expiring-card-item__days-num">{{ computeRemainingDays(card.validityEnd) }}</text>
<text class="expiring-card-item__days-unit">天后到期</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 会员卡区域 -->
<view class="member-card-section">
<view class="member-card-section__inner">
<view class="member-card-section__head">
<view class="member-card-section__head-inner">
<text class="member-card-section__title">我的会员卡</text>
<view class="member-card-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goMemberCard">
<text class="member-card-section__link-text">查看全部</text>
<image class="member-card-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright12.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="member-card-preview" hover-class="mi-tap-card--hover" :hover-stay-time="150" @tap="goMemberCard">
<view class="member-card-preview__inner">
<view class="member-card-preview__head">
<view class="member-card-preview__head-inner">
<view class="member-card-preview__type-row">
<view class="member-card-preview__icon-wrap">
<view class="member-card-preview__icon-border">
<view class="member-card-preview__icon-bg"></view>
<view class="member-card-preview__icon-stroke"></view>
</view>
<image class="member-card-preview__icon-line" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Line_2_468.png" mode="aspectFill" />
</view>
<text class="member-card-preview__name">{{ cardInfo.name }}</text>
</view>
<view class="member-card-preview__tag">
<text class="member-card-preview__tag-text">{{ cardInfo.detailTag || '详情' }}</text>
</view>
</view>
</view>
<text class="member-card-preview__expire">{{ cardInfo.expireDate }}</text>
<view class="member-card-preview__footer">
<view class="member-card-preview__footer-inner">
<view class="member-card-preview__days">
<text class="member-card-preview__days-num">{{ cardInfo.remainingDays }}</text>
<text class="member-card-preview__days-unit">天剩余</text>
</view>
<view class="member-card-preview__renew" hover-class="mi-tap-btn--hover" :hover-stay-time="150" @tap.stop="onRenewCard">
<text class="member-card-preview__renew-text">续费</text>
</view>
</view>
</view>
</view>
</view>
<view class="member-card-tip">
<view class="member-card-tip__inner">
<view class="member-card-tip__content">
<image class="member-card-tip__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/clock1.png" mode="aspectFit" />
<text class="member-card-tip__text">{{ cardInfo.tip }}</text>
</view>
</view>
<view class="member-card-tip__border"></view>
</view>
</view>
</view>
<!-- 快捷操作区域 -->
<view class="quick-actions">
<view class="quick-actions__inner">
<view class="quick-actions__grid">
<view class="quick-actions__grid-inner">
<view v-for="item in quickActionsRow1" :key="item.key" class="quick-actions__item" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="onQuickAction(item.key)">
<view class="quick-actions__item-inner">
<view class="quick-actions__icon-wrap">
<view class="quick-actions__icon-wrap-inner">
<view v-if="item.key === 'booking'" class="quick-actions__icon">
<image class="quick-actions__icon-part" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Vector_2_490.png" mode="aspectFit" />
<image class="quick-actions__icon-part" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Vector_2_491.png" mode="aspectFit" />
<view class="quick-actions__border-wrap">
<view class="quick-actions__rect"></view>
<view class="quick-actions__border"></view>
</view>
<image class="quick-actions__icon-part" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Vector_2_493.png" mode="aspectFit" />
<image class="quick-actions__icon-part" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Vector_2_494.png" mode="aspectFit" />
</view>
<image v-else class="quick-actions__icon-img" :src="item.icon" mode="aspectFit" />
</view>
</view>
<text :class="item.textClass">{{ item.label }}</text>
</view>
</view>
</view>
</view>
<view class="quick-actions__divider"></view>
<view class="quick-actions__grid">
<view class="quick-actions__grid-inner">
<view v-for="item in quickActionsRow2" :key="item.key" class="quick-actions__item" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="onQuickAction(item.key)">
<view class="quick-actions__item-inner">
<view class="quick-actions__icon-wrap">
<view class="quick-actions__icon-wrap-inner">
<image class="quick-actions__icon-img" :src="item.icon" mode="aspectFit" />
</view>
</view>
<text :class="item.textClass">{{ item.label }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 预约课程区域 -->
<view class="booking-section">
<view class="booking-section__inner">
<view class="booking-section__header">
<view class="booking-section__header-inner">
<text class="booking-section__title">我的预约</text>
<view class="booking-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goBooking">
<text class="booking-section__view-all">预约记录</text>
<image class="booking-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright4.png" mode="aspectFit" />
</view>
</view>
</view>
<view v-for="item in bookingPreview" :key="item.id" class="booking-section__item" hover-class="mi-tap-row--hover" :hover-stay-time="150" @tap="goBooking">
<view class="booking-section__item-inner">
<view class="booking-section__date">
<view class="booking-section__date-inner">
<text class="booking-section__num">{{ item.dateDay }}</text>
<text class="booking-section__date-sub">{{ item.dateMonth }}</text>
</view>
</view>
<view class="booking-section__content">
<view class="booking-section__content-inner">
<text class="booking-section__desc">{{ item.desc }}</text>
<view class="booking-section__meta">
<view class="booking-section__meta-inner">
<image class="booking-section__icon-coach" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/user2.png" mode="aspectFit" />
<text class="booking-section__coach">教练{{ item.coach }}</text>
<image class="booking-section__icon-location" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/mappin1.png" mode="aspectFit" />
<text class="booking-section__text">{{ item.location }}</text>
</view>
</view>
</view>
</view>
<view class="booking-section__status-wrap">
<view class="booking-section__status-badge" :class="'booking-section__status-badge--' + item.status">
<text class="booking-section__status-text" :class="{ 'booking-section__status-text--pending': item.status === 'pending' }">{{ item.statusLabel }}</text>
</view>
</view>
</view>
</view>
<view v-if="!bookingPreview.length" class="booking-section__empty">
<text class="booking-section__empty-text">暂无进行中的预约</text>
</view>
</view>
</view>
<!-- 签到记录区域 -->
<view class="checkin-section">
<view class="checkin-section__inner">
<view class="checkin-section__header">
<view class="checkin-section__header-inner">
<text class="checkin-section__title">签到记录</text>
<view class="checkin-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goCheckInHistory">
<text class="checkin-section__view-all">查看全部</text>
<image class="checkin-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright2.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="checkin-section__list">
<view class="checkin-section__list-inner">
<view v-for="(item, index) in checkIns" :key="item.id" class="checkin-section__row">
<view v-if="index > 0" class="checkin-section__divider"></view>
<view class="checkin-section__item" hover-class="mi-tap-row--hover" :hover-stay-time="150" @tap="onCheckInTap(item)">
<view class="checkin-section__item-inner">
<view class="checkin-section__dot" :class="'checkin-section__dot--' + item.tagTheme"></view>
<view class="checkin-section__content">
<view class="checkin-section__content-inner">
<text class="checkin-section__desc">{{ item.title }}</text>
<text class="checkin-section__text">{{ item.time }}</text>
</view>
</view>
<view class="checkin-section__tag-badge" :class="'checkin-section__tag-badge--' + item.tagTheme">
<text class="checkin-section__tag-text" :class="'checkin-section__tag-text--' + item.tagTheme">{{ item.tag }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 体测报告区域 -->
<view class="body-report-section">
<view class="body-report-section__inner">
<view class="body-report-section__header">
<view class="body-report-section__header-inner">
<text class="body-report-section__title">体测报告</text>
<view class="body-report-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goBodyTestHistory">
<text class="body-report-section__history-link">历史记录</text>
<image class="body-report-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright3.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="body-report-section__card">
<view class="body-report-section__card-inner">
<view class="body-report-section__card-head">
<view class="body-report-section__card-head-inner">
<text class="body-report-section__desc">最新数据 · {{ bodyReport.date }}</text>
<view class="body-report-section__view-btn" hover-class="mi-tap-btn--hover" :hover-stay-time="150" @tap="goBodyReport">
<image class="body-report-section__view-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/filetext.png" mode="aspectFit" />
<text class="body-report-section__view-report">查看报告</text>
</view>
</view>
</view>
<view class="body-report-section__metrics">
<view class="body-report-section__metrics-inner">
<view class="body-report-section__metric">
<view class="body-report-section__metric-inner">
<text class="body-report-section__text">{{ bodyReport.weight }}</text>
<text class="body-report-section__metric-value">体重(kg)</text>
</view>
</view>
<view class="body-report-section__metric-divider"></view>
<view class="body-report-section__metric">
<view class="body-report-section__metric-inner">
<text class="body-report-section__text-2">{{ bodyReport.bmi }}</text>
<text class="body-report-section__text-3">BMI</text>
</view>
</view>
<view class="body-report-section__metric-divider"></view>
<view class="body-report-section__metric">
<view class="body-report-section__metric-inner">
<text class="body-report-section__text-4">{{ bodyReport.bodyFat }}</text>
<text class="body-report-section__metric-label">体脂率</text>
</view>
</view>
<view class="body-report-section__metric-divider"></view>
<view class="body-report-section__metric">
<view class="body-report-section__metric-inner">
<text class="body-report-section__num">{{ bodyReport.bmr }}</text>
<text class="body-report-section__text-5">BMR</text>
</view>
</view>
</view>
</view>
<view class="body-report-section__summary">
<view class="body-report-section__summary-inner">
<view class="body-report-section__goal">
<image class="body-report-section__goal-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/target.png" mode="aspectFit" />
<text class="body-report-section__goal-text">状态{{ bodyReport.status }}</text>
</view>
<view class="body-report-section__change">
<image class="body-report-section__change-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/trendingdown.png" mode="aspectFit" />
<text class="body-report-section__metric-value-2">较上次 {{ bodyReport.change }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 优惠券&积分区域 -->
<view class="coupon-section">
<view class="coupon-section__inner">
<view class="coupon-section__header">
<view class="coupon-section__header-inner">
<text class="coupon-section__title">优惠券 & 积分</text>
<view class="coupon-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goCoupons">
<text class="coupon-section__view-all">更多详情</text>
<image class="coupon-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright5.png" mode="aspectFit" />
</view>
</view>
</view>
<view class="coupon-section__cards">
<view class="coupon-section__cards-inner">
<view class="coupon-section__coupon" hover-class="mi-tap-card--hover" :hover-stay-time="150" @tap="goCoupons">
<view class="coupon-section__coupon-inner">
<text class="coupon-section__amount">{{ couponPoints.amount }}</text>
<text class="coupon-section__desc">{{ couponPoints.couponDesc }}</text>
<view class="coupon-section__coupon-status">
<text class="coupon-section__status">{{ couponPoints.couponAction }}</text>
</view>
</view>
</view>
<view class="coupon-section__points" hover-class="mi-tap-card--hover" :hover-stay-time="150" @tap="goPoints">
<view class="coupon-section__points-inner">
<text class="coupon-section__num">{{ couponPoints.points }}</text>
<text class="coupon-section__points-label">{{ couponPoints.pointsLabel }}</text>
<view class="coupon-section__points-action">
<text class="coupon-section__text">{{ couponPoints.pointsAction }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 推荐奖励区域 -->
<view class="referral-section">
<view class="referral-section__inner">
<view class="referral-section__header">
<text class="referral-section__title">推荐奖励</text>
<view class="referral-section__link" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="goReferral">
<text class="referral-section__records-link">规则说明</text>
<image class="referral-section__link-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright11.png" mode="aspectFit" />
</view>
</view>
<view class="referral-section__code-row">
<view class="referral-section__code-box">
<text class="referral-section__code-label">我的邀请码</text>
<text class="referral-section__code-value">{{ referral.code }}</text>
</view>
<view class="referral-section__copy-btn" hover-class="mi-tap-btn--hover" :hover-stay-time="150" @tap="copyReferralCode">
<view class="referral-section__copy-icon">
<view class="referral-section__copy-sheet referral-section__copy-sheet--back"></view>
<view class="referral-section__copy-sheet referral-section__copy-sheet--front"></view>
</view>
<text class="referral-section__copy-text">复制</text>
</view>
</view>
<view class="referral-section__stats">
<view class="referral-section__stat">
<text class="referral-section__stat-num referral-section__stat-num--orange">{{ referral.invited }}</text>
<text class="referral-section__stat-label">已推荐</text>
</view>
<view class="referral-section__stat-divider"></view>
<view class="referral-section__stat">
<text class="referral-section__stat-num referral-section__stat-num--green">{{ referral.registered }}</text>
<text class="referral-section__stat-label">已注册</text>
</view>
<view class="referral-section__stat-divider"></view>
<view class="referral-section__stat">
<text class="referral-section__stat-num referral-section__stat-num--amber">{{ referral.purchased }}</text>
<text class="referral-section__stat-label">已购课</text>
</view>
</view>
</view>
</view>
<!-- 设置区域 -->
<view class="settings-section">
<view class="settings-section__inner">
<text class="settings-section__title">设置与安全</text>
<view class="settings-section__list">
<view class="settings-section__list-inner">
<view v-for="(item, index) in settingsItems" :key="item.key">
<view v-if="index > 0" class="settings-section__item-divider"></view>
<view class="settings-section__item" :class="{ 'settings-section__item--tall': item.subtitle }" hover-class="mi-tap-row--hover" :hover-stay-time="150" @tap="onSetting(item.key)">
<view class="settings-section__item-inner">
<view class="settings-section__item-icon-wrap" :class="item.iconWrapClass">
<image class="settings-section__item-icon" :src="item.icon" mode="aspectFit" />
</view>
<view v-if="item.subtitle" class="settings-section__item-texts">
<text class="settings-section__item-title">{{ item.label }}</text>
<text class="settings-section__item-desc">{{ item.subtitle }}</text>
</view>
<text v-else class="settings-section__item-label" :class="item.labelClass">{{ item.label }}</text>
<image class="settings-section__item-arrow" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright10.png" mode="aspectFit" />
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 退出登录 -->
<view class="logout-section">
<view class="logout-section__btn" hover-class="mi-tap-btn--hover" :hover-stay-time="150" @tap="handleLogout">
<image class="logout-section__icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/logout.png" mode="aspectFit" />
<text class="logout-section__text">退出登录</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 固定 TabBar -->
<view class="tabbar-fixed">
<TabBar />
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { PAGE, navigateToPage } from '@/common/constants/routes.js'
import { getCenterPageData, renewMemberCard, computeRemainingDays, saveMemberStore } from '@/common/memberInfo/store.js'
import { getMemberId } from '@/utils/request.js'
import { getMyMemberCards } from '@/api/main.js'
import TabBar from '@/components/TabBar.vue'
// 页面数据
const userInfo = ref({})
const stats = ref({})
const cardInfo = ref({})
const expiringCards = ref([])
const bookingPreview = ref([])
const checkIns = ref([])
const bodyReport = ref({})
const couponPoints = ref({})
const referral = ref({})
// 导航栏样式
const toolbarStyle = ref({})
const toolbarSpacerStyle = ref({})
const navRightStyle = ref({})
// 快捷操作配置
const quickActionsRow1 = ref([
{ key: 'booking', label: '预约课程', textClass: 'quick-actions__title', icon: '' },
{ key: 'bodyTest', label: '智能体测', textClass: 'quick-actions__title-2', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/mappin2.png' },
{ key: 'bodyReport', label: '体测报告', textClass: 'quick-actions__title-3', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/activity.png' },
{ key: 'trainReport', label: '训练报告', textClass: 'quick-actions__coach', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/usercheck.png' }
])
const quickActionsRow2 = ref([
{ key: 'coupon', label: '我的优惠券', textClass: 'quick-actions__text', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/ticket.png' },
{ key: 'points', label: '我的积分', textClass: 'quick-actions__points-desc', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/star.png' },
{ key: 'referral', label: '邀请好友', textClass: 'quick-actions__title-4', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/share2.png' },
{ key: 'course', label: '我的课程', textClass: 'quick-actions__text-2', icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/play.png' }
])
// 设置项配置
const settingsItems = ref([
{
key: 'notify',
label: '通知设置',
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/bell.png',
iconWrapClass: ''
},
{
key: 'password',
label: '修改密码',
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/Vector_2_727.png',
iconWrapClass: 'settings-section__item-icon-wrap--blue'
},
{
key: 'privacy',
label: '隐私政策',
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/shield.png',
iconWrapClass: 'settings-section__item-icon-wrap--green'
},
{
key: 'nfc',
label: 'NFC 门禁卡',
subtitle: '已绑定',
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/ticket.png',
iconWrapClass: ''
},
{
key: 'delete',
label: '注销账户',
icon: 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/userx.png',
iconWrapClass: 'settings-section__item-icon-wrap--red',
labelClass: 'settings-section__item-label--danger'
}
])
// 计算属性
const displayAvatar = computed(() => {
return userInfo.value.avatar || 'https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/AvatarEditWrap.png'
})
// 方法
function syncNavSafeArea() {
try {
const sys = uni.getSystemInfoSync()
const statusBarHeight = sys.statusBarHeight || 0
const navHeight = 44
const menu = uni.getMenuButtonBoundingClientRect?.()
toolbarStyle.value = {
paddingTop: `${statusBarHeight}px`
}
toolbarSpacerStyle.value = {
height: `${statusBarHeight + navHeight}px`
}
if (menu && menu.width) {
const capsuleGap = sys.windowWidth - menu.left + 8
navRightStyle.value = {
width: `${capsuleGap}px`,
minWidth: `${capsuleGap}px`
}
}
} catch (e) {
toolbarSpacerStyle.value = { height: '44px' }
}
}
function refreshFromStore() {
const store = loadMemberStore()
const pageData = getCenterPageData(store)
userInfo.value = pageData.userInfo
stats.value = pageData.stats
cardInfo.value = pageData.cardInfo
expiringCards.value = pageData.expiringCards || []
bookingPreview.value = pageData.bookingPreview
checkIns.value = pageData.checkIns
bodyReport.value = pageData.bodyReport
couponPoints.value = pageData.couponPoints
referral.value = pageData.referral
}
async function refreshCardsFromServer() {
const memberId = getMemberId()
if (!memberId) return
try {
const res = await getMyMemberCards(memberId)
let cards = []
if (Array.isArray(res)) {
cards = res
} else if (res.code === 200 && res.data) {
cards = res.data
} else if (Array.isArray(res.data)) {
cards = res.data
}
if (cards.length > 0) {
const allCards = cards.map(myCard => {
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
const validityEnd = myCard.validityEnd || myCard.expireTime
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
return {
id: myCard.id,
name: cardName,
status: myCard.status || 'active',
validity: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
validityEnd: validityEnd,
validityStart: validityStart
}
})
store.cards = allCards
const myCard = cards[0]
const cardName = myCard.memberCardName || myCard.cardName || myCard.name || `会员卡#${myCard.memberCardId || myCard.id}`
const validityEnd = myCard.validityEnd || myCard.expireTime
const validityStart = myCard.validityStart || myCard.purchaseTime || myCard.createdAt
store.card = {
id: myCard.id,
name: cardName,
status: myCard.status || 'active',
validity: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
validityEnd: validityEnd,
validityStart: validityStart
}
store.cardInfo = {
expireDate: validityEnd ? `有效期至 ${validityEnd}` : '永久有效',
remainingDays: computeRemainingDays(validityEnd)
}
saveMemberStore(store)
refreshFromStore()
}
} catch (e) {
console.error('[MemberCenter] 刷新会员卡数据失败:', e)
}
}
function goUserInfo() {
navigateToPage(PAGE.USER_INFO)
}
function goMemberCard() {
navigateToPage(PAGE.MEMBER_CARD)
}
function goBooking() {
navigateToPage(PAGE.BOOKING)
}
function goCheckInHistory() {
navigateToPage(PAGE.CHECK_IN_HISTORY)
}
function goBodyTestHistory() {
navigateToPage(PAGE.BODY_TEST_HISTORY)
}
function goBodyReport() {
const id = bodyReport.value?.recordId
const url = id ? `${PAGE.BODY_TEST_REPORT}?id=${id}` : PAGE.BODY_TEST_HISTORY
navigateToPage(url)
}
function goCoupons() {
navigateToPage(PAGE.COUPONS)
}
function goPoints() {
navigateToPage(PAGE.POINTS)
}
function goReferral() {
navigateToPage(PAGE.REFERRAL)
}
function onRenewCard() {
uni.showModal({
title: '续费会员卡',
content: '确认续费 90 天?',
success: (res) => {
if (!res.confirm) return
const store = loadMemberStore()
renewMemberCard(store, 90)
refreshFromStore()
uni.showToast({ title: '续费成功', icon: 'success' })
}
})
}
function onQuickAction(type) {
const routes = {
booking: PAGE.COURSE_LIST,
bodyTest: PAGE.BODY_TEST_HOME,
bodyReport: PAGE.BODY_TEST_HISTORY,
trainReport: PAGE.TRAIN_REPORT,
coupon: PAGE.COUPONS,
points: PAGE.POINTS,
referral: PAGE.REFERRAL,
course: PAGE.MY_COURSES
}
if (routes[type]) {
navigateToPage(routes[type])
}
}
function onCheckInTap(item) {
uni.showModal({
title: item.title,
content: item.time,
showCancel: false
})
}
function copyReferralCode() {
uni.setClipboardData({
data: referral.value.code,
success: () => {
uni.showToast({ title: '已复制', icon: 'success' })
}
})
}
function onSetting(key) {
const labels = {
notify: '通知设置',
password: '修改密码',
privacy: '隐私政策',
nfc: 'NFC 门禁卡',
delete: '注销账户'
}
if (key === 'delete') {
uni.showModal({
title: '注销账户',
content: '注销后数据将无法恢复,确定继续吗?',
confirmColor: '#E74C3C'
})
return
}
if (key === 'privacy') {
uni.showModal({
title: '隐私政策',
content: '我们重视您的隐私,详细政策请前往官网查看。',
showCancel: false
})
return
}
uni.showToast({
title: `${labels[key] || '设置'}开发中`,
icon: 'none'
})
}
function handleLogout() {
uni.showModal({
title: '退出登录',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({ title: '已退出', icon: 'success' })
}
}
})
}
onMounted(() => {
syncNavSafeArea()
refreshFromStore()
})
onShow(() => {
refreshCardsFromServer()
})
</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-member-card.css';
@import '@/common/style/memberInfo/member-info-quick-actions.css';
@import '@/common/style/memberInfo/member-info-booking-list.css';
@import '@/common/style/memberInfo/member-info-check-in-list.css';
@import '@/common/style/memberInfo/member-info-body-report.css';
@import '@/common/style/memberInfo/member-info-coupon-points.css';
@import '@/common/style/memberInfo/member-info-referral.css';
@import '@/common/style/memberInfo/member-info-settings.css';
@import '@/common/style/memberInfo/member-info-logout.css';
.tabbar-fixed {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: transparent;
}
.booking-section__empty {
display: flex;
align-items: center;
justify-content: center;
padding: 24px 16px;
}
.booking-section__empty-text {
font-size: 14px;
color: #8A99B4;
}
.expiring-cards-section {
margin: 0 16px 12px;
background: linear-gradient(135deg, #FFF7E6 0%, #FFE4CC 100%);
border-radius: 16px;
overflow: hidden;
}
.expiring-cards__inner {
padding: 16px;
}
.expiring-cards__header {
margin-bottom: 12px;
}
.expiring-cards__header-inner {
display: flex;
align-items: center;
justify-content: space-between;
}
.expiring-cards__title-row {
display: flex;
align-items: center;
gap: 8px;
}
.expiring-cards__warn-icon {
width: 20px;
height: 20px;
}
.expiring-cards__title {
font-size: 16px;
font-weight: 600;
color: #D46B08;
}
.expiring-cards__link {
display: flex;
align-items: center;
gap: 4px;
}
.expiring-cards__link-text {
font-size: 13px;
color: #D46B08;
}
.expiring-cards__link-arrow {
width: 12px;
height: 12px;
}
.expiring-cards__list {
background: rgba(255, 255, 255, 0.6);
border-radius: 12px;
padding: 4px 0;
}
.expiring-card-item {
padding: 12px;
}
.expiring-card-item__inner {
display: flex;
align-items: center;
gap: 12px;
}
.expiring-card-item__icon-wrap {
width: 40px;
height: 40px;
border-radius: 10px;
background: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.expiring-card-item__icon {
width: 24px;
height: 24px;
}
.expiring-card-item__info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.expiring-card-item__name {
font-size: 14px;
font-weight: 600;
color: #1A202C;
}
.expiring-card-item__validity {
font-size: 12px;
color: #718096;
}
.expiring-card-item__days {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
}
.expiring-card-item__days-num {
font-size: 20px;
font-weight: 700;
color: #E74C3C;
}
.expiring-card-item__days-unit {
font-size: 11px;
color: #E74C3C;
}
</style>