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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>