990 lines
51 KiB
Vue
990 lines
51 KiB
Vue
<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>
|