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

662 lines
21 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="coupon-page">
<TopSafe></TopSafe>
<Header title="优惠券" />
<!-- 顶部分类 Tab 栏 -->
<view class="tab-container">
<view
v-for="(item, index) in tabs"
:key="index"
class="tab-item"
:class="{ active: currentTab === index }"
@click="switchTab(index)"
>
<text class="tab-text">{{ item.name }}</text>
<view class="active-line" v-if="currentTab === index"></view>
</view>
</view>
<!-- 优惠券列表及分页加载区域 -->
<mescroll-uni
ref="mescrollRef"
top="176rpx"
@down="downCallback"
@up="upCallback"
:up="upOption"
:down="downOption"
@init="mescrollInit"
>
<!-- 列表视图 -->
<view class="coupon-list-container" v-if="couponList && couponList.length > 0">
<view
v-for="(coupon, index) in couponList"
:key="coupon.couponUserId || coupon.userCouponId || coupon.templateId || index"
class="coupon-card"
:class="{ 'card-disabled': getCardDisabled(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': getCardDisabled(coupon) }">
{{ getTagText(coupon) }}
</text>
<text class="coupon-scope">{{ getScopeText(coupon) }}</text>
</view>
<view class="coupon-validity">{{ getValidityText(coupon) }}</view>
</view>
<!-- 操作按钮 -->
<view class="btn-box">
<view
v-if="!getCardDisabled(coupon)"
class="action-btn btn-use"
@click="onUseCoupon(coupon)"
>
去使用
</view>
<view
v-else
class="action-btn btn-disabled"
>
{{ getBtnText(coupon) }}
</view>
</view>
</view>
</view>
</view>
<!-- 无优惠券缺省视图 (图二高保真) -->
<view class="empty-container" v-else-if="isEmpty">
<view class="empty-graphic">
<view class="purple-bag-wrapper">
<view class="glow-bg"></view>
<view class="bag-card">
<view class="bag-handle"></view>
<view class="bag-logo">TB</view>
</view>
<view class="heart-bubble">
<text class="heart-icon">♥</text>
</view>
</view>
</view>
<text class="empty-tip">暂无优惠券</text>
<view class="btn-go-get" v-if="currentTab === 0" @click="onGoGetCoupon">
去领取优惠券
</view>
</view>
</mescroll-uni>
<up-toast ref="uToastRef"></up-toast>
<qiaobao-assistant page-key="pages/other_package/coupon/coupon" title="优惠券" />
</view>
</template>
<script>
import Header from '@/components/common/header.vue';
import TopSafe from '@/components/common/top-safe.nvue';
import MescrollMixin from '@/components/mescroll-uni/mescroll-mixins.js';
import MescrollUni from '@/components/mescroll-uni/mescroll-uni.vue';
import { getMyCouponPage } from '@/api/coupon.js';
export default {
mixins: [MescrollMixin],
components: { Header, TopSafe, MescrollUni },
data() {
return {
currentTab: 0,
tabs: [
{ name: '待使用', status: 1 },
{ name: '已使用', status: 2 },
{ name: '已过期', status: 3 },
{ name: '已失效', status: 4 }
],
couponList: [],
isEmpty: false,
upOption: {
auto: true,
page: {
num: 0,
size: 10
},
noMoreSize: 5,
empty: {
use: false // 使用自定义高保真 Empty 视图
},
textColor: '#333',
bgColor: 'rgba(0,0,0,0)'
},
downOption: {
auto: false,
textColor: '#333',
bgColor: 'rgba(0,0,0,0)'
},
mescroll: null
};
},
methods: {
mescrollInit(mescroll) {
this.mescroll = mescroll;
},
downCallback() {
this.mescroll && this.mescroll.resetUpScroll();
},
// 切换 Tab 标签
switchTab(index) {
if (this.currentTab !== index) {
this.currentTab = index;
this.couponList = [];
this.isEmpty = false;
this.mescroll && this.mescroll.resetUpScroll();
}
},
// 分页获取我的优惠券列表
async upCallback(page) {
try {
const currentStatus = this.tabs[this.currentTab].status;
const params = {
status: currentStatus,
page: page.num,
pageSize: page.size
};
const resp = await getMyCouponPage(params);
if (resp && resp.bizcode === 100) {
const data = resp.data || {};
const list = data.entitys || [];
const curPageLen = list.length;
const totalCount = data.totalCount || 0;
if (page.num === 1) {
this.couponList = [];
}
this.couponList = this.couponList.concat(list);
this.isEmpty = this.couponList.length === 0;
this.mescroll.endBySize(curPageLen, totalCount);
} else {
if (page.num === 1 && this.couponList.length === 0) {
this.couponList = [];
this.isEmpty = true;
this.mescroll.endBySize(0, 0);
} else {
this.mescroll && this.mescroll.endErr();
}
}
} catch (e) {
console.error('获取我的优惠券列表异常:', e);
if (page.num === 1 && this.couponList.length === 0) {
this.couponList = [];
this.isEmpty = true;
this.mescroll.endBySize(0, 0);
} else {
this.mescroll && this.mescroll.endErr();
}
}
},
// 点击“去使用”
onUseCoupon(coupon) {
const templateId = coupon.templateId || coupon.id || '';
uni.navigateTo({
url: `/pages/other_package/applicable_goods/applicable_goods?templateId=${templateId}`
});
},
// 点击“去领取优惠券” -> 跳转至领券中心
onGoGetCoupon() {
uni.navigateTo({
url: '/pages/other_package/get-coupon/get-coupon'
});
},
// 数据映射格式化函数
getDiscountAmount(coupon) {
return coupon.discountAmount !== undefined ? coupon.discountAmount : (coupon.amount || 0);
},
getThresholdText(coupon) {
if (coupon.thresholdType) return coupon.thresholdType;
const threshold = coupon.thresholdAmount !== undefined ? Number(coupon.thresholdAmount) : 0;
if (threshold === 0) {
return '无门槛';
}
return `满${threshold}元可用`;
},
getTagText(coupon) {
if (coupon.tag) return coupon.tag;
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.scope) return coupon.scope;
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.endTime) {
return `有效期至 ${this.formatTime(coupon.endTime)}`;
}
if (coupon.useEndTime) {
return `有效期至 ${this.formatTime(coupon.useEndTime)}`;
}
if (coupon.validityText) return coupon.validityText;
if (coupon.validDays && coupon.validDays > 0) {
return `领取之日起${coupon.validDays}天内有效`;
}
if (coupon.receiveEndTime) {
return `截止时间 ${this.formatTime(coupon.receiveEndTime)}`;
}
return '有效期至 2026-8-14 14:20:55';
},
getBtnText(coupon) {
if (coupon.statusText) return coupon.statusText;
const status = coupon.status !== undefined ? Number(coupon.status) : this.tabs[this.currentTab].status;
if (status === 1) return '去使用';
if (status === 2) return '已使用';
if (status === 3) return '已过期';
if (status === 4) return '已失效';
return '已失效';
},
getCardDisabled(coupon) {
const status = coupon.status !== undefined ? Number(coupon.status) : this.tabs[this.currentTab].status;
return status !== 1;
},
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');
const hh = String(date.getHours()).padStart(2, '0');
const mm = String(date.getMinutes()).padStart(2, '0');
const ss = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${hh}:${mm}:${ss}`;
}
}
};
</script>
<style lang="scss" scoped>
.coupon-page {
min-height: 100vh;
background-color: #f7f8fa;
position: relative;
}
/* 顶部分类 Tab 栏 */
.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;
position: relative;
z-index: 90;
.tab-item {
margin-right: 48rpx;
position: relative;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.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: 48rpx;
height: 6rpx;
background-color: #7a35f6;
border-radius: 4rpx;
}
}
}
/* 优惠券列表容器 */
.coupon-list-container {
padding: 24rpx 24rpx 40rpx 24rpx;
box-sizing: border-box;
}
/* 优惠券卡片基础样式 (待使用状态) */
.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;
/* 已使用 / 已过期 / 已失效 灰色变暗样式 (图三图四高保真) */
&.card-disabled {
background: #f5f5f5 !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;
.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: 12rpx;
.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;
}
}
.btn-box {
flex-shrink: 0;
.action-btn {
width: 130rpx;
height: 54rpx;
line-height: 54rpx;
text-align: center;
font-size: 24rpx;
font-weight: bold;
border-radius: 8rpx;
box-sizing: border-box;
&.btn-use {
background: #ff1e38;
color: #ffffff;
}
&.btn-disabled {
background: #e5e5e5;
color: #a0a0a0;
font-weight: normal;
}
}
}
}
}
/* 缺省页面 (图二高保真) */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 140rpx;
.empty-graphic {
position: relative;
width: 220rpx;
height: 220rpx;
margin-bottom: 24rpx;
.purple-bag-wrapper {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
.glow-bg {
position: absolute;
width: 180rpx;
height: 180rpx;
background: radial-gradient(circle, rgba(168, 85, 247, 0.25) 0%, rgba(247, 248, 250, 0) 70%);
border-radius: 50%;
}
.bag-card {
position: relative;
width: 110rpx;
height: 120rpx;
background: linear-gradient(135deg, #a855f7 0%, #7a35f6 100%);
border-radius: 20rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 12rpx 28rpx rgba(122, 53, 246, 0.3);
z-index: 2;
.bag-handle {
position: absolute;
top: -16rpx;
width: 44rpx;
height: 24rpx;
border: 4rpx solid #c084fc;
border-bottom: none;
border-radius: 14rpx 14rpx 0 0;
}
.bag-logo {
font-size: 32rpx;
font-weight: 900;
color: #ffffff;
letter-spacing: 2rpx;
}
}
.heart-bubble {
position: absolute;
top: 20rpx;
right: 24rpx;
width: 44rpx;
height: 44rpx;
background: linear-gradient(135deg, #c084fc, #a855f7);
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4rpx 12rpx rgba(168, 85, 247, 0.3);
z-index: 3;
.heart-icon {
color: #ffffff;
font-size: 24rpx;
}
}
}
}
.empty-tip {
font-size: 28rpx;
color: #666666;
margin-bottom: 48rpx;
}
.btn-go-get {
width: 440rpx;
height: 80rpx;
line-height: 80rpx;
text-align: center;
background: linear-gradient(135deg, #a855f7 0%, #7a35f6 100%);
color: #ffffff;
font-size: 30rpx;
font-weight: bold;
border-radius: 40rpx;
box-shadow: 0 8rpx 24rpx rgba(122, 53, 246, 0.3);
transition: transform 0.1s ease;
&:active {
transform: scale(0.98);
}
}
}
</style>