修正多处问题
This commit is contained in:
@@ -10,69 +10,60 @@
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('view-all')"
|
||||
>
|
||||
<text class="booking-section__view-all">预约记录</text>
|
||||
<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"
|
||||
src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/chevronright2.png"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
v-for="item in previewItems"
|
||||
:key="item.id"
|
||||
class="booking-section__item"
|
||||
hover-class="mi-tap-row--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('item-tap', item)"
|
||||
>
|
||||
<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 class="booking-section__list">
|
||||
<!-- 无数据 -->
|
||||
<view v-if="!displayItems || displayItems.length === 0" class="booking-section__empty">
|
||||
<text class="booking-section__empty-text">暂无预约记录</text>
|
||||
</view>
|
||||
<view v-else class="booking-section__list-inner">
|
||||
<view
|
||||
v-for="(item, index) in displayItems"
|
||||
:key="item.id"
|
||||
class="booking-section__row"
|
||||
>
|
||||
<view v-if="index > 0" class="booking-section__divider"></view>
|
||||
<view
|
||||
class="booking-section__item"
|
||||
hover-class="mi-tap-row--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('item-tap', item)"
|
||||
>
|
||||
<view class="booking-section__item-inner">
|
||||
<view
|
||||
class="booking-section__dot"
|
||||
:class="'booking-section__dot--' + item.status"
|
||||
></view>
|
||||
<view class="booking-section__content">
|
||||
<view class="booking-section__content-inner">
|
||||
<text class="booking-section__desc">{{ item.title }}</text>
|
||||
<text class="booking-section__text">{{ item.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="booking-section__tag-badge"
|
||||
:class="'booking-section__tag-badge--' + item.status"
|
||||
>
|
||||
<text
|
||||
class="booking-section__tag-text"
|
||||
:class="'booking-section__tag-text--' + item.status"
|
||||
>
|
||||
{{ item.statusLabel }}
|
||||
</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="!previewItems.length" class="booking-section__empty">
|
||||
<text class="booking-section__empty-text">暂无进行中的预约</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -89,25 +80,186 @@ const props = defineProps({
|
||||
|
||||
defineEmits(['view-all', 'item-tap'])
|
||||
|
||||
const previewItems = computed(() => {
|
||||
return props.items
|
||||
const displayItems = computed(() => {
|
||||
return props.items || []
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/member-info-booking-list.css';
|
||||
@import '@/common/style/memberInfo/member-info-tap.css';
|
||||
|
||||
.booking-section__empty {
|
||||
.booking-section {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.booking-section__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.booking-section__header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.booking-section__header-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.booking-section__title {
|
||||
font-size: var(--font-size-md);
|
||||
font-family: var(--font-family);
|
||||
font-weight: var(--font-weight-bold);
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.booking-section__list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
border-radius: 14px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background-color: var(--bg-white);
|
||||
border: 1px solid var(--border-light);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.booking-section__list-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.booking-section__empty {
|
||||
padding: 48rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.booking-section__empty-text {
|
||||
font-size: 14px;
|
||||
color: #8A99B4;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.booking-section__row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.booking-section__divider {
|
||||
height: 1px;
|
||||
background: var(--border-light);
|
||||
margin: 0 16rpx;
|
||||
}
|
||||
|
||||
.booking-section__item {
|
||||
padding: 24rpx 16rpx;
|
||||
}
|
||||
|
||||
.booking-section__item-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.booking-section__dot {
|
||||
width: 14rpx;
|
||||
height: 14rpx;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.booking-section__dot--ongoing {
|
||||
background: #4CAF50;
|
||||
}
|
||||
|
||||
.booking-section__dot--completed {
|
||||
background: #2196F3;
|
||||
}
|
||||
|
||||
.booking-section__dot--cancelled {
|
||||
background: var(--text-muted);
|
||||
}
|
||||
|
||||
.booking-section__content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.booking-section__content-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6rpx;
|
||||
}
|
||||
|
||||
.booking-section__desc {
|
||||
font-size: var(--font-size-md);
|
||||
font-family: var(--font-family);
|
||||
color: var(--text-dark);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.booking-section__text {
|
||||
font-size: var(--font-size-sm);
|
||||
font-family: var(--font-family);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.booking-section__tag-badge {
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 8rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.booking-section__tag-badge--ongoing {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.booking-section__tag-badge--completed {
|
||||
background: rgba(33, 150, 243, 0.1);
|
||||
}
|
||||
|
||||
.booking-section__tag-badge--cancelled {
|
||||
background: rgba(153, 153, 153, 0.1);
|
||||
}
|
||||
|
||||
.booking-section__tag-text {
|
||||
font-size: var(--font-size-xs);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
.booking-section__tag-text--ongoing {
|
||||
color: #4CAF50;
|
||||
}
|
||||
|
||||
.booking-section__tag-text--completed {
|
||||
color: #2196F3;
|
||||
}
|
||||
|
||||
.booking-section__tag-text--cancelled {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,7 +20,11 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="checkin-section__list">
|
||||
<view class="checkin-section__list-inner">
|
||||
<!-- 无数据 -->
|
||||
<view v-if="!items || items.length === 0" class="checkin-section__empty">
|
||||
<text class="checkin-section__empty-text">暂无签到记录</text>
|
||||
</view>
|
||||
<view v-else class="checkin-section__list-inner">
|
||||
<view
|
||||
v-for="(item, index) in items"
|
||||
:key="item.id"
|
||||
@@ -79,4 +83,16 @@ defineEmits(['view-all', 'item-tap'])
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/member-info-check-in-list.css';
|
||||
@import '@/common/style/memberInfo/member-info-tap.css';
|
||||
|
||||
.checkin-section__empty {
|
||||
padding: 48rpx 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.checkin-section__empty-text {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,89 +1,50 @@
|
||||
<template>
|
||||
<view class="profile-header">
|
||||
<!-- 用户信息区:深蓝渐变 -->
|
||||
<view class="profile-header__hero">
|
||||
<view class="profile-header__inner" v-if="isLogin">
|
||||
<view class="profile-header__user" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="handleUserTap">
|
||||
<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 class="profile-card" v-if="isLogin" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="handleUserTap">
|
||||
<view class="profile-card__main">
|
||||
<view class="profile-avatar">
|
||||
<image
|
||||
class="profile-avatar__img"
|
||||
:key="userInfo.avatar"
|
||||
:src="displayAvatar"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="profile-info">
|
||||
<view class="profile-info__row">
|
||||
<text class="profile-info__name">{{ userInfo.name || "活氧舱用户" }}</text>
|
||||
<view class="profile-info__badge">
|
||||
<image class="profile-info__badge-icon" src="https://gymfuture.oss-cn-chengdu.aliyuncs.com/static/images/crown0.png" mode="aspectFit" />
|
||||
<text class="profile-info__level">{{ userInfo.memberLevel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="profile-info__phone">{{ userInfo.phone }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="profile-card__stats">
|
||||
<view class="profile-stat">
|
||||
<text class="profile-stat__value">{{ stats.checkInCount ?? 0 }}</text>
|
||||
<text class="profile-stat__label">累计签到</text>
|
||||
</view>
|
||||
<view class="profile-stat">
|
||||
<text class="profile-stat__value">{{ stats.courseCount ?? 0 }}</text>
|
||||
<text class="profile-stat__label">报课次数</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 未登录状态 -->
|
||||
<view class="profile-card profile-card--guest" v-else @tap="handleGuestLogin">
|
||||
<view class="profile-card__main">
|
||||
<view class="profile-avatar">
|
||||
<image class="profile-avatar__img" src="/static/OIP-C.png" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="profile-info">
|
||||
<text class="profile-info__name">小伙伴,点我登录</text>
|
||||
<text class="profile-info__phone">陪你遇见更好的自己 ~</text>
|
||||
</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 class="profile-header__inner" v-else>
|
||||
<view class="profile-header__user" hover-class="mi-tap--hover" :hover-stay-time="150" @tap="handleUserTap">
|
||||
<view
|
||||
class="profile-header__user-inner--guest"
|
||||
@tap="handleGuestLogin"
|
||||
>
|
||||
<view class="profile-header__avatar-wrap--guest">
|
||||
<image
|
||||
class="profile-header__avatar--guest"
|
||||
src="/static/OIP-C.png"
|
||||
mode="aspectFill"
|
||||
/>
|
||||
</view>
|
||||
<view class="profile-header__user-meta--guest">
|
||||
<text class="profile-header__name--guest">小伙伴,点我登录</text>
|
||||
<text class="profile-header__phone--guest">陪你遇见更好的自己 ~</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
@@ -94,10 +55,9 @@ import { computed } from 'vue'
|
||||
const props = defineProps({
|
||||
userInfo: { type: Object, required: true },
|
||||
stats: { type: Object, required: true },
|
||||
isLogin: { type: Boolean }
|
||||
isLogin: { type: Boolean }
|
||||
})
|
||||
|
||||
|
||||
const emit = defineEmits(['user-info', 'guest-login'])
|
||||
|
||||
const displayAvatar = computed(() => {
|
||||
@@ -115,8 +75,112 @@ function handleGuestLogin() {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/member-info-header.css';
|
||||
@import '@/common/style/memberInfo/member-info-tap.css';
|
||||
<style lang="scss" scoped>
|
||||
.profile-header {
|
||||
padding: 24rpx 24rpx 0;
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
background: #FFFFFF;
|
||||
border-radius: 20rpx;
|
||||
padding: 28rpx 28rpx 24rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(45, 74, 90, 0.06);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
|
||||
&--guest {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-card__main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.profile-avatar {
|
||||
width: 88rpx;
|
||||
height: 88rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
border: 3rpx solid #E8F4F8;
|
||||
}
|
||||
|
||||
.profile-avatar__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.profile-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.profile-info__row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-bottom: 6rpx;
|
||||
}
|
||||
|
||||
.profile-info__name {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #1A202C;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.profile-info__badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
background: linear-gradient(135deg, #FFF7ED, #FFF1E6);
|
||||
border-radius: 20rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
}
|
||||
|
||||
.profile-info__badge-icon {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.profile-info__level {
|
||||
font-size: 20rpx;
|
||||
font-weight: 600;
|
||||
color: #ED8936;
|
||||
}
|
||||
|
||||
.profile-info__phone {
|
||||
font-size: 24rpx;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
|
||||
.profile-card__stats {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
padding-top: 20rpx;
|
||||
border-top: 1rpx solid #F0F4F8;
|
||||
}
|
||||
|
||||
.profile-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
}
|
||||
|
||||
.profile-stat__value {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
color: #2D4A5A;
|
||||
}
|
||||
|
||||
.profile-stat__label {
|
||||
font-size: 22rpx;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,98 +1,82 @@
|
||||
<template>
|
||||
<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__count"
|
||||
>
|
||||
<text class="member-card-section__count-num">{{ cardInfo.activeCount || 0 }}</text>
|
||||
<text class="member-card-section__title">我的会员卡</text>
|
||||
<view class="member-card-section__count" v-if="activeCount > 0">
|
||||
<text class="member-card-section__count-num">{{ activeCount }}</text>
|
||||
<text class="member-card-section__count-text">张有效</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="member-card-preview"
|
||||
hover-class="mi-tap-card--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('view-all')"
|
||||
>
|
||||
<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.hasActiveCard ? cardInfo.name : '暂无会员卡' }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="member-card-preview__tag"
|
||||
:class="getTagClass()"
|
||||
v-if="cardInfo.hasActiveCard"
|
||||
>
|
||||
<text class="member-card-preview__tag-text">
|
||||
{{ getTagText() }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 三张卡展示 -->
|
||||
<view class="card-three-row">
|
||||
<!-- 储值卡 -->
|
||||
<view
|
||||
class="card-three-item"
|
||||
:class="{ 'card-three-item--inactive': !storedCard.hasCard }"
|
||||
hover-class="mi-tap-card--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('stored-card-tap')"
|
||||
>
|
||||
<view class="card-three-item__icon-wrap card-three-item__icon-wrap--stored">
|
||||
<uni-icons type="wallet" size="28rpx" :color="storedCard.hasCard ? '#667eea' : '#B0BEC5'" />
|
||||
</view>
|
||||
<text class="member-card-preview__expire" v-if="cardInfo.hasActiveCard && cardInfo.expireDate">
|
||||
有效期至 {{ cardInfo.expireDate }}
|
||||
</text>
|
||||
<view class="member-card-preview__footer">
|
||||
<view class="member-card-preview__footer-inner">
|
||||
<view
|
||||
class="member-card-preview__days"
|
||||
v-if="cardInfo.hasActiveCard"
|
||||
>
|
||||
<text
|
||||
class="member-card-preview__days-num"
|
||||
:class="getDaysClass()"
|
||||
>
|
||||
{{ getDisplayDays() }}
|
||||
</text>
|
||||
<text
|
||||
class="member-card-preview__days-unit"
|
||||
>
|
||||
{{ getDaysUnit() }}
|
||||
</text>
|
||||
</view>
|
||||
<view
|
||||
class="member-card-preview__purchase"
|
||||
hover-class="mi-tap-btn--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap.stop="$emit('purchase')"
|
||||
>
|
||||
<text
|
||||
class="member-card-preview__purchase-text"
|
||||
>
|
||||
购买新卡
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
<text class="card-three-item__label">储值卡</text>
|
||||
<text class="card-three-item__value" v-if="storedCard.hasCard">¥{{ storedCard.balance }}</text>
|
||||
<text class="card-three-item__value card-three-item__value--empty" v-else>未购买</text>
|
||||
<text class="card-three-item__sub" v-if="storedCard.hasCard">余额</text>
|
||||
</view>
|
||||
|
||||
<!-- 次数卡 -->
|
||||
<view
|
||||
class="card-three-item"
|
||||
:class="{ 'card-three-item--inactive': !countCard.hasCard }"
|
||||
>
|
||||
<view class="card-three-item__icon-wrap card-three-item__icon-wrap--count">
|
||||
<uni-icons type="loop" size="28rpx" :color="countCard.hasCard ? '#4CAF50' : '#B0BEC5'" />
|
||||
</view>
|
||||
<text class="card-three-item__label">次数卡</text>
|
||||
<text class="card-three-item__value" v-if="countCard.hasCard">{{ countCard.remainingTimes }}次</text>
|
||||
<text class="card-three-item__value card-three-item__value--empty" v-else>未购买</text>
|
||||
<text class="card-three-item__sub" v-if="countCard.hasCard">剩余</text>
|
||||
</view>
|
||||
|
||||
<!-- 时长卡 -->
|
||||
<view
|
||||
class="card-three-item"
|
||||
:class="{ 'card-three-item--inactive': !timeCard.hasCard }"
|
||||
>
|
||||
<view class="card-three-item__icon-wrap card-three-item__icon-wrap--time">
|
||||
<uni-icons type="calendar" size="28rpx" :color="timeCard.hasCard ? '#FF6B35' : '#B0BEC5'" />
|
||||
</view>
|
||||
<text class="card-three-item__label">时长卡</text>
|
||||
<text class="card-three-item__value" v-if="timeCard.hasCard">{{ timeCard.remainingDays }}天</text>
|
||||
<text class="card-three-item__value card-three-item__value--empty" v-else>未购买</text>
|
||||
<text class="card-three-item__sub" v-if="timeCard.hasCard">剩余</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="card-actions">
|
||||
<view
|
||||
class="card-actions__btn card-actions__btn--primary"
|
||||
hover-class="mi-tap-btn--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap="$emit('view-all')"
|
||||
>
|
||||
<text class="card-actions__btn-text">查看卡片</text>
|
||||
</view>
|
||||
<view
|
||||
class="card-actions__btn card-actions__btn--ghost"
|
||||
hover-class="mi-tap-btn--hover"
|
||||
:hover-stay-time="150"
|
||||
@tap.stop="$emit('purchase')"
|
||||
>
|
||||
<text class="card-actions__btn-text card-actions__btn-text--ghost">购买新卡</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -100,176 +84,260 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, onMounted } from 'vue'
|
||||
import { getPrimaryMemberCard, getMyMemberCardsWithStatus } from '@/api/main.js'
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { getStoredCardInfo, getMyMemberCardsWithStatus } from '@/api/main.js'
|
||||
import { getMemberId } from '@/utils/request.js'
|
||||
|
||||
defineEmits(['view-all', 'purchase'])
|
||||
|
||||
const cardInfo = reactive({
|
||||
name: '',
|
||||
type: '',
|
||||
remainingTimes: 0,
|
||||
remainingDays: 0,
|
||||
expireDate: '',
|
||||
activeCount: 0,
|
||||
isExpiring: false,
|
||||
isExpired: false,
|
||||
isUsedUp: false,
|
||||
hasActiveCard: false
|
||||
})
|
||||
defineEmits(['view-all', 'purchase', 'stored-card-tap'])
|
||||
|
||||
const storedCard = reactive({ hasCard: false, balance: 0 })
|
||||
const countCard = reactive({ hasCard: false, remainingTimes: 0 })
|
||||
const timeCard = reactive({ hasCard: false, remainingDays: 0 })
|
||||
const activeCount = ref(0)
|
||||
let isLoading = false
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(dateStr) {
|
||||
if (!dateStr) return ''
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
} catch (e) {
|
||||
return dateStr
|
||||
}
|
||||
}
|
||||
|
||||
// 加载会员卡数据
|
||||
async function loadMemberCard() {
|
||||
if (isLoading) {
|
||||
console.log('[MemberInfoMemberCard] 正在加载中,跳过重复请求')
|
||||
return
|
||||
}
|
||||
if (isLoading) return
|
||||
isLoading = true
|
||||
try {
|
||||
// 使用统一的 getMemberId 方法获取会员ID
|
||||
const memberId = getMemberId()
|
||||
console.log('[MemberInfoMemberCard] memberId:', memberId)
|
||||
|
||||
if (!memberId) {
|
||||
console.log('[MemberInfoMemberCard] 未登录,无会员ID')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[MemberInfoMemberCard] 加载会员卡数据, memberId:', memberId)
|
||||
|
||||
// 并行获取主要会员卡和有效卡列表
|
||||
const [primaryCardRes, activeCardsRes] = await Promise.all([
|
||||
getPrimaryMemberCard(memberId),
|
||||
getMyMemberCardsWithStatus(memberId, 'active')
|
||||
if (!memberId) return
|
||||
|
||||
// 并行获取储值卡和所有有效卡
|
||||
const [storedRes, activeCardsRes] = await Promise.all([
|
||||
getStoredCardInfo(memberId).catch(() => null),
|
||||
getMyMemberCardsWithStatus(memberId, 'active').catch(() => null)
|
||||
])
|
||||
|
||||
console.log('[MemberInfoMemberCard] getPrimaryMemberCard 返回:', JSON.stringify(primaryCardRes, null, 2))
|
||||
console.log('[MemberInfoMemberCard] getMyMemberCardsWithStatus(active) 返回:', JSON.stringify(activeCardsRes, null, 2))
|
||||
|
||||
// 解析主要会员卡数据
|
||||
let displayCard = null
|
||||
if (primaryCardRes?.data) {
|
||||
displayCard = primaryCardRes.data
|
||||
} else if (primaryCardRes?.value) {
|
||||
displayCard = primaryCardRes.value
|
||||
} else if (primaryCardRes && typeof primaryCardRes === 'object') {
|
||||
displayCard = primaryCardRes
|
||||
}
|
||||
|
||||
// 计算有效会员卡数量(过滤储值卡)
|
||||
let activeCount = 0
|
||||
let allActiveCards = []
|
||||
if (activeCardsRes?.value) {
|
||||
allActiveCards = activeCardsRes.value
|
||||
} else if (Array.isArray(activeCardsRes)) {
|
||||
allActiveCards = activeCardsRes
|
||||
} else if (activeCardsRes?.data) {
|
||||
allActiveCards = Array.isArray(activeCardsRes.data) ? activeCardsRes.data : (activeCardsRes.data.value || [])
|
||||
}
|
||||
// 过滤储值卡
|
||||
allActiveCards = allActiveCards.filter(card => card.memberCardType !== 'STORED_VALUE_CARD')
|
||||
activeCount = allActiveCards.length
|
||||
|
||||
// 计算卡片状态
|
||||
let diffDays = 0
|
||||
let isExpiring = false
|
||||
let isExpired = false
|
||||
let isUsedUp = false
|
||||
let displayDays = 0
|
||||
|
||||
if (displayCard) {
|
||||
if (displayCard.status === 'USED_UP') {
|
||||
isUsedUp = true
|
||||
displayDays = displayCard.remainingTimes || 0
|
||||
} else if (displayCard.expireTime) {
|
||||
const expireTime = new Date(displayCard.expireTime)
|
||||
const now = new Date()
|
||||
diffDays = Math.ceil((expireTime - now) / (1000 * 60 * 60 * 24))
|
||||
isExpiring = diffDays > 0 && diffDays <= 3
|
||||
isExpired = diffDays <= 0
|
||||
displayDays = Math.abs(diffDays)
|
||||
|
||||
// 解析储值卡
|
||||
if (storedRes) {
|
||||
const data = storedRes.data || storedRes.value || storedRes
|
||||
if (data && (data.balance > 0 || data.totalAmount > 0 || data.id)) {
|
||||
storedCard.hasCard = true
|
||||
storedCard.balance = data.balance || data.totalAmount || 0
|
||||
}
|
||||
}
|
||||
console.log(displayCard)
|
||||
|
||||
// 更新数据
|
||||
cardInfo.name = displayCard?.memberCardName || '暂无会员卡'
|
||||
cardInfo.type = displayCard?.memberCardType || ''
|
||||
cardInfo.remainingTimes = displayCard?.remainingTimes || displayCard?.remainingAmount || 0
|
||||
cardInfo.remainingDays = displayDays
|
||||
cardInfo.expireDate = displayCard?.expireTime ? formatDate(displayCard.expireTime) : ''
|
||||
cardInfo.activeCount = activeCount
|
||||
cardInfo.isExpiring = isExpiring
|
||||
cardInfo.isExpired = isExpired
|
||||
cardInfo.isUsedUp = isUsedUp
|
||||
cardInfo.hasActiveCard = activeCount > 0
|
||||
|
||||
console.log('[MemberInfoMemberCard] 更新后的 cardInfo:', JSON.stringify(cardInfo, null, 2))
|
||||
|
||||
// 解析有效卡列表(排除储值卡)
|
||||
let allActive = []
|
||||
if (activeCardsRes?.value) {
|
||||
allActive = activeCardsRes.value
|
||||
} else if (Array.isArray(activeCardsRes)) {
|
||||
allActive = activeCardsRes
|
||||
} else if (activeCardsRes?.data) {
|
||||
allActive = Array.isArray(activeCardsRes.data) ? activeCardsRes.data : (activeCardsRes.data.value || [])
|
||||
}
|
||||
allActive = allActive.filter(card => card.memberCardType !== 'STORED_VALUE_CARD')
|
||||
activeCount.value = allActive.length
|
||||
|
||||
// 查找次数卡
|
||||
const countCardData = allActive.find(c => c.memberCardType === 'COUNT_CARD')
|
||||
if (countCardData) {
|
||||
countCard.hasCard = true
|
||||
countCard.remainingTimes = countCardData.remainingTimes || countCardData.remainingAmount || 0
|
||||
}
|
||||
|
||||
// 查找时长卡(优先找有效期最长的)
|
||||
const timeCards = allActive.filter(c => c.memberCardType === 'TIME_CARD')
|
||||
if (timeCards.length > 0) {
|
||||
timeCard.hasCard = true
|
||||
// 计算剩余天数
|
||||
let maxDays = 0
|
||||
timeCards.forEach(c => {
|
||||
if (c.expireTime) {
|
||||
const now = new Date()
|
||||
const expire = new Date(c.expireTime)
|
||||
const days = Math.max(0, Math.ceil((expire - now) / (1000 * 60 * 60 * 24)))
|
||||
if (days > maxDays) maxDays = days
|
||||
}
|
||||
})
|
||||
timeCard.remainingDays = maxDays
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[MemberInfoMemberCard] 加载会员卡数据失败:', e)
|
||||
cardInfo.hasActiveCard = false
|
||||
console.error('[MemberInfoMemberCard] 加载失败:', e)
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
// 组件挂载时加载数据
|
||||
onMounted(() => {
|
||||
console.log('[MemberInfoMemberCard] 组件已挂载,开始加载数据')
|
||||
loadMemberCard()
|
||||
})
|
||||
|
||||
function getTagClass() {
|
||||
if (cardInfo.isExpiring) return 'member-card-preview__tag--expiring'
|
||||
if (cardInfo.isExpired) return 'member-card-preview__tag--expired'
|
||||
if (cardInfo.isUsedUp) return 'member-card-preview__tag--expired'
|
||||
return 'member-card-preview__tag--active'
|
||||
}
|
||||
|
||||
function getTagText() {
|
||||
if (cardInfo.isUsedUp) return '已用完'
|
||||
if (cardInfo.isExpired) return '已失效'
|
||||
if (cardInfo.isExpiring) return '临期'
|
||||
return '有效'
|
||||
}
|
||||
|
||||
function getDaysClass() {
|
||||
if (cardInfo.isExpiring) return 'member-card-preview__days-num--expiring'
|
||||
if (cardInfo.isExpired) return 'member-card-preview__days-num--expired'
|
||||
if (cardInfo.isUsedUp) return 'member-card-preview__days-num--expired'
|
||||
return ''
|
||||
}
|
||||
|
||||
function getDisplayDays() {
|
||||
if (cardInfo.isUsedUp) return cardInfo.remainingTimes || 0
|
||||
return cardInfo.remainingDays || 0
|
||||
}
|
||||
|
||||
function getDaysUnit() {
|
||||
if (cardInfo.isUsedUp) return '次剩余'
|
||||
return '天剩余'
|
||||
}
|
||||
defineExpose({ loadMemberCard })
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
@import '@/common/style/memberInfo/member-info-component-reset.css';
|
||||
@import '@/common/style/memberInfo/member-info-member-card.css';
|
||||
@import '@/common/style/memberInfo/member-info-tap.css';
|
||||
|
||||
// ========== 容器 ==========
|
||||
.member-card-section {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.member-card-section__inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
// ========== 标题栏 ==========
|
||||
.member-card-section__head {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.member-card-section__head-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.member-card-section__title {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 700;
|
||||
color: var(--text-dark);
|
||||
}
|
||||
|
||||
.member-card-section__count {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 4rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba(255, 107, 53, 0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.member-card-section__count-num {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #FF6B35;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.member-card-section__count-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 400;
|
||||
color: #FF6B35;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
// ========== 三张卡片行 ==========
|
||||
.card-three-row {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-three-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 24rpx 12rpx;
|
||||
border-radius: 16rpx;
|
||||
background: #FFFFFF;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.06);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.card-three-item--inactive {
|
||||
opacity: 0.55;
|
||||
background: #F5F7FA;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.card-three-item__icon-wrap {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
border-radius: 28rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-three-item__icon-wrap--stored {
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.card-three-item__icon-wrap--count {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
}
|
||||
|
||||
.card-three-item__icon-wrap--time {
|
||||
background: rgba(255, 107, 53, 0.1);
|
||||
}
|
||||
|
||||
.card-three-item--inactive .card-three-item__icon-wrap {
|
||||
background: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-three-item__label {
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
color: #5E6F8D;
|
||||
}
|
||||
|
||||
.card-three-item__value {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #1E2A3A;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.card-three-item__value--empty {
|
||||
font-size: 24rpx;
|
||||
font-weight: 400;
|
||||
color: #B0BEC5;
|
||||
}
|
||||
|
||||
.card-three-item__sub {
|
||||
font-size: 20rpx;
|
||||
color: #A0AEC0;
|
||||
}
|
||||
|
||||
// ========== 操作按钮 ==========
|
||||
.card-actions {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card-actions__btn {
|
||||
flex: 1;
|
||||
height: 72rpx;
|
||||
border-radius: 36rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.card-actions__btn--primary {
|
||||
background: linear-gradient(135deg, #FF6B35, #FF8C5A);
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
|
||||
}
|
||||
|
||||
.card-actions__btn--ghost {
|
||||
background: #FFFFFF;
|
||||
border: 2rpx solid #FF6B35;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-actions__btn-text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.card-actions__btn-text--ghost {
|
||||
color: #FF6B35;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user