任务大厅,限量显示

This commit is contained in:
邹方成 2026-02-19 19:55:00 +08:00
parent b97cd0f267
commit 4fe3ecb571
3 changed files with 117 additions and 45 deletions

View File

@ -11,17 +11,13 @@
<!-- Tab栏 - 毛玻璃风格 -->
<view class="tab-bar glass-card">
<view class="tab-item" :class="{ active: currentTab === 1 }" @click="switchTab(1)">
<text class="tab-text">未使用</text>
<text class="tab-text">有效</text>
<view class="tab-indicator" v-if="currentTab === 1"></view>
</view>
<view class="tab-item" :class="{ active: currentTab === 2 }" @click="switchTab(2)">
<text class="tab-text">使用</text>
<text class="tab-text">失效</text>
<view class="tab-indicator" v-if="currentTab === 2"></view>
</view>
<view class="tab-item" :class="{ active: currentTab === 3 }" @click="switchTab(3)">
<text class="tab-text">已过期</text>
<view class="tab-indicator" v-if="currentTab === 3"></view>
</view>
</view>
<!-- 内容区 -->
@ -51,7 +47,7 @@
v-for="(item, index) in list"
:key="item.id || index"
class="coupon-ticket"
:class="getCouponClass()"
:class="getCouponClass(item)"
:style="{ animationDelay: `${index * 0.05}s` }"
>
<!-- 左侧金额区域 -->
@ -60,7 +56,7 @@
<text class="coupon-symbol">¥</text>
<text class="coupon-amount">{{ formatValue(item.remaining ?? item.amount ?? 0) }}</text>
</view>
<text class="coupon-label">{{ currentTab === 1 ? '可用' : (currentTab === 2 ? '已用' : '过期') }}</text>
<text class="coupon-label" :class="getLabelClass(item)">{{ item.status_desc || '可用' }}</text>
</view>
<!-- 中间分割线 -->
@ -105,7 +101,7 @@
</view>
</view>
<view class="coupon-status" v-else>
<text class="status-tag" :class="currentTab === 2 ? 'used' : 'expired'">{{ currentTab === 2 ? '已使用' : '已过期' }}</text>
<text class="status-tag" :class="getStatusTagClass(item)">{{ item.status_desc || '已失效' }}</text>
</view>
</view>
</view>
@ -224,17 +220,32 @@ function getUsedPercent(item) {
//
function getEmptyText() {
if (currentTab.value === 1) return '暂无可用优惠券'
if (currentTab.value === 2) return '暂无使用记录'
return '暂无过期优惠券'
return '暂无失效优惠券'
}
//
function getCouponClass() {
if (currentTab.value === 2) return 'coupon-used'
if (currentTab.value === 3) return 'coupon-expired'
function getCouponClass(item) {
const sub = item?.sub_status || ''
if (currentTab.value === 2) return 'coupon-invalid'
if (sub === 'in_use') return 'coupon-in-use'
return ''
}
//
function getLabelClass(item) {
const sub = item?.sub_status || ''
if (sub === 'in_use') return 'label-in-use'
if (sub === 'unused') return 'label-unused'
return 'label-invalid'
}
// Tab
function getStatusTagClass(item) {
const sub = item?.sub_status || ''
if (sub === 'expired') return 'expired'
return 'used'
}
// Tab
function switchTab(tab) {
if (currentTab.value === tab) return
@ -270,8 +281,8 @@ async function fetchData(append = false) {
try {
const userId = getUserId()
// status: 0=unused, 1=used, 2=expired
const statusMap = { 1: 0, 2: 1, 3: 2 }
const res = await getUserCoupons(userId, statusMap[currentTab.value], page.value, pageSize)
// status: 1=, 2=
const res = await getUserCoupons(userId, currentTab.value, page.value, pageSize)
const items = res.list || res.data || []
if (append) {
@ -555,13 +566,26 @@ onLoad(() => {
.coupon-label {
font-size: 20rpx;
color: $brand-primary;
margin-top: 8rpx;
border: 1px solid $brand-primary;
padding: 2rpx 8rpx;
border-radius: 6rpx;
}
.label-unused {
color: $brand-primary;
border: 1px solid $brand-primary;
}
.label-in-use {
color: #FF8D3F;
border: 1px solid #FF8D3F;
}
.label-invalid {
color: $text-tertiary;
border: 1px solid $text-tertiary;
}
/* 分割线 */
.coupon-divider {
width: 30rpx;
@ -763,25 +787,25 @@ onLoad(() => {
margin-left: auto;
}
/* 过期/已使用状态 */
.coupon-used .coupon-left,
.coupon-expired .coupon-left {
/* 已失效/使用中状态 */
.coupon-invalid .coupon-left {
background: #f9f9f9;
}
.coupon-used .coupon-value,
.coupon-expired .coupon-value,
.coupon-used .coupon-label,
.coupon-expired .coupon-label {
.coupon-invalid .coupon-value,
.coupon-invalid .coupon-label {
color: $text-tertiary;
border-color: $text-tertiary;
}
.coupon-used .coupon-name,
.coupon-expired .coupon-name {
.coupon-invalid .coupon-name {
color: $text-sub;
}
.coupon-in-use .coupon-left {
background: linear-gradient(135deg, #FFF3E0, #fff);
}
/* 加载更多 */
.loading-more {
display: flex;

View File

@ -87,6 +87,12 @@
</view>
</view>
<view class="task-status-wrap">
<view v-if="task.quota > 0 && task.quota <= task.claimed_count" class="task-quota-tag exhausted">
已领完
</view>
<view v-else-if="task.quota > 0" class="task-quota-tag">
剩余 {{ task.quota - task.claimed_count }}
</view>
<view class="task-status" :class="getTaskStatusClass(task)">
{{ getTaskStatusText(task) }}
</view>
@ -117,7 +123,11 @@
<view v-if="isTierClaimed(task.id, tier.id)" class="tier-btn claimed">
<text>已领取</text>
</view>
<!-- 已领完 (限量逻辑) -->
<!-- 已领完 (任务级限量逻辑) -->
<view v-else-if="task.quota > 0 && task.quota <= task.claimed_count" class="tier-btn disabled">
<text>已领完</text>
</view>
<!-- 已领完 (档位级限量逻辑) -->
<view v-else-if="tier.quota > 0 && tier.remaining === 0" class="tier-btn disabled">
<text>已领完</text>
</view>
@ -215,6 +225,9 @@ function getTaskStatusClass(task) {
const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return 'status-done'
//
if (task.quota > 0 && task.quota <= task.claimed_count) return 'status-done'
//
if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return 'status-claimable'
@ -232,6 +245,9 @@ function getTaskStatusText(task) {
const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return '已完成'
//
if (task.quota > 0 && task.quota <= task.claimed_count) return '已领完'
if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return '可领取'
}
@ -811,6 +827,22 @@ function getSubProgressWidth(sub, task) {
align-items: center;
flex-shrink: 0;
margin-left: 16rpx;
gap: 8rpx;
}
.task-quota-tag {
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 100rpx;
background: rgba(#ff9500, 0.1);
color: #ff9500;
font-weight: 600;
white-space: nowrap;
&.exhausted {
background: rgba(0, 0, 0, 0.05);
color: $text-tertiary;
}
}
.task-status {

View File

@ -253,10 +253,9 @@
<text class="close-btn" @tap="closeCouponsPopup">×</text>
</view>
<view class="popup-tabs coupon-tabs-3">
<view class="popup-tab" :class="{ active: couponsTab === 1 }" @tap="switchCouponsTab(1)">未使用</view>
<view class="popup-tab" :class="{ active: couponsTab === 2 }" @tap="switchCouponsTab(2)">已使用</view>
<view class="popup-tab" :class="{ active: couponsTab === 3 }" @tap="switchCouponsTab(3)">已过期</view>
<view class="popup-tabs coupon-tabs-2">
<view class="popup-tab" :class="{ active: couponsTab === 1 }" @tap="switchCouponsTab(1)">有效</view>
<view class="popup-tab" :class="{ active: couponsTab === 2 }" @tap="switchCouponsTab(2)">已失效</view>
</view>
<scroll-view scroll-y class="coupon-scroll" @scrolltolower="loadMoreCoupons">
@ -273,7 +272,7 @@
<text class="coupon-symbol">¥</text>
<text class="coupon-amount-num">{{ formatCouponValue(item.remaining ?? item.amount ?? 0) }}</text>
</view>
<text class="coupon-label">{{ couponsTab === 1 ? '可用' : (couponsTab === 2 ? '已用' : '过期') }}</text>
<text class="coupon-label" :class="getCouponLabelClass(item)">{{ item.status_desc || '可用' }}</text>
</view>
<!-- 中间分割线 -->
@ -313,7 +312,7 @@
<view class="use-btn-v2">去使用</view>
</view>
<view class="coupon-status-v2" v-else>
<text class="status-tag" :class="couponsTab === 2 ? 'used' : 'expired'">{{ couponsTab === 2 ? '已使用' : '已过期' }}</text>
<text class="status-tag" :class="getCouponStatusTagClass(item)">{{ item.status_desc || '已失效' }}</text>
</view>
</view>
</view>
@ -1181,8 +1180,8 @@ export default {
this.couponsLoading = true
try {
// status: 0=unused, 1=used, 2=expired
const statusMap = { 1: 0, 2: 1, 3: 2 }
const res = await getUserCoupons(this.userId, statusMap[this.couponsTab], this.couponsPage)
// status: 1=, 2=
const res = await getUserCoupons(this.userId, this.couponsTab, this.couponsPage)
const list = res.list || res.data || []
if (list.length < 10) this.couponsHasMore = false
this.couponsList = [...this.couponsList, ...list]
@ -1195,14 +1194,25 @@ export default {
},
getCouponEmptyText() {
if (this.couponsTab === 1) return '暂无可用优惠券'
if (this.couponsTab === 2) return '暂无使用记录'
return '暂无过期优惠券'
return '暂无失效优惠券'
},
getCouponCardClass(item) {
if (this.couponsTab === 2) return 'coupon-used'
if (this.couponsTab === 3) return 'coupon-expired'
if (this.couponsTab === 2) return 'coupon-invalid'
const sub = item?.sub_status || ''
if (sub === 'in_use') return 'coupon-in-use'
return ''
},
getCouponLabelClass(item) {
const sub = item?.sub_status || ''
if (sub === 'in_use') return 'label-in-use'
if (sub === 'unused') return 'label-unused'
return 'label-invalid'
},
getCouponStatusTagClass(item) {
const sub = item?.sub_status || ''
if (sub === 'expired') return 'expired'
return 'used'
},
formatCouponValue(val) {
return (Number(val) / 100).toFixed(0)
},
@ -2046,18 +2056,24 @@ export default {
.status-tag { font-size: 22rpx; color: $text-tertiary; background: #F5F5F5; padding: 4rpx 12rpx; border-radius: 6rpx; }
.coupon-used-time { font-size: 18rpx; color: $text-tertiary; margin-top: 4rpx; text-align: left; }
/* 过期/已使用状态 */
.coupon-used .coupon-left-v2, .coupon-expired .coupon-left-v2 {
/* 失效/使用中状态 */
.coupon-invalid .coupon-left-v2 {
background: #f9f9f9;
}
.coupon-used .coupon-remaining, .coupon-expired .coupon-remaining,
.coupon-used .coupon-label, .coupon-expired .coupon-label {
.coupon-invalid .coupon-remaining,
.coupon-invalid .coupon-label {
color: $text-tertiary;
border-color: $text-tertiary;
}
.coupon-used .coupon-name-v2, .coupon-expired .coupon-name-v2 {
.coupon-invalid .coupon-name-v2 {
color: $text-sub;
}
.coupon-in-use .coupon-left-v2 {
background: linear-gradient(135deg, #FFF3E0, #fff);
}
.label-unused { color: $brand-primary; border: 1px solid $brand-primary; }
.label-in-use { color: #FF8D3F; border: 1px solid #FF8D3F; }
.label-invalid { color: $text-tertiary; border: 1px solid $text-tertiary; }
/* 道具卡 */
.item-cards-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx; }