任务大厅,限量显示

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

View File

@ -87,6 +87,12 @@
</view> </view>
</view> </view>
<view class="task-status-wrap"> <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)"> <view class="task-status" :class="getTaskStatusClass(task)">
{{ getTaskStatusText(task) }} {{ getTaskStatusText(task) }}
</view> </view>
@ -117,7 +123,11 @@
<view v-if="isTierClaimed(task.id, tier.id)" class="tier-btn claimed"> <view v-if="isTierClaimed(task.id, tier.id)" class="tier-btn claimed">
<text>已领取</text> <text>已领取</text>
</view> </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"> <view v-else-if="tier.quota > 0 && tier.remaining === 0" class="tier-btn disabled">
<text>已领完</text> <text>已领完</text>
</view> </view>
@ -215,6 +225,9 @@ function getTaskStatusClass(task) {
const allClaimed = allTiers.every(t => progress.includes(t.id)) const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return 'status-done' 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))) { if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return 'status-claimable' return 'status-claimable'
@ -232,6 +245,9 @@ function getTaskStatusText(task) {
const allClaimed = allTiers.every(t => progress.includes(t.id)) const allClaimed = allTiers.every(t => progress.includes(t.id))
if (allClaimed) return '已完成' if (allClaimed) return '已完成'
//
if (task.quota > 0 && task.quota <= task.claimed_count) return '已领完'
if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) { if (allTiers.some(t => isTierClaimable(task, t) && !progress.includes(t.id))) {
return '可领取' return '可领取'
} }
@ -811,6 +827,22 @@ function getSubProgressWidth(sub, task) {
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
margin-left: 16rpx; 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 { .task-status {

View File

@ -253,10 +253,9 @@
<text class="close-btn" @tap="closeCouponsPopup">×</text> <text class="close-btn" @tap="closeCouponsPopup">×</text>
</view> </view>
<view class="popup-tabs coupon-tabs-3"> <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 === 1 }" @tap="switchCouponsTab(1)">有效</view>
<view class="popup-tab" :class="{ active: couponsTab === 2 }" @tap="switchCouponsTab(2)">已使用</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> </view>
<scroll-view scroll-y class="coupon-scroll" @scrolltolower="loadMoreCoupons"> <scroll-view scroll-y class="coupon-scroll" @scrolltolower="loadMoreCoupons">
@ -273,7 +272,7 @@
<text class="coupon-symbol">¥</text> <text class="coupon-symbol">¥</text>
<text class="coupon-amount-num">{{ formatCouponValue(item.remaining ?? item.amount ?? 0) }}</text> <text class="coupon-amount-num">{{ formatCouponValue(item.remaining ?? item.amount ?? 0) }}</text>
</view> </view>
<text class="coupon-label">{{ couponsTab === 1 ? '可用' : (couponsTab === 2 ? '已用' : '过期') }}</text> <text class="coupon-label" :class="getCouponLabelClass(item)">{{ item.status_desc || '可用' }}</text>
</view> </view>
<!-- 中间分割线 --> <!-- 中间分割线 -->
@ -313,7 +312,7 @@
<view class="use-btn-v2">去使用</view> <view class="use-btn-v2">去使用</view>
</view> </view>
<view class="coupon-status-v2" v-else> <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> </view>
</view> </view>
@ -1181,8 +1180,8 @@ export default {
this.couponsLoading = true this.couponsLoading = true
try { try {
// status: 0=unused, 1=used, 2=expired // status: 0=unused, 1=used, 2=expired
const statusMap = { 1: 0, 2: 1, 3: 2 } // status: 1=, 2=
const res = await getUserCoupons(this.userId, statusMap[this.couponsTab], this.couponsPage) const res = await getUserCoupons(this.userId, this.couponsTab, this.couponsPage)
const list = res.list || res.data || [] const list = res.list || res.data || []
if (list.length < 10) this.couponsHasMore = false if (list.length < 10) this.couponsHasMore = false
this.couponsList = [...this.couponsList, ...list] this.couponsList = [...this.couponsList, ...list]
@ -1195,14 +1194,25 @@ export default {
}, },
getCouponEmptyText() { getCouponEmptyText() {
if (this.couponsTab === 1) return '暂无可用优惠券' if (this.couponsTab === 1) return '暂无可用优惠券'
if (this.couponsTab === 2) return '暂无使用记录' return '暂无失效优惠券'
return '暂无过期优惠券'
}, },
getCouponCardClass(item) { getCouponCardClass(item) {
if (this.couponsTab === 2) return 'coupon-used' if (this.couponsTab === 2) return 'coupon-invalid'
if (this.couponsTab === 3) return 'coupon-expired' const sub = item?.sub_status || ''
if (sub === 'in_use') return 'coupon-in-use'
return '' 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) { formatCouponValue(val) {
return (Number(val) / 100).toFixed(0) 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; } .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-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; background: #f9f9f9;
} }
.coupon-used .coupon-remaining, .coupon-expired .coupon-remaining, .coupon-invalid .coupon-remaining,
.coupon-used .coupon-label, .coupon-expired .coupon-label { .coupon-invalid .coupon-label {
color: $text-tertiary; color: $text-tertiary;
border-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; 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; } .item-cards-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx; }