bindbox-mini/components/activity/RewardsPreview.vue
Zuncle 7487e7224a feat(无限赏): 恢复奖池查看全部弹窗,新增参考价和概率总览
- 恢复无限赏页面"查看全部"按钮和 RewardsPopup 弹窗
- RewardsPopup 顶部新增按档次分类的中奖率概览条
- 奖品项显示参考价(来自后端 price_snapshot_cents)
- 每个奖品图片左下角添加档次标签(S赏/A赏/BOSS赏等)
- normalizeRewards 新增 product_price 字段提取
- 理性消费提示改为始终显示
2026-03-25 22:01:22 +08:00

279 lines
6.2 KiB
Vue
Executable File

<template>
<view>
<view class="section-header">
<text class="section-title">{{ title }}</text>
<!-- 通过 hideViewAll 控制是否显示查看全部按钮 -->
<text v-if="!hideViewAll" class="section-more" @tap="$emit('view-all')">查看全部</text>
</view>
<view class="tip-text">每抽都有概率出以下商品,盲盒消费具有随机性,请理性消费</view>
<!-- 分组展示 -->
<view v-if="grouped && rewardGroups.length > 0">
<view class="prize-level-row" v-for="group in rewardGroups" :key="group.level">
<view class="level-header-row">
<view class="level-badge" :class="{ 'badge-boss': group.level === 'BOSS' }">
{{ isMatchingGroup(group.level) ? group.level : `${group.level}赏` }}
</view>
<text class="level-prob">总概率 {{ group.totalPercent }}%</text>
</view>
<scroll-view class="preview-scroll" scroll-x>
<view class="preview-item" v-for="(item, idx) in group.rewards" :key="item.id || idx">
<view class="prize-tag tag-boss" v-if="item.boss">BOSS</view>
<image class="preview-img" :src="item.image" mode="aspectFill" />
<view class="preview-name">{{ item.title }}</view>
</view>
</scroll-view>
</view>
</view>
<!-- 简单列表展示 -->
<view v-else-if="rewards.length > 0">
<scroll-view class="preview-scroll" scroll-x>
<view class="preview-item" v-for="(item, idx) in rewards" :key="idx">
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : (item.level || '赏') }}</view>
<image class="preview-img" :src="item.image" mode="aspectFill" />
<view class="preview-name">{{ item.title }}</view>
</view>
</scroll-view>
</view>
<!-- 空状态 -->
<view v-else class="empty-state">
<text class="empty-icon">📭</text>
<text class="empty-text">{{ emptyText }}</text>
</view>
</view>
</template>
<script setup>
import { computed } from 'vue'
import { groupRewardsByLevel } from '@/utils/activity'
const props = defineProps({
title: {
type: String,
default: '奖池配置'
},
rewards: {
type: Array,
default: () => []
},
grouped: {
type: Boolean,
default: false
},
playType: {
type: String,
default: 'normal'
},
emptyText: {
type: String,
default: '暂无奖品配置'
},
hideViewAll: {
type: Boolean,
default: false
}
})
defineEmits(['view-all'])
// 判断是否为对对碰分组(包含"对子"字样)
const isMatchingGroup = (level) => {
return String(level || '').includes('对子')
}
const rewardGroups = computed(() => {
if (!props.grouped) return []
return groupRewardsByLevel(props.rewards, props.playType)
})
</script>
<style lang="scss" scoped>
/* ============================================
奖池预览 - 与原始设计完全一致
============================================ */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-md;
}
.section-title {
font-size: $font-md;
font-weight: 700;
color: $text-main;
}
.section-more {
font-size: $font-sm;
color: $text-tertiary;
display: flex;
align-items: center;
&::after {
content: '>';
font-family: monospace;
margin-left: 6rpx;
font-weight: 700;
}
}
.tip-text {
font-size: 22rpx;
color: #E67E22;
margin-bottom: $spacing-md;
background: rgba(230, 126, 34, 0.08);
padding: 12rpx 16rpx;
border-radius: 8rpx;
border-left: 4rpx solid rgba(230, 126, 34, 0.5);
}
/* 等级分组 */
.prize-level-row {
margin-bottom: $spacing-lg;
background: rgba(0,0,0,0.02);
padding: $spacing-md;
border-radius: $radius-lg;
&:last-child {
margin-bottom: 0;
}
}
.level-header-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: $spacing-md;
}
.level-badge {
display: inline-block;
font-size: $font-xs;
font-weight: 900;
color: $text-main;
background: #F0F0F0;
padding: 4rpx 16rpx;
border-radius: 8rpx;
font-style: italic;
border: 1rpx solid rgba(0,0,0,0.05);
box-shadow: $shadow-xs;
&.badge-boss {
background: $gradient-gold;
color: #78350F;
border-color: rgba(217, 119, 6, 0.3);
}
}
.level-prob {
font-size: 22rpx;
color: $brand-primary;
font-weight: 800;
opacity: 0.9;
}
/* 预览滚动区域 */
.preview-scroll {
white-space: nowrap;
width: 100%;
}
.preview-item {
display: inline-block;
width: 180rpx;
margin-right: $spacing-md;
vertical-align: top;
position: relative;
transition: transform 0.2s;
&:active {
transform: scale(0.96);
}
&:last-child {
margin-right: 0;
}
}
.preview-img {
width: 180rpx;
height: 180rpx;
border-radius: $radius-lg;
background: $bg-secondary;
margin-bottom: $spacing-sm;
box-shadow: $shadow-sm;
border: 1rpx solid rgba(0,0,0,0.03);
}
.preview-name {
font-size: $font-xs;
color: $text-secondary;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: center;
font-weight: 500;
}
/* 奖品标签 */
.prize-tag {
position: absolute;
top: 10rpx;
left: 10rpx;
background: rgba(0,0,0,0.6);
color: #fff;
font-size: $font-xs;
padding: 4rpx $spacing-sm;
border-radius: $radius-sm;
z-index: 10;
font-weight: 700;
backdrop-filter: blur(4rpx);
transform: scale(0.9);
transform-origin: top left;
&.tag-boss {
background: $gradient-brand;
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.4);
}
}
.drop-qty-badge {
position: absolute;
top: 10rpx;
right: 10rpx;
background: linear-gradient(135deg, #ff6b35, #ff4500);
color: #fff;
font-size: 20rpx;
padding: 2rpx 10rpx;
border-radius: 16rpx;
z-index: 10;
font-weight: 700;
box-shadow: 0 2rpx 6rpx rgba(255, 69, 0, 0.3);
}
/* 空状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: $spacing-xl;
color: $text-sub;
min-height: 300rpx; /* 防止切换时布局跳动 */
}
.empty-icon {
font-size: 64rpx;
margin-bottom: $spacing-sm;
}
.empty-text {
font-size: $font-sm;
}
</style>