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

380 lines
7.7 KiB
Vue
Executable File
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" class="rewards-overlay" @touchmove.stop.prevent>
<view class="rewards-mask" @tap="$emit('update:visible', false)"></view>
<view class="rewards-panel" @tap.stop>
<view class="rewards-header">
<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="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">
<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-price">参考价¥{{ item.product_price > 0 ? (item.product_price / 100).toFixed(2) : '--' }}</text>
</view>
</view>
</view>
</view>
<view v-else class="rewards-empty">{{ emptyText }}</view>
</scroll-view>
</view>
</view>
</template>
<script setup>
import { formatPercent } from '@/utils/format'
defineProps({
visible: {
type: Boolean,
default: false
},
title: {
type: String,
default: '奖品与概率'
},
rewardGroups: {
type: Array,
default: () => []
},
emptyText: {
type: String,
default: '暂无奖品数据'
}
})
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>
.rewards-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.rewards-mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}
.rewards-panel {
position: relative;
width: 90%;
max-height: 80vh;
background: $bg-card;
border-radius: $radius-xl;
overflow: hidden;
box-shadow: $shadow-lg;
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(50rpx);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.rewards-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: $spacing-lg;
border-bottom: 1rpx solid $border-color-light;
}
.rewards-title {
font-size: $font-lg;
font-weight: 700;
color: $text-main;
}
.rewards-close {
font-size: 48rpx;
color: $text-sub;
line-height: 1;
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: 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;
}
.group-badge {
font-size: $font-xs;
font-weight: 700;
color: $brand-primary;
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 {
display: flex;
align-items: center;
padding: $spacing-sm 0;
border-bottom: 1rpx solid rgba(0, 0, 0, 0.03);
&:last-child {
border-bottom: none;
}
}
.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 {
flex: 1;
min-width: 0;
}
.rewards-name-row {
display: flex;
align-items: center;
gap: $spacing-xs;
margin-bottom: 6rpx;
}
.rewards-name {
font-size: $font-md;
font-weight: 600;
color: $text-main;
@include text-ellipsis(2);
}
.rewards-tag {
font-size: $font-xxs;
font-weight: 700;
color: #6b4b1f;
background: $gradient-gold;
padding: 2rpx 8rpx;
border-radius: $radius-sm;
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;
color: #fff;
background: linear-gradient(135deg, #ff6b35, #ff4500);
padding: 2rpx 10rpx;
border-radius: $radius-sm;
flex-shrink: 0;
}
.rewards-empty {
text-align: center;
color: $text-sub;
padding: $spacing-xl;
font-size: $font-sm;
}
</style>