文件
fenleixinxi/uniapp/pages/member/coupons.vue

846 行
21 KiB
Vue

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<template>
<view class="coupon-page fc-page">
<!-- 自定义导航栏:深绿渐变沉浸式 -->
<fc-navbar title="优惠券中心" theme="gradient" :show-back="true" />
<!-- 顶部深绿沉浸底座 -->
<view class="coupon-page__backdrop">
<view class="coupon-page__backdrop-ambient"></view>
</view>
<!-- 顶部主标签页(我的卡券 vs 领券中心) -->
<view class="coupon-page__tabs-wrap">
<view class="coupon-page__tabs">
<view
class="coupon-page__tab-item"
:class="{ 'coupon-page__tab-item--active': mainTab === 'mine' }"
@click="switchMainTab('mine')"
>
<text class="coupon-page__tab-text">我的卡券</text>
<text v-if="myCouponCount > 0" class="coupon-page__tab-badge">{{ myCouponCount }}</text>
<view v-if="mainTab === 'mine'" class="coupon-page__tab-line"></view>
</view>
<view
class="coupon-page__tab-item"
:class="{ 'coupon-page__tab-item--active': mainTab === 'market' }"
@click="switchMainTab('market')"
>
<text class="coupon-page__tab-text">领券中心</text>
<view v-if="marketAvailableCount > 0" class="coupon-page__tab-dot"></view>
<view v-if="mainTab === 'market'" class="coupon-page__tab-line"></view>
</view>
</view>
</view>
<!-- 二级筛选栏(仅在「我的卡券」显示:全部 / 未使用 / 已使用 / 已过期) -->
<view v-if="mainTab === 'mine'" class="coupon-page__sub-filter">
<view
v-for="f in subFilters"
:key="f.key"
class="coupon-page__filter-chip"
:class="{ 'coupon-page__filter-chip--active': subStatus === f.key }"
@click="switchSubStatus(f.key)"
>
<text class="coupon-page__filter-chip-text">{{ f.label }}</text>
<text v-if="f.count > 0" class="coupon-page__filter-chip-num">({{ f.count }})</text>
</view>
</view>
<!-- 卡券列表主体容器 -->
<view class="coupon-page__body">
<!-- 骨架/加载占位 -->
<view v-if="loading" class="coupon-page__loading">
<text class="coupon-page__loading-text">正在加载优惠券...</text>
</view>
<!-- 1. 我的卡券列表 -->
<view v-else-if="mainTab === 'mine'">
<view v-if="filteredMyCoupons.length > 0" class="coupon-list">
<view
v-for="item in filteredMyCoupons"
:key="item.id"
class="coupon-card"
:class="[`coupon-card--${item.status}`]"
>
<!-- 卡片左侧:金额与门槛 -->
<view class="coupon-card__left">
<view class="coupon-card__amount-row">
<text class="coupon-card__currency">¥</text>
<text class="coupon-card__amount">{{ formatAmount(item.amount) }}</text>
</view>
<text class="coupon-card__condition">
{{ item.minAmount > 0 ? `满${item.minAmount}元可用` : '无门槛' }}
</text>
</view>
<!-- 中间齿孔分隔 -->
<view class="coupon-card__divider">
<view class="coupon-card__hole coupon-card__hole--top"></view>
<view class="coupon-card__dash"></view>
<view class="coupon-card__hole coupon-card__hole--bottom"></view>
</view>
<!-- 卡片右侧:信息与操作 -->
<view class="coupon-card__right">
<view class="coupon-card__info">
<view class="coupon-card__title-row">
<text class="coupon-card__tag">{{ getCouponTypeLabel(item.type) }}</text>
<text class="coupon-card__title fc-ellipsis">{{ item.name || '通用优惠券' }}</text>
</view>
<text class="coupon-card__date">
{{ item.expireAt ? `有效期至 ${formatDate(item.expireAt)}` : '永久有效' }}
</text>
</view>
<!-- 右侧操作/状态印章 -->
<view class="coupon-card__action">
<!-- 未使用态:去使用按钮(春绿底墨绿字) -->
<view
v-if="item.status === 'unused'"
class="coupon-card__btn coupon-card__btn--use"
@click="goUseCoupon(item)"
>
<text class="coupon-card__btn-text">去使用</text>
</view>
<!-- 已使用印章 -->
<view v-else-if="item.status === 'used'" class="coupon-card__stamp coupon-card__stamp--used">
<text class="coupon-card__stamp-text">已使用</text>
</view>
<!-- 已过期印章 -->
<view v-else class="coupon-card__stamp coupon-card__stamp--expired">
<text class="coupon-card__stamp-text">已过期</text>
</view>
</view>
</view>
</view>
</view>
<!-- 我的卡券空状态 -->
<fc-empty
v-else
icon="inbox"
:text="emptyTextMine"
btn-text="去领券中心"
@action="switchMainTab('market')"
/>
</view>
<!-- 2. 领券中心列表 -->
<view v-else-if="mainTab === 'market'">
<view v-if="marketCoupons.length > 0" class="coupon-list">
<view
v-for="c in marketCoupons"
:key="c.id"
class="coupon-card"
:class="{ 'coupon-card--received': c.received || c.left === 0 }"
>
<!-- 卡片左侧:面额与门槛 -->
<view class="coupon-card__left">
<view class="coupon-card__amount-row">
<text class="coupon-card__currency">¥</text>
<text class="coupon-card__amount">{{ formatAmount(c.amount) }}</text>
</view>
<text class="coupon-card__condition">
{{ c.minAmount > 0 ? `满${c.minAmount}元可用` : '无门槛' }}
</text>
</view>
<!-- 中间齿孔分隔 -->
<view class="coupon-card__divider">
<view class="coupon-card__hole coupon-card__hole--top"></view>
<view class="coupon-card__dash"></view>
<view class="coupon-card__hole coupon-card__hole--bottom"></view>
</view>
<!-- 卡片右侧:券信息与领取按钮 -->
<view class="coupon-card__right">
<view class="coupon-card__info">
<view class="coupon-card__title-row">
<text class="coupon-card__tag">{{ getCouponTypeLabel(c.type) }}</text>
<text class="coupon-card__title fc-ellipsis">{{ c.name || '通用优惠券' }}</text>
</view>
<view class="coupon-card__meta-row">
<text class="coupon-card__date">
{{ c.days > 0 ? `领取后 ${c.days} 天内有效` : '永久有效' }}
</text>
<text v-if="c.left > 0" class="coupon-card__stock">仅剩{{ c.left }}张</text>
</view>
</view>
<view class="coupon-card__action">
<!-- 已领取 -->
<view v-if="c.received" class="coupon-card__btn coupon-card__btn--disabled">
<text class="coupon-card__btn-text--disabled">已领取</text>
</view>
<!-- 已抢光 -->
<view v-else-if="c.left === 0" class="coupon-card__btn coupon-card__btn--disabled">
<text class="coupon-card__btn-text--disabled">已抢光</text>
</view>
<!-- 立即领取按钮(亮绿底墨绿字) -->
<view
v-else
class="coupon-card__btn coupon-card__btn--receive"
:class="{ 'coupon-card__btn--loading': receivingId === c.id }"
@click="handleReceive(c)"
>
<text class="coupon-card__btn-text">
{{ receivingId === c.id ? '领取中' : '立即领取' }}
</text>
</view>
</view>
</view>
</view>
</view>
<!-- 领券中心空状态 -->
<fc-empty
v-else
icon="inbox"
text="暂无可领取的优惠券,稍后再来瞧瞧吧"
/>
</view>
</view>
</view>
</template>
<script>
import FcNavbar from '@/components/fc-navbar/fc-navbar.vue'
import FcEmpty from '@/components/fc-empty/fc-empty.vue'
import couponApi from '@/common/api/coupon.js'
import memberApi from '@/common/api/member.js'
import { useUserStore } from '@/common/store/index.js'
export default {
name: 'CouponsPage',
components: {
FcNavbar,
FcEmpty,
},
data() {
return {
/** 当前主 Tab:mine(我的卡券) | market(领券中心) */
mainTab: 'mine',
/** 我的卡券二级筛选状态:all(全部) | unused(未使用) | used(已使用) | expired(已过期) */
subStatus: 'unused',
/** 数据加载状态 */
loading: false,
/** 正在领取的券ID */
receivingId: null,
/** 用户拥有的卡券列表 */
myCoupons: [],
/** 领券中心的券模板列表 */
marketCoupons: [],
}
},
computed: {
/**
* 检查当前是否登录
* @returns {boolean}
*/
isLogin() {
try {
return useUserStore().isLogin.value
} catch (e) {
return false
}
},
/**
* 我的可用卡券总数
* @returns {number}
*/
myCouponCount() {
return this.myCoupons.filter((c) => c.status === 'unused').length
},
/**
* 领券中心剩余可领取数量标记
* @returns {number}
*/
marketAvailableCount() {
return this.marketCoupons.filter((c) => !c.received && (c.left === -1 || c.left > 0)).length
},
/**
* 我的卡券二级分类配置及各状态计数
* @returns {Array<{key: string, label: string, count: number}>}
*/
subFilters() {
const unusedCount = this.myCoupons.filter((c) => c.status === 'unused').length
const usedCount = this.myCoupons.filter((c) => c.status === 'used').length
const expiredCount = this.myCoupons.filter((c) => c.status === 'expired').length
return [
{ key: 'unused', label: '未使用', count: unusedCount },
{ key: 'all', label: '全部', count: this.myCoupons.length },
{ key: 'used', label: '已使用', count: usedCount },
{ key: 'expired', label: '已过期', count: expiredCount },
]
},
/**
* 经过二级分类筛选后的卡券列表
* @returns {Array}
*/
filteredMyCoupons() {
if (this.subStatus === 'all') {
return this.myCoupons
}
return this.myCoupons.filter((c) => c.status === this.subStatus)
},
/**
* 我的卡券空状态提示语
* @returns {string}
*/
emptyTextMine() {
if (this.subStatus === 'unused') return '暂无未使用的优惠券'
if (this.subStatus === 'used') return '暂无已使用的优惠券记录'
if (this.subStatus === 'expired') return '暂无过期的优惠券'
return '您还没有优惠券,快去领取专属福利吧'
},
},
/**
* 页面加载生命周期
* @param {Object} options 页面传参,可指定 tab=mine|market
*/
onLoad(options) {
if (options && options.tab === 'market') {
this.mainTab = 'market'
}
this.initData()
},
/**
* 下拉刷新生命周期
*/
async onPullDownRefresh() {
await this.initData()
uni.stopPullDownRefresh()
},
methods: {
/**
* 初始化加载所有卡券数据
*/
async initData() {
this.loading = true
try {
await Promise.all([this.loadMyCoupons(), this.loadMarketCoupons()])
} finally {
this.loading = false
}
},
/**
* 加载我的卡券资产
*/
async loadMyCoupons() {
if (!this.isLogin) {
this.myCoupons = []
return
}
try {
const res = await memberApi.assets()
this.myCoupons = (res && Array.isArray(res.coupons)) ? res.coupons : []
} catch (err) {
this.myCoupons = []
}
},
/**
* 加载领券中心公开列表
*/
async loadMarketCoupons() {
try {
const res = await couponApi.list({ page: 1, size: 50 })
this.marketCoupons = (res && Array.isArray(res.list)) ? res.list : []
} catch (err) {
this.marketCoupons = []
}
},
/**
* 切换一级主 Tab
* @param {'mine'|'market'} tab
*/
switchMainTab(tab) {
this.mainTab = tab
},
/**
* 切换二级状态筛选
* @param {'all'|'unused'|'used'|'expired'} key
*/
switchSubStatus(key) {
this.subStatus = key
},
/**
* 点击去使用优惠券
* @param {Object} item 优惠券对象
*/
goUseCoupon(item) {
uni.showActionSheet({
itemList: ['发布同城信息(可用券抵扣)', '浏览最新同城信息'],
success: (res) => {
if (res.tapIndex === 0) {
uni.navigateTo({ url: '/pages/post/create' })
} else {
uni.switchTab({ url: '/pages/index/index' })
}
},
})
},
/**
* 领取优惠券操作
* @param {Object} c 券对象
*/
async handleReceive(c) {
if (!this.isLogin) {
uni.showToast({ title: '请先登录后再领券', icon: 'none' })
setTimeout(() => {
uni.navigateTo({ url: '/pages/auth/login' })
}, 600)
return
}
if (this.receivingId) return
this.receivingId = c.id
try {
await couponApi.receive(c.id)
uni.showToast({ title: '领取成功!', icon: 'success' })
// 标记已领取
c.received = true
if (c.left > 0) c.left -= 1
// 重新静默拉取我的资产
this.loadMyCoupons()
} catch (e) {
// 提示已由 request 拦截层处理或直接提示
uni.showToast({ title: (e && e.message) || '领取失败,请重试', icon: 'none' })
} finally {
this.receivingId = null
}
},
/**
* 格式化金额数值
* @param {number|string} val
* @returns {string}
*/
formatAmount(val) {
const n = Number(val)
if (isNaN(n)) return '0'
return Number.isInteger(n) ? String(n) : n.toFixed(1)
},
/**
* 格式化时间字符串至年月日
* @param {string} dateStr
* @returns {string}
*/
formatDate(dateStr) {
if (!dateStr) return ''
return String(dateStr).slice(0, 10)
},
/**
* 解析优惠券类型文案
* @param {string} type
* @returns {string}
*/
getCouponTypeLabel(type) {
if (type === 'discount') return '折扣券'
if (type === 'cash') return '现金抵扣'
return '满减券'
},
},
}
</script>
<style lang="scss" scoped>
/* 页面根容器 */
.coupon-page {
min-height: 100vh;
background-color: $fc-bg;
position: relative;
padding-bottom: 60rpx;
}
/* 顶部沉浸底座 */
.coupon-page__backdrop {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 380rpx;
background: $fc-gradient-hero;
overflow: hidden;
z-index: 0;
pointer-events: none;
}
.coupon-page__backdrop-ambient {
position: absolute;
top: -60rpx;
right: -60rpx;
width: 320rpx;
height: 320rpx;
border-radius: 50%;
background: radial-gradient(circle, rgba(0, 224, 112, 0.28) 0%, rgba(0, 224, 112, 0) 70%);
filter: blur(20px);
}
/* 主标签页切换条 */
.coupon-page__tabs-wrap {
position: relative;
z-index: 2;
padding: 16rpx 32rpx 0;
}
.coupon-page__tabs {
display: flex;
align-items: center;
justify-content: space-around;
height: 88rpx;
}
.coupon-page__tab-item {
position: relative;
display: flex;
align-items: center;
padding: 12rpx 24rpx;
}
.coupon-page__tab-text {
font-size: 32rpx;
font-weight: 500;
color: $fc-text-secondary;
transition: all 0.2s ease;
}
.coupon-page__tab-item--active .coupon-page__tab-text {
font-size: 34rpx;
font-weight: 700;
color: $fc-primary-text;
}
.coupon-page__tab-badge {
margin-left: 8rpx;
padding: 2rpx 12rpx;
border-radius: 20rpx;
background-color: $fc-accent;
color: #ffffff;
font-size: 20rpx;
font-weight: 700;
line-height: 28rpx;
}
.coupon-page__tab-dot {
width: 12rpx;
height: 12rpx;
border-radius: 50%;
background-color: $fc-primary;
margin-left: 8rpx;
}
.coupon-page__tab-line {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 48rpx;
height: 6rpx;
border-radius: 4rpx;
background-color: $fc-primary;
}
/* 二级筛选栏(胶囊筛选) */
.coupon-page__sub-filter {
position: relative;
z-index: 2;
display: flex;
align-items: center;
padding: 20rpx 32rpx 10rpx;
gap: 16rpx;
overflow-x: auto;
}
.coupon-page__filter-chip {
display: flex;
align-items: center;
padding: 10rpx 24rpx;
border-radius: 32rpx;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(8px);
border: 1rpx solid rgba(255, 255, 255, 0.25);
transition: all 0.2s ease;
}
.coupon-page__filter-chip--active {
background-color: #ffffff;
border-color: #ffffff;
box-shadow: 0 4rpx 12rpx rgba(4, 48, 30, 0.1);
}
.coupon-page__filter-chip-text {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.9);
font-weight: 500;
}
.coupon-page__filter-chip--active .coupon-page__filter-chip-text {
color: $fc-primary-text;
font-weight: 600;
}
.coupon-page__filter-chip-num {
font-size: 22rpx;
color: rgba(255, 255, 255, 0.8);
margin-left: 6rpx;
}
.coupon-page__filter-chip--active .coupon-page__filter-chip-num {
color: $fc-primary-text;
}
/* 内容主体 */
.coupon-page__body {
position: relative;
z-index: 2;
padding: 20rpx 28rpx;
}
/* 加载中状态 */
.coupon-page__loading {
padding: 100rpx 0;
text-align: center;
}
.coupon-page__loading-text {
font-size: 26rpx;
color: rgba(255, 255, 255, 0.8);
}
/* 卡券列表 */
.coupon-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
/* 经典打孔卡券卡片 */
.coupon-card {
display: flex;
height: 196rpx;
background-color: #ffffff;
border-radius: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(4, 48, 30, 0.05);
overflow: hidden;
position: relative;
transition: all 0.2s ease;
}
/* 卡片左侧:阳光金价值面额 */
.coupon-card__left {
width: 200rpx;
background: linear-gradient(135deg, #fffcf5 0%, #fff7e6 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16rpx 12rpx;
flex-shrink: 0;
}
.coupon-card__amount-row {
display: flex;
align-items: baseline;
color: $fc-accent;
}
.coupon-card__currency {
font-size: 26rpx;
font-weight: 700;
margin-right: 2rpx;
}
.coupon-card__amount {
font-size: 54rpx;
font-weight: 800;
line-height: 1;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.coupon-card__condition {
margin-top: 10rpx;
font-size: 20rpx;
color: $fc-accent-dark;
background-color: rgba(245, 166, 35, 0.12);
padding: 2rpx 12rpx;
border-radius: 12rpx;
text-align: center;
}
/* 中间打孔虚线 */
.coupon-card__divider {
width: 24rpx;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #ffffff;
flex-shrink: 0;
}
.coupon-card__hole {
position: absolute;
width: 24rpx;
height: 24rpx;
border-radius: 50%;
background-color: $fc-bg;
left: 0;
z-index: 2;
}
.coupon-card__hole--top {
top: -12rpx;
}
.coupon-card__hole--bottom {
bottom: -12rpx;
}
.coupon-card__dash {
height: 120rpx;
border-left: 2rpx dashed #e2e8e4;
}
/* 卡片右侧:详情与操作 */
.coupon-card__right {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx 20rpx 16rpx;
min-width: 0;
}
.coupon-card__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
padding-right: 16rpx;
}
.coupon-card__title-row {
display: flex;
align-items: center;
gap: 10rpx;
margin-bottom: 12rpx;
}
.coupon-card__tag {
font-size: 20rpx;
color: $fc-primary-ink;
background: $fc-primary;
padding: 2rpx 10rpx;
border-radius: 6rpx;
font-weight: 600;
flex-shrink: 0;
}
.coupon-card__title {
font-size: 28rpx;
font-weight: 700;
color: $fc-text;
}
.coupon-card__date {
font-size: 22rpx;
color: $fc-text-placeholder;
}
.coupon-card__meta-row {
display: flex;
align-items: center;
gap: 12rpx;
}
.coupon-card__stock {
font-size: 20rpx;
color: $fc-accent;
background: $fc-accent-lighter;
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
/* 操作区域 */
.coupon-card__action {
flex-shrink: 0;
}
/* 按钮通用 */
.coupon-card__btn {
padding: 14rpx 30rpx;
border-radius: 32rpx;
display: flex;
align-items: center;
justify-content: center;
min-width: 140rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 224, 112, 0.2);
transition: all 0.2s ease;
}
.coupon-card__btn:active {
transform: scale(0.96);
}
/* 亮绿主按钮:规范严格执行亮绿底用深墨绿字 */
.coupon-card__btn--use,
.coupon-card__btn--receive {
background: $fc-gradient-primary;
}
.coupon-card__btn-text {
font-size: 24rpx;
font-weight: 700;
color: $fc-primary-ink; /* 亮绿底配深墨绿字,符合高对比度规范 */
}
/* 禁用态按钮 */
.coupon-card__btn--disabled {
background: #f0f3f1;
box-shadow: none;
pointer-events: none;
}
.coupon-card__btn-text--disabled {
font-size: 24rpx;
color: #9eb1a7;
font-weight: 500;
}
/* 状态印章(微倾斜盖章质感) */
.coupon-card__stamp {
width: 110rpx;
height: 110rpx;
border-radius: 50%;
border: 3rpx dashed #b8c7bf;
display: flex;
align-items: center;
justify-content: center;
transform: rotate(-15deg);
opacity: 0.6;
}
.coupon-card__stamp-text {
font-size: 22rpx;
font-weight: 700;
color: #7b9286;
}
.coupon-card__stamp--used {
border-color: #8fa398;
}
.coupon-card__stamp--expired {
border-color: #d1b8b8;
}
.coupon-card__stamp--expired .coupon-card__stamp-text {
color: #a88d8d;
}
/* 针对已失效或已领取的弱化卡面 */
.coupon-card--used,
.coupon-card--expired {
opacity: 0.72;
}
.coupon-card--used .coupon-card__left,
.coupon-card--expired .coupon-card__left {
background: #f7f9f8;
}
.coupon-card--used .coupon-card__amount-row,
.coupon-card--expired .coupon-card__amount-row {
color: #8c9e95;
}
.coupon-card--used .coupon-card__condition,
.coupon-card--expired .coupon-card__condition {
color: #8c9e95;
background-color: #ecefe5;
}
.coupon-card--used .coupon-card__tag,
.coupon-card--expired .coupon-card__tag {
background: #e2e8e4;
color: #63776d;
}
</style>