You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

281 lines
7.0 KiB

<template>
<view
class="member-card"
:class="[`size-${size}`, { 'is-select': selectable && selected }]"
:style="{ background: cardGradient }"
@click="onClick"
>
<!-- 背景装饰 -->
<view class="decor-circle decor-1"></view>
<view class="decor-circle decor-2"></view>
<!-- 卡片内容 -->
<view class="card-content">
<!-- 顶部:卡类型标签 + 卡名 -->
<view class="card-top">
<text class="card-type">{{ card.chargeType === 'count' ? '次卡' : card.chargeType ==='days'?'周期卡':'储值卡' }}</text>
<text class="card-name">{{ card.cardName || '会员卡' }}</text>
</view>
<!-- 中部:余额/次数 -->
<view class="card-middle">
<view class="balance-box">
<text class="balance-label">
{{ card.chargeType === 'count' ? '剩余次数' : card.chargeType ==='days'?'剩余天数':'卡内余额' }}
</text>
<view class="balance-value-box">
<text v-if="card.chargeType == 'total_fee'" class="unit">¥</text>
<text class="balance-value">
{{ card.chargeType === 'count' ? (card.remainingCount || 0) : card.chargeType ==='days'?card.remainingDays: formatAmount(card.remainingTotalFee) }}
</text>
<text v-if="card.chargeType === 'count'" class="unit">次</text>
<text v-if="card.chargeType === 'days'" class="unit">天</text>
</view>
</view>
<!-- 冻结信息(可选) -->
<view v-if="card.chargeType !== 'days' || card.frozenCount > 0 || card.frozenAmount > 0" class="frozen-box">
<text class="frozen-label">预约占用</text>
<text class="frozen-value">
{{ card.type === 'count' ? card.frozenCount + '次' : '¥' + formatAmount(card.frozenAmount) }}
</text>
</view>
</view>
<!-- 底部:卡号 + 有效期 -->
<view class="card-bottom">
<text class="card-no">NO. {{ card.cardNo || '--' }}</text>
<text class="card-expire">
{{ card.expireDate ? '有效期至 ' + card.expireDate : '长期有效' }}
</text>
</view>
</view>
<!-- 选中标记 -->
<view v-if="selectable && selected" class="selected-icon">
<uni-icons type="checkmarkempty" size="22" color="#fff"></uni-icons>
</view>
</view>
</template>
<script>
export default {
name: 'MemberCard',
props: {
// 会员卡数据
card: {
type: Object,
required: true,
default: () => ({})
},
// 尺寸:small | large
size: {
type: String,
default: 'small'
},
// 是否可选(用于选择弹窗)
selectable: {
type: Boolean,
default: false
},
// 是否选中
selected: {
type: Boolean,
default: false
}
},
computed: {
// 根据卡号生成渐变色(保证每张卡颜色稳定)
cardGradient() {
const themes = [
'linear-gradient(135deg, #00A99A 0%, #1abcb3 100%)', // 青绿
'linear-gradient(135deg, #FF6B6B 0%, #FF8E8E 100%)', // 珊瑚红
'linear-gradient(135deg, #4E7AF5 0%, #6C9EFF 100%)', // 宝蓝
'linear-gradient(135deg, #9B59B6 0%, #B07CC6 100%)', // 紫罗兰
'linear-gradient(135deg, #F39C12 0%, #F7B955 100%)', // 琥珀橙
'linear-gradient(135deg, #16A085 0%, #1ABC9C 100%)', // 翡翠绿
];
// 优先用后端返回的主题色,其次按卡号/ID取模
if (this.card.themeColor) {
return this.card.themeColor;
}
const key = this.card.id || this.card.cardNo || '0';
const idx = Number(String(key).replace(/\D/g, '').slice(-2) || 0) % themes.length;
return themes[idx];
}
},
methods: {
onClick() {
this.$emit('click', this.card);
},
// 金额千分位
formatAmount(val) {
if (val === null || val === undefined) return '0.00';
return Number(val).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
}
};
</script>
<style lang="scss" scoped>
.member-card {
position: relative;
border-radius: 16rpx; /* 20 → 16,圆角同步缩小 */
overflow: hidden;
color: #fff;
box-shadow: 0 6rpx 18rpx rgba(0, 169, 154, 0.25);
transition: transform 0.2s;
flex-shrink: 0;
&:active {
transform: scale(0.98);
}
/* ====== 尺寸变体(缩小后) ====== */
&.size-large {
width: 690rpx;
height: 360rpx;
}
&.size-small {
width: 440rpx;
height: 250rpx;
}
&.size-mini {
width: 380rpx;
height: 220rpx;
}
/* ====== 选中态 ====== */
&.is-select {
border: 4rpx solid #fff;
box-shadow: 0 0 0 4rpx #00A99A, 0 6rpx 18rpx rgba(0, 169, 154, 0.4);
}
}
/* 装饰圆:同步缩小 */
.decor-circle {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.08);
pointer-events: none;
}
.decor-1 {
width: 200rpx;
height: 200rpx;
right: -60rpx;
top: -60rpx;
}
.decor-2 {
width: 130rpx;
height: 130rpx;
right: 50rpx;
bottom: -40rpx;
background: rgba(255, 255, 255, 0.05);
}
.card-content {
position: relative;
z-index: 2;
padding: 24rpx 28rpx; /* 32/36 → 24/28 */
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
}
/* 顶部 */
.card-top {
display: flex;
align-items: center;
justify-content: space-between;
}
.card-type {
font-size: 20rpx; /* 22 → 20 */
padding: 2rpx 12rpx;
border-radius: 16rpx;
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10rpx);
}
.card-name {
font-size: 24rpx; /* 28 → 24 */
font-weight: 600;
max-width: 60%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 中部 */
.card-middle {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin: 8rpx 0;
}
.balance-label {
font-size: 20rpx; /* 22 → 20 */
opacity: 0.85;
display: block;
margin-bottom: 4rpx;
}
.balance-value-box {
display: flex;
align-items: baseline;
}
.unit {
font-size: 22rpx; /* 26 → 22 */
font-weight: 500;
margin: 0 4rpx;
}
.balance-value {
font-size: 42rpx; /* 56 → 42 */
font-weight: 700;
letter-spacing: 1rpx;
font-family: 'DIN Alternate', 'PingFang SC', sans-serif;
}
.frozen-box {
display: flex;
flex-direction: column;
align-items: flex-end;
font-size: 20rpx;
opacity: 0.85;
}
.frozen-label {
margin-bottom: 2rpx;
}
.frozen-value {
font-weight: 600;
}
/* 底部 */
.card-bottom {
display: flex;
flex-direction: column;
font-size: 20rpx;
opacity: 0.85;
}
.card-no {
align-self: flex-start;
}
.card-expire {
align-self: flex-end; /* 有效期靠右 */
margin-top: 4rpx;
}
/* 选中图标:同步缩小 */
.selected-icon {
position: absolute;
top: 16rpx;
right: 16rpx;
width: 36rpx; /* 44 → 36 */
height: 36rpx;
border-radius: 50%;
background: #00A99A;
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
</style>