Files
frontend-app/pages/order_package/order_submit/coupon-dialog.vue
T
2026-08-21 14:10:43 +08:00

575 lines
14 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>
<view class="content">
<up-popup
:show="show"
mode="bottom"
:round="20"
:closeOnClickOverlay="true"
:closeable="true"
:safeAreaInsetBottom="true"
@close="handleClose"
>
<view class="coupon-popup">
<!-- 固定的标题头部 -->
<view class="popup-header">
<text class="title">选择优惠券</text>
</view>
<!-- 固定的切换标签 (全部/可使用/不可用) -->
<view class="tab-container">
<view
v-for="(tab, index) in tabs"
:key="index"
:class="['tab-item', { active: activeTab === index }]"
@click="switchTab(index)"
>
<text class="tab-text">{{ tab.name }}</text>
<view class="active-line" v-if="activeTab === index"></view>
</view>
</view>
<!-- 可上下滚动的优惠券列表区域 -->
<scroll-view class="coupon-scroll" scroll-y :enhanced="true" :show-scrollbar="false">
<view class="coupon-list" v-if="displayCoupons.length > 0">
<view
v-for="(coupon, index) in displayCoupons"
:key="coupon.couponUserId || coupon.id || coupon.templateId || index"
class="coupon-card"
:class="{ 'card-disabled': !isAvailable(coupon), 'card-selected': isSelected(coupon) }"
@click="isAvailable(coupon) && toggleSelect(coupon)"
>
<!-- 左侧:金额与门槛 -->
<view class="card-left">
<view class="price-box">
<text class="currency">¥</text>
<text class="amount">{{ getDiscountAmount(coupon) }}</text>
</view>
<text class="condition">{{ getThresholdText(coupon) }}</text>
</view>
<!-- 带有上下半圆凹槽的虚线分割线 -->
<view class="divider-wrapper">
<view class="notch top-notch"></view>
<view class="divider-line"></view>
<view class="notch bottom-notch"></view>
</view>
<!-- 右侧:详细内容与单选框 -->
<view class="card-right">
<view class="info-content">
<view class="coupon-title">{{ coupon.title || '优惠券' }}</view>
<view class="tag-row">
<text class="coupon-tag" :class="{ 'tag-disabled': !isAvailable(coupon) }">
{{ getTagText(coupon) }}
</text>
<text class="coupon-scope">{{ getScopeText(coupon) }}</text>
</view>
<view class="coupon-validity">{{ getValidityText(coupon) }}</view>
<view class="unavailable-reason" v-if="!isAvailable(coupon)">
{{ getUnavailableReason(coupon) }}
</view>
</view>
<!-- 右侧操作区:可使用状态下展示单选框 (Radio Button) -->
<view class="action-box">
<view v-if="isAvailable(coupon)" class="radio-wrap" @click.stop="toggleSelect(coupon)">
<view class="radio-circle" :class="{ 'checked': isSelected(coupon) }">
<text class="check-icon" v-if="isSelected(coupon)">✓</text>
</view>
</view>
<view v-else class="disabled-tag-text">
不可用
</view>
</view>
</view>
</view>
</view>
<!-- 列表为空的缺省视图 -->
<view class="empty-wrap" v-else-if="!isLoading">
<text class="empty-text">暂无相关优惠券</text>
</view>
</scroll-view>
<!-- 底部不使用优惠券按钮 (方便取消选择) -->
<view class="popup-footer" v-if="coupons && coupons.length > 0">
<view class="no-use-btn" @click="noUseCoupon">
不使用优惠券
</view>
</view>
</view>
</up-popup>
</view>
</template>
<script>
export default {
name: "CouponDialog",
props: {
show: {
type: Boolean,
default: false,
},
goodsId: {
type: [Number, String],
default: 0,
},
coupons: {
type: Array,
default: () => [],
},
orderAmount: {
type: [Number, String],
default: 0,
},
selectedCoupon: {
type: Object,
default: () => null,
},
},
data() {
return {
activeTab: 0,
tabs: [
{ name: "全部", value: "" },
{ name: "可使用", value: 1 },
{ name: "不可用", value: 2 },
],
isLoading: false,
};
},
computed: {
displayCoupons() {
if (this.activeTab === 1) {
return this.coupons.filter((coupon) => this.isAvailable(coupon));
}
if (this.activeTab === 2) {
return this.coupons.filter((coupon) => !this.isAvailable(coupon));
}
return this.coupons;
},
},
methods: {
switchTab(index) {
this.activeTab = index;
},
isAvailable(coupon) {
if (coupon.canUse === false) return false;
if (coupon.available === false) return false;
if (coupon.receiveStatus === 2) return false;
if (coupon.status === 2 || coupon.status === 3 || coupon.status === 4) return false;
return Number(this.orderAmount || 0) >= Number(coupon.thresholdAmount || 0);
},
isSelected(coupon) {
if (!this.selectedCoupon) return false;
const curId = coupon.couponUserId || coupon.id || coupon.templateId;
const selId = this.selectedCoupon.couponUserId || this.selectedCoupon.id || this.selectedCoupon.templateId;
return curId && selId && String(curId) === String(selId);
},
toggleSelect(coupon) {
if (this.isSelected(coupon)) {
this.$emit("select", null);
} else {
this.$emit("select", coupon);
}
this.handleClose();
},
noUseCoupon() {
this.$emit("select", null);
this.handleClose();
},
handleClose() {
this.$emit("close");
},
// 格式化辅助函数
getDiscountAmount(coupon) {
return coupon.discountAmount !== undefined ? coupon.discountAmount : (coupon.amount || 0);
},
getThresholdText(coupon) {
const threshold = coupon.thresholdAmount !== undefined ? Number(coupon.thresholdAmount) : 0;
if (threshold === 0) {
return "无门槛";
}
return `满${threshold}元可用`;
},
getTagText(coupon) {
if (coupon.scopeType === 1) return "通用券";
if (coupon.scopeType === 2) return "商品券";
if (coupon.scopeType === 3 || coupon.scopeType === 4) return "品类券";
if (coupon.scopeText && coupon.scopeText.includes("通用")) return "通用券";
return "通用券";
},
getScopeText(coupon) {
if (coupon.scopeText) return coupon.scopeText;
if (coupon.scopeType === 1) return "所有商品可用";
if (coupon.scopeType === 2) return "指定商品可用";
if (coupon.scopeType === 3) return "指定类目可用";
if (coupon.scopeType === 4) return "指定专区商品可用";
return "所有商品可用";
},
getValidityText(coupon) {
if (coupon.useEndTime) {
return `有效期至 ${this.formatTime(coupon.useEndTime)}`;
}
if (coupon.endTime) {
return `有效期至 ${this.formatTime(coupon.endTime)}`;
}
if (coupon.validDays && coupon.validDays > 0) {
return `领取之日起${coupon.validDays}天内有效`;
}
return "长期有效";
},
getUnavailableReason(coupon) {
const threshold = Number(coupon.thresholdAmount || 0);
if (Number(this.orderAmount || 0) < threshold) {
return `订单金额未满${threshold}元`;
}
return coupon.unavailableReason || "该优惠券不适用于当前订单";
},
formatTime(time) {
if (!time) return "";
if (typeof time === "string" && time.includes("-")) return time;
const date = new Date(Number(time));
if (isNaN(date.getTime())) return String(time);
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, "0");
const d = String(date.getDate()).padStart(2, "0");
return `${y}-${m}-${d}`;
},
},
};
</script>
<style lang="scss" scoped>
.coupon-popup {
height: 75vh;
background-color: #f7f8fa;
border-radius: 24rpx 24rpx 0 0;
display: flex;
flex-direction: column;
overflow: hidden;
}
.popup-header {
text-align: center;
font-size: 32rpx;
background-color: #ffffff;
color: #333333;
font-weight: bold;
padding: 30rpx 0 20rpx 0;
position: relative;
}
.tab-container {
display: flex;
width: 100%;
background-color: #ffffff;
height: 88rpx;
align-items: center;
padding-left: 32rpx;
box-sizing: border-box;
border-top: 1rpx solid #f6f6f6;
border-bottom: 1rpx solid #f0f0f0;
.tab-item {
margin-right: 48rpx;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
.tab-text {
font-size: 28rpx;
color: #666666;
transition: color 0.2s ease;
}
&.active {
.tab-text {
color: #7a35f6;
font-weight: bold;
}
}
.active-line {
position: absolute;
bottom: 6rpx;
width: 44rpx;
height: 6rpx;
background-color: #7a35f6;
border-radius: 3rpx;
}
}
}
.coupon-scroll {
flex: 1;
height: 0;
padding: 24rpx 24rpx 0 24rpx;
box-sizing: border-box;
}
.coupon-list {
display: flex;
flex-direction: column;
padding-bottom: 30rpx;
}
/* 高保真优惠券卡片样式 */
.coupon-card {
position: relative;
display: flex;
align-items: center;
background: rgba(253, 28, 36, 0.06);
border-radius: 16rpx;
margin-bottom: 20rpx;
box-sizing: border-box;
transition: border-color 0.2s ease;
&.card-selected {
/* 不加外边框 */
}
&.card-disabled {
background: #f5f5f5 !important;
border-color: transparent !important;
.card-left {
.price-box {
color: #a0a0a0 !important;
}
.condition {
color: #999999 !important;
}
}
.coupon-title {
color: #333333 !important;
}
}
.card-left {
width: 170rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24rpx 0;
flex-shrink: 0;
.price-box {
display: flex;
align-items: baseline;
color: #ff2442;
.currency {
font-size: 28rpx;
font-weight: bold;
margin-right: 2rpx;
}
.amount {
font-size: 60rpx;
font-weight: 700;
line-height: 1;
}
}
.condition {
font-size: 24rpx;
color: #ff2442;
margin-top: 10rpx;
font-weight: 500;
}
}
.divider-wrapper {
position: relative;
width: 2rpx;
align-self: stretch;
.divider-line {
height: 100%;
border-left: 2rpx dashed #fca5a5;
}
.notch {
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 24rpx;
height: 24rpx;
background-color: #f7f8fa;
border-radius: 50%;
z-index: 10;
}
.top-notch {
top: -12rpx;
}
.bottom-notch {
bottom: -12rpx;
}
}
.card-disabled .divider-line {
border-left: 2rpx dashed #dddddd !important;
}
.card-right {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 20rpx 20rpx 24rpx;
.info-content {
flex: 1;
min-width: 0;
margin-right: 16rpx;
.coupon-title {
font-size: 30rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 10rpx;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tag-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
white-space: nowrap;
.coupon-tag {
display: inline-block;
font-size: 20rpx;
color: #ff2442;
background: #ffe4e6;
border: 1px solid #ff99a4;
border-radius: 6rpx;
padding: 2rpx 10rpx;
margin-right: 10rpx;
line-height: 1.2;
font-weight: 500;
flex-shrink: 0;
white-space: nowrap;
&.tag-disabled {
color: #888888 !important;
background: #eeeeee !important;
border: 1px solid #cccccc !important;
}
}
.coupon-scope {
font-size: 22rpx;
color: #666666;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.coupon-validity {
font-size: 22rpx;
color: #999999;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.unavailable-reason {
font-size: 20rpx;
color: #ff4d4f;
margin-top: 6rpx;
}
}
.action-box {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
padding-right: 8rpx;
/* 单选框 (Radio Button) 高保真样式 */
.radio-wrap {
padding: 10rpx;
.radio-circle {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
border: 2rpx solid #cccccc;
background-color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
&.checked {
background-color: #7a35f6;
border-color: #7a35f6;
}
.check-icon {
color: #ffffff;
font-size: 26rpx;
font-weight: bold;
}
}
}
.disabled-tag-text {
font-size: 24rpx;
color: #aaaaaa;
}
}
}
}
.empty-wrap {
display: flex;
align-items: center;
justify-content: center;
padding: 120rpx 0;
.empty-text {
font-size: 28rpx;
color: #999999;
}
}
.popup-footer {
padding: 20rpx 32rpx 30rpx 32rpx;
background-color: #ffffff;
.no-use-btn {
width: 100%;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background-color: #f5f5f5;
color: #666666;
font-size: 28rpx;
font-weight: 500;
border-radius: 40rpx;
&:active {
background-color: #eeeeee;
}
}
}
</style>