bindbox-mini/components/activity/PrizeClaimPopup.vue
Zuncle d530ec11e7 feat(activity): 新增小程序奖品领取弹窗
新增首页承载的奖品领取弹窗与领取接口接入,支持待领取检查、会话静默关闭与领取操作展示。
2026-05-07 22:10:15 +08:00

290 lines
6.1 KiB
Vue
Raw Permalink 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 v-if="visible && activity" class="prize-claim-overlay" @touchmove.stop.prevent>
<view class="prize-claim-mask" @tap="handleClose"></view>
<view class="prize-claim-panel" @tap.stop>
<view class="prize-claim-hero">
<view class="hero-glow"></view>
<view class="hero-top">
<view class="hero-badge">奖励发放</view>
<text class="prize-claim-close" @tap="handleClose">×</text>
</view>
<view class="hero-content">
<text class="hero-title">奖励已到账待你领取</text>
<text class="hero-reason">{{ activity.reason }}</text>
</view>
</view>
<view class="prize-claim-body">
<view class="section-title">奖品内容</view>
<view class="reward-list">
<view
v-for="(item, index) in activity.rewards || []"
:key="`${item.reward_type}-${item.reward_ref_id}-${index}`"
class="reward-item"
>
<view class="reward-thumb-wrap">
<image v-if="item.image" class="reward-thumb" :src="item.image" mode="aspectFill" />
<view v-else class="reward-thumb-empty">{{ typeShortLabel(item.reward_type) }}</view>
</view>
<view class="reward-main">
<text class="reward-name">{{ item.name || item.reward_type }}</text>
<view class="reward-meta-row">
<text class="reward-type">{{ typeLabel(item.reward_type) }}</text>
<text v-if="item.value_cents > 0" class="reward-value">单价 ¥{{ (item.value_cents / 100).toFixed(2) }}</text>
</view>
</view>
<view class="reward-side">
<text class="reward-quantity">x{{ item.quantity }}</text>
</view>
</view>
</view>
</view>
<view class="prize-claim-footer">
<button class="claim-button" :disabled="loading" @tap="handleClaim">
{{ loading ? '领取中...' : '立即领取' }}
</button>
</view>
</view>
</view>
</template>
<script setup>
const props = defineProps({
visible: { type: Boolean, default: false },
activity: { type: Object, default: null },
loading: { type: Boolean, default: false }
})
const emit = defineEmits(['update:visible', 'claim', 'close'])
function handleClose() {
emit('close')
emit('update:visible', false)
}
function handleClaim() {
if (!props.loading) emit('claim')
}
function typeLabel(type) {
if (type === 'product') return '商品'
if (type === 'coupon') return '优惠券'
if (type === 'item_card') return '道具卡'
return type
}
function typeShortLabel(type) {
if (type === 'product') return '商品'
if (type === 'coupon') return '券'
if (type === 'item_card') return '卡'
return '奖'
}
</script>
<style lang="scss" scoped>
.prize-claim-overlay {
position: fixed;
inset: 0;
z-index: 1001;
display: flex;
align-items: center;
justify-content: center;
}
.prize-claim-mask {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
backdrop-filter: blur(6rpx);
}
.prize-claim-panel {
position: relative;
width: 88%;
max-height: 78vh;
background: $bg-card;
border-radius: $radius-xl;
overflow: hidden;
box-shadow: $shadow-lg;
animation: slideUp 0.25s ease-out;
}
.prize-claim-hero {
position: relative;
padding: $spacing-lg $spacing-lg $spacing-xl;
background: linear-gradient(135deg, $brand-primary 0%, $brand-primary-light 100%);
}
.hero-glow {
position: absolute;
right: -80rpx;
top: -80rpx;
width: 260rpx;
height: 260rpx;
border-radius: 50%;
background: rgba(255, 255, 255, 0.16);
}
.hero-top {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-lg;
}
.hero-badge {
padding: 8rpx 18rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.18);
color: #fff;
font-size: $font-xs;
font-weight: 700;
}
.prize-claim-close {
color: rgba(255, 255, 255, 0.9);
font-size: 48rpx;
line-height: 1;
}
.hero-content {
position: relative;
}
.hero-title {
display: block;
font-size: 40rpx;
font-weight: 700;
color: #fff;
line-height: 1.3;
margin-bottom: 12rpx;
}
.hero-reason {
display: block;
font-size: $font-md;
color: rgba(255, 255, 255, 0.92);
line-height: 1.6;
}
.prize-claim-body {
padding: $spacing-lg;
}
.section-title {
font-size: $font-md;
font-weight: 700;
color: $text-main;
margin-bottom: $spacing-md;
}
.reward-list {
display: flex;
flex-direction: column;
gap: $spacing-md;
}
.reward-item {
display: flex;
align-items: center;
gap: $spacing-md;
background: $bg-page;
border-radius: $radius-lg;
padding: $spacing-md;
}
.reward-thumb-wrap {
width: 112rpx;
height: 112rpx;
border-radius: $radius-md;
overflow: hidden;
flex-shrink: 0;
background: #fff;
box-shadow: $shadow-sm;
}
.reward-thumb {
width: 100%;
height: 100%;
}
.reward-thumb-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: $font-sm;
color: $text-sub;
background: rgba($brand-primary, 0.08);
}
.reward-main {
flex: 1;
display: flex;
flex-direction: column;
gap: 10rpx;
min-width: 0;
}
.reward-name {
font-size: $font-md;
color: $text-main;
font-weight: 700;
line-height: 1.4;
}
.reward-meta-row {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.reward-type,
.reward-value,
.reward-quantity {
font-size: $font-sm;
color: $text-sub;
}
.reward-value {
color: $brand-primary-dark;
font-weight: 600;
}
.reward-side {
display: flex;
align-items: center;
justify-content: center;
min-width: 64rpx;
}
.reward-quantity {
font-size: $font-md;
font-weight: 700;
color: $brand-primary;
}
.prize-claim-footer {
padding: 0 $spacing-lg $spacing-lg;
}
.claim-button {
width: 100%;
border: none;
border-radius: $radius-round;
background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
color: #fff;
font-size: $font-md;
font-weight: 700;
padding: 24rpx 0;
box-shadow: $shadow-warm;
}
.claim-button[disabled] {
opacity: 0.65;
}
</style>