feat(无限赏): 恢复奖池查看全部弹窗,新增参考价和概率总览

- 恢复无限赏页面"查看全部"按钮和 RewardsPopup 弹窗
- RewardsPopup 顶部新增按档次分类的中奖率概览条
- 奖品项显示参考价(来自后端 price_snapshot_cents)
- 每个奖品图片左下角添加档次标签(S赏/A赏/BOSS赏等)
- normalizeRewards 新增 product_price 字段提取
- 理性消费提示改为始终显示
This commit is contained in:
Zuncle 2026-03-25 22:01:22 +08:00
parent 495b46ec8b
commit 7487e7224a
4 changed files with 175 additions and 25 deletions

View File

@ -6,21 +6,38 @@
<text class="rewards-title">{{ title }}</text>
<text class="rewards-close" @tap="$emit('update:visible', false)">×</text>
</view>
<!-- 概率总览条 -->
<view class="prob-overview" v-if="rewardGroups.length > 0">
<view
class="prob-item"
v-for="group in rewardGroups"
:key="'prob-' + group.level"
>
<view class="prob-dot" :class="getDotClass(group.level)"></view>
<text class="prob-label">{{ group.level }}</text>
<text class="prob-value">{{ group.totalPercent }}%</text>
</view>
</view>
<scroll-view scroll-y class="rewards-list">
<view v-if="rewardGroups.length > 0">
<view class="rewards-group-v2" v-for="group in rewardGroups" :key="group.level">
<view class="group-header-row">
<text class="group-badge" :class="{ 'badge-boss': group.level === 'BOSS' }">{{ group.level }}</text>
<text class="group-badge" :class="getBadgeClass(group.level)">{{ group.level }}</text>
<text class="group-total-prob">该档总概率 {{ group.totalPercent }}%</text>
</view>
<view v-for="(item, idx) in group.rewards" :key="item.id || idx" class="rewards-item">
<image class="rewards-thumb" :src="item.image" mode="aspectFill" />
<view class="thumb-wrap">
<image class="rewards-thumb" :src="item.image" mode="aspectFill" />
<view class="thumb-level-tag" :class="getBadgeClass(group.level)">{{ group.level }}</view>
</view>
<view class="rewards-info">
<view class="rewards-name-row">
<text class="rewards-name">{{ item.title || '-' }}</text>
<view class="rewards-tag" v-if="item.boss">BOSS</view>
</view>
<text class="rewards-percent">单项概率 {{ formatPercent(item.percent) }}</text>
<text class="rewards-price">参考价¥{{ item.product_price > 0 ? (item.product_price / 100).toFixed(2) : '--' }}</text>
</view>
</view>
</view>
@ -54,6 +71,22 @@ defineProps({
})
defineEmits(['update:visible'])
/** 概率总览圆点颜色 class */
function getDotClass(level) {
if (level === 'BOSS') return 'dot-boss'
if (level === 'S' || level === 'Last') return 'dot-rare'
if (level === 'A') return 'dot-a'
return 'dot-normal'
}
/** 分组标签颜色 class */
function getBadgeClass(level) {
if (level === 'BOSS') return 'badge-boss'
if (level === 'S' || level === 'Last') return 'badge-rare'
if (level === 'A') return 'badge-a'
return ''
}
</script>
<style lang="scss" scoped>
@ -121,18 +154,84 @@ defineEmits(['update:visible'])
padding: $spacing-xs;
}
/* ============================================
概率总览条
============================================ */
.prob-overview {
display: flex;
flex-wrap: wrap;
gap: $spacing-sm $spacing-lg;
padding: $spacing-md $spacing-lg;
background: rgba(0, 0, 0, 0.02);
border-bottom: 1rpx solid $border-color-light;
}
.prob-item {
display: flex;
align-items: center;
gap: 8rpx;
}
.prob-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
flex-shrink: 0;
&.dot-boss {
background: $accent-gold;
box-shadow: 0 0 8rpx rgba(255, 193, 7, 0.5);
}
&.dot-rare {
background: $brand-primary;
box-shadow: 0 0 8rpx rgba($brand-primary, 0.4);
}
&.dot-a {
background: $accent-orange;
}
&.dot-normal {
background: $text-tertiary;
}
}
.prob-label {
font-size: $font-xs;
font-weight: 600;
color: $text-main;
}
.prob-value {
font-size: $font-xs;
font-weight: 800;
color: $brand-primary;
}
/* ============================================
奖品列表
============================================ */
.rewards-list {
max-height: 60vh;
max-height: 55vh;
padding: $spacing-lg;
}
.rewards-group-v2 {
margin-bottom: $spacing-lg;
background: rgba(0, 0, 0, 0.02);
padding: $spacing-md;
border-radius: $radius-lg;
&:last-child {
margin-bottom: 0;
}
}
.group-header-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: $spacing-sm;
margin-bottom: $spacing-sm;
}
@ -144,16 +243,27 @@ defineEmits(['update:visible'])
background: rgba($brand-primary, 0.1);
padding: 4rpx $spacing-sm;
border-radius: $radius-sm;
&.badge-boss {
background: $gradient-gold;
color: #6b4b1f;
}
&.badge-rare {
background: $gradient-brand;
color: #fff;
}
&.badge-a {
background: rgba($accent-orange, 0.15);
color: $accent-orange;
}
}
.group-total-prob {
font-size: $font-xs;
color: $text-sub;
font-weight: 600;
}
.rewards-item {
@ -161,19 +271,51 @@ defineEmits(['update:visible'])
align-items: center;
padding: $spacing-sm 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.03);
&:last-child {
border-bottom: none;
}
}
.rewards-thumb {
width: 100rpx;
height: 100rpx;
border-radius: $radius-md;
margin-right: $spacing-md;
background: $bg-secondary;
.thumb-wrap {
position: relative;
flex-shrink: 0;
margin-right: $spacing-md;
}
.rewards-thumb {
width: 120rpx;
height: 120rpx;
border-radius: $radius-md;
background: $bg-secondary;
display: block;
}
.thumb-level-tag {
position: absolute;
left: 0;
bottom: 0;
font-size: 20rpx;
font-weight: 700;
padding: 2rpx 10rpx;
border-radius: 0 $radius-md 0 $radius-md;
color: $brand-primary;
background: rgba($brand-primary, 0.12);
&.badge-boss {
background: $gradient-gold;
color: #6b4b1f;
}
&.badge-rare {
background: $gradient-brand;
color: #fff;
}
&.badge-a {
background: rgba($accent-orange, 0.15);
color: $accent-orange;
}
}
.rewards-info {
@ -185,14 +327,14 @@ defineEmits(['update:visible'])
display: flex;
align-items: center;
gap: $spacing-xs;
margin-bottom: $spacing-xs;
margin-bottom: 6rpx;
}
.rewards-name {
font-size: $font-md;
font-weight: 600;
color: $text-main;
@include text-ellipsis(1);
@include text-ellipsis(2);
}
.rewards-tag {
@ -205,6 +347,19 @@ defineEmits(['update:visible'])
flex-shrink: 0;
}
.rewards-price {
font-size: $font-md;
font-weight: 700;
color: $brand-primary;
display: block;
margin-bottom: 4rpx;
}
.rewards-percent {
font-size: $font-sm;
color: $text-sub;
}
.rewards-qty-tag {
font-size: $font-xxs;
font-weight: 700;
@ -215,11 +370,6 @@ defineEmits(['update:visible'])
flex-shrink: 0;
}
.rewards-percent {
font-size: $font-sm;
color: $text-sub;
}
.rewards-empty {
text-align: center;
color: $text-sub;

View File

@ -5,7 +5,7 @@
<!-- 通过 hideViewAll 控制是否显示查看全部按钮 -->
<text v-if="!hideViewAll" class="section-more" @tap="$emit('view-all')">查看全部</text>
</view>
<view v-if="hideViewAll" class="tip-text">每抽都有概率出以下商品盲盒消费具有随机性请理性消费</view>
<view class="tip-text">每抽都有概率出以下商品盲盒消费具有随机性请理性消费</view>
<!-- 分组展示 -->
<view v-if="grouped && rewardGroups.length > 0">

View File

@ -19,7 +19,7 @@
title="奖池配置"
:rewards="currentIssueRewards"
:grouped="true"
:hide-view-all="true"
@view-all="rewardsVisible = true"
/>
</template>
<template #records>
@ -51,12 +51,11 @@
</template>
<template #modals>
<!-- 暂时注释掉奖池详情弹窗 -->
<!-- <RewardsPopup
<RewardsPopup
v-model:visible="rewardsVisible"
:title="`${currentIssueTitle} · 奖池与概率`"
:reward-groups="rewardGroups"
/> -->
/>
<LotteryResultPopup
v-model:visible="showResultPopup"

View File

@ -111,7 +111,8 @@ export function normalizeRewards(list, cleanUrl = (u) => u) {
boss: detectBoss(i),
min_score: i.min_score || 0,
drop_quantity: Number(i.drop_quantity) || 1,
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏')),
product_price: Number(i.price_snapshot_cents ?? i.product_price ?? i.price ?? i.reference_price) || 0
}))
const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
const enriched = items.map(it => {