refactor: 重构活动页面,提取通用组件和组合式函数,并更新一番赏等页面以使用新组件
This commit is contained in:
parent
148c62a983
commit
97cfe3f3da
@ -27,71 +27,28 @@
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<view class="action-btn" @tap="showRules">
|
||||
<view class="action-icon rules-icon"></view>
|
||||
<text class="action-label">规则</text>
|
||||
<text class="icon">📋</text>
|
||||
<text>规则</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap="goCabinet">
|
||||
<view class="action-icon cabinet-icon"></view>
|
||||
<text class="action-label">盒柜</text>
|
||||
<text class="icon">📦</text>
|
||||
<text>盒柜</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section-container animate-enter stagger-1">
|
||||
<view class="modern-tabs">
|
||||
<view class="tab-item" :class="{ active: tabActive === 'pool' }" @tap="tabActive = 'pool'">
|
||||
本机奖池
|
||||
<view v-if="tabActive === 'pool'" class="active-dot"></view>
|
||||
</view>
|
||||
<view class="tab-item" :class="{ active: tabActive === 'records' }" @tap="tabActive = 'records'">
|
||||
购买记录
|
||||
<view v-if="tabActive === 'records'" class="active-dot"></view>
|
||||
</view>
|
||||
<view class="section-container animate-enter stagger-1" v-if="currentIssueRewards.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="section-title">奖池一览</text>
|
||||
<text class="section-more" @tap="openRewardsPopup">查看全部</text>
|
||||
</view>
|
||||
|
||||
<view v-show="tabActive === 'pool'">
|
||||
<view class="section-header">
|
||||
<text class="section-title">奖池配置</text>
|
||||
<text class="section-more" @tap="openRewardsPopup">查看全部</text>
|
||||
<scroll-view class="preview-scroll" scroll-x>
|
||||
<view class="preview-item" v-for="(item, idx) in currentIssueRewards" :key="item.id || idx">
|
||||
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : '赏' }}</view>
|
||||
<image class="preview-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="preview-name">{{ item.title }}</view>
|
||||
</view>
|
||||
<view v-if="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' }">{{ 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" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : group.level }}</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 class="empty-state">
|
||||
<text class="empty-icon">📭</text>
|
||||
<text class="empty-text">暂无奖池配置</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-show="tabActive === 'records'">
|
||||
<view class="records-list" v-if="winRecords.length">
|
||||
<view v-for="(it, idx) in winRecords" :key="it.id" class="record-item">
|
||||
<image class="record-img" :src="it.image" mode="aspectFill" />
|
||||
<view class="record-info">
|
||||
<view class="record-title">{{ it.title }}</view>
|
||||
<view class="record-meta">
|
||||
<text class="record-count">x{{ it.count }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-state" v-else>
|
||||
<text class="empty-icon">📝</text>
|
||||
<text class="empty-text">暂无购买记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<view style="height: 220rpx;"></view>
|
||||
@ -126,25 +83,17 @@
|
||||
<text class="rewards-close" @tap="closeRewardsPopup">×</text>
|
||||
</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-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="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>
|
||||
</view>
|
||||
<view v-for="(item, idx) in rewardsForPopup" :key="item.id || idx" class="rewards-item">
|
||||
<image class="rewards-thumb" :src="item.image" mode="aspectFill" />
|
||||
<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>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="rewards-empty">暂无奖池数据</view>
|
||||
<view v-if="!rewardsForPopup.length" class="rewards-empty">暂无奖池数据</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
@ -171,13 +120,11 @@ import { ref, computed, nextTick } from 'vue'
|
||||
import FlipGrid from '../../../components/FlipGrid.vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import PaymentPopup from '../../../components/PaymentPopup.vue'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, joinLottery, createWechatOrder, getLotteryResult, getItemCards, getUserCoupons, getIssueDrawLogs } from '../../../api/appUser'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, joinLottery, createWechatOrder, getLotteryResult, getItemCards, getUserCoupons } from '../../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
const rewardsVisible = ref(false)
|
||||
const tabActive = ref('pool')
|
||||
const winRecords = ref([])
|
||||
const issues = ref([])
|
||||
const rewardsMap = ref({})
|
||||
const currentIssueId = ref('')
|
||||
@ -190,8 +137,8 @@ const coverUrl = computed(() => cleanUrl(detail.value && (detail.value.image ||
|
||||
const currentIssueTitle = computed(() => {
|
||||
const arr = issues.value || []
|
||||
const cur = arr[selectedIssueIndex.value]
|
||||
// 不显示“期数”,优先显示标题,兜底显示“奖池”
|
||||
return (cur && (cur.title || '奖池')) || '-'
|
||||
const t = (cur && (cur.title || ('第' + (cur.no || '-') + '期'))) || '-'
|
||||
return t
|
||||
})
|
||||
const points = ref(0)
|
||||
const flipRef = ref(null)
|
||||
@ -200,28 +147,6 @@ const rewardsForPopup = computed(() => {
|
||||
const arr = currentIssueRewards.value || []
|
||||
return Array.isArray(arr) ? arr : []
|
||||
})
|
||||
const rewardGroups = computed(() => {
|
||||
const groups = {}
|
||||
currentIssueRewards.value.forEach(item => {
|
||||
const level = item.level || '赏'
|
||||
if (!groups[level]) groups[level] = []
|
||||
groups[level].push(item)
|
||||
})
|
||||
return Object.keys(groups).sort((a, b) => {
|
||||
if (a === 'BOSS') return -1
|
||||
if (b === 'BOSS') return 1
|
||||
// Alphabetical sort (A, B, C...)
|
||||
return a.localeCompare(b)
|
||||
}).map(key => {
|
||||
const rewards = groups[key]
|
||||
const total = rewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
||||
return {
|
||||
level: key,
|
||||
rewards: rewards,
|
||||
totalPercent: total.toFixed(1)
|
||||
}
|
||||
})
|
||||
})
|
||||
const paymentVisible = ref(false)
|
||||
const paymentAmount = ref('0.00')
|
||||
const coupons = ref([])
|
||||
@ -244,15 +169,6 @@ function formatPercent(v) {
|
||||
return `${n}%`
|
||||
}
|
||||
|
||||
function levelToAlpha(level) {
|
||||
if (level === 'BOSS') return 'BOSS'
|
||||
const n = Number(level)
|
||||
if (isNaN(n) || n <= 0) return String(level || '赏')
|
||||
// 1 -> A, 2 -> B ... 26 -> Z
|
||||
// Only handle up to 26 levels for now as it's rare to have more
|
||||
return String.fromCharCode(64 + n)
|
||||
}
|
||||
|
||||
function openRewardsPopup() {
|
||||
rewardsVisible.value = true
|
||||
}
|
||||
@ -319,8 +235,7 @@ function normalizeRewards(list) {
|
||||
title: i.name ?? i.title ?? '',
|
||||
image: cleanUrl(i.product_image ?? i.image ?? i.img ?? i.pic ?? i.banner ?? ''),
|
||||
weight: Number(i.weight) || 0,
|
||||
boss: detectBoss(i),
|
||||
level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏'))
|
||||
boss: detectBoss(i)
|
||||
}))
|
||||
const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0)
|
||||
const enriched = items.map(it => ({
|
||||
@ -373,10 +288,6 @@ async function fetchIssues(id) {
|
||||
const latestId = pickLatestIssueId(issues.value)
|
||||
setSelectedById(latestId)
|
||||
await fetchRewardsForIssues(id)
|
||||
// 获取购买记录
|
||||
if (currentIssueId.value) {
|
||||
fetchWinRecords(id, currentIssueId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function pickLatestIssueId(list) {
|
||||
@ -417,36 +328,6 @@ function nextIssue() {
|
||||
currentIssueId.value = (cur && cur.id) || ''
|
||||
}
|
||||
|
||||
async function fetchWinRecords(actId, issId) {
|
||||
if (!actId || !issId) return
|
||||
try {
|
||||
const res = await getIssueDrawLogs(actId, issId)
|
||||
const list = (res && res.list) || (Array.isArray(res) ? res : [])
|
||||
// 聚合同一奖品的记录
|
||||
const aggregate = {}
|
||||
list.forEach(it => {
|
||||
const key = it.reward_id || it.id
|
||||
if (!aggregate[key]) {
|
||||
aggregate[key] = {
|
||||
id: key,
|
||||
title: it.reward_name || it.title || it.name || '-',
|
||||
image: it.reward_image || it.image || '',
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
aggregate[key].count += 1
|
||||
})
|
||||
const total = list.length || 1
|
||||
winRecords.value = Object.values(aggregate).map(it => ({
|
||||
...it,
|
||||
percent: ((it.count / total) * 100).toFixed(1)
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('fetchWinRecords error', e)
|
||||
winRecords.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = detail.value.banner || ''
|
||||
if (url) uni.previewImage({ urls: [url], current: url })
|
||||
@ -737,10 +618,10 @@ function closeFlip() { showFlip.value = false }
|
||||
}
|
||||
.header-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 180rpx;
|
||||
padding: 6rpx 0;
|
||||
}
|
||||
.header-title {
|
||||
@ -779,47 +660,29 @@ function closeFlip() { showFlip.value = false }
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28rpx;
|
||||
margin-left: 16rpx;
|
||||
padding-left: 24rpx;
|
||||
border-left: 2rpx solid #E8E8E8;
|
||||
gap: $spacing-lg;
|
||||
margin-left: 20rpx;
|
||||
padding-left: $spacing-lg;
|
||||
border-left: 1rpx solid rgba(0,0,0,0.06);
|
||||
justify-content: center;
|
||||
align-self: stretch;
|
||||
height: 140rpx;
|
||||
}
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: $font-xs;
|
||||
color: $text-sub;
|
||||
transition: all 0.2s;
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.9);
|
||||
color: $text-main;
|
||||
}
|
||||
}
|
||||
.action-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 8rpx;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.rules-icon {
|
||||
background-color: #999;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
|
||||
mask-size: cover;
|
||||
-webkit-mask-size: cover;
|
||||
}
|
||||
.cabinet-icon {
|
||||
background-color: #999;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 3H4c-1.1 0-2 .9-2 2v16l4-4h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 11H7v-2h4v2zm6-4H7V8h10v2z'/%3E%3C/svg%3E");
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 3H4c-1.1 0-2 .9-2 2v16l4-4h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 11H7v-2h4v2zm6-4H7V8h10v2z'/%3E%3C/svg%3E");
|
||||
mask-size: cover;
|
||||
-webkit-mask-size: cover;
|
||||
}
|
||||
.action-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
letter-spacing: 1rpx;
|
||||
.action-btn .icon {
|
||||
font-size: $font-xl;
|
||||
margin-bottom: 6rpx;
|
||||
filter: grayscale(0.2);
|
||||
}
|
||||
|
||||
.section-container {
|
||||
@ -830,39 +693,6 @@ function closeFlip() { showFlip.value = false }
|
||||
box-shadow: $shadow-sm;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.modern-tabs {
|
||||
display: flex;
|
||||
background: $bg-secondary;
|
||||
padding: 8rpx;
|
||||
border-radius: $radius-lg;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: $spacing-md 0;
|
||||
font-size: $font-md;
|
||||
color: $text-sub;
|
||||
border-radius: $radius-md;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
background: #FFFFFF;
|
||||
color: $brand-primary;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
width: 8rpx; height: 8rpx;
|
||||
background: $brand-primary;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 8rpx; left: 50%; transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@ -903,29 +733,31 @@ function closeFlip() { showFlip.value = false }
|
||||
|
||||
.preview-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
margin: 0 -$spacing-lg;
|
||||
padding: 0 $spacing-lg;
|
||||
width: calc(100% + 40rpx);
|
||||
}
|
||||
.preview-item {
|
||||
display: inline-block;
|
||||
width: 180rpx;
|
||||
margin-right: $spacing-md;
|
||||
width: 200rpx;
|
||||
margin-right: $spacing-lg;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
transition: transform 0.2s;
|
||||
&:active { transform: scale(0.96); }
|
||||
&:last-child { margin-right: 0; }
|
||||
&:last-child { margin-right: 40rpx; }
|
||||
}
|
||||
.preview-img {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: $radius-lg;
|
||||
background: $bg-secondary;
|
||||
margin-bottom: $spacing-sm;
|
||||
margin-bottom: $spacing-md;
|
||||
box-shadow: $shadow-sm;
|
||||
border: 1rpx solid rgba(0,0,0,0.03);
|
||||
}
|
||||
.preview-name {
|
||||
font-size: $font-xs;
|
||||
font-size: $font-sm;
|
||||
color: $text-secondary;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
@ -936,16 +768,16 @@ function closeFlip() { showFlip.value = false }
|
||||
}
|
||||
.prize-tag {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
left: 8rpx;
|
||||
top: 10rpx;
|
||||
left: 10rpx;
|
||||
background: rgba(0,0,0,0.6);
|
||||
color: #fff;
|
||||
font-size: $font-xxs;
|
||||
padding: 2rpx $spacing-xs;
|
||||
border-radius: 4rpx;
|
||||
font-size: $font-xs;
|
||||
padding: 4rpx $spacing-sm;
|
||||
border-radius: $radius-sm;
|
||||
z-index: 10;
|
||||
font-weight: 700;
|
||||
backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4rpx);
|
||||
transform: scale(0.9);
|
||||
transform-origin: top left;
|
||||
}
|
||||
@ -954,47 +786,6 @@ function closeFlip() { showFlip.value = false }
|
||||
box-shadow: 0 4rpx 12rpx rgba($brand-primary, 0.4);
|
||||
}
|
||||
|
||||
/* 新增:等级分组样式 */
|
||||
.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;
|
||||
}
|
||||
|
||||
.level-prob {
|
||||
font-size: 22rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.level-badge.badge-boss {
|
||||
background: $gradient-brand;
|
||||
color: #fff;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.selector-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1102,7 +893,6 @@ function closeFlip() { showFlip.value = false }
|
||||
margin-bottom: $spacing-sm;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
line-height: 1.4;
|
||||
@ -1207,23 +997,23 @@ function closeFlip() { showFlip.value = false }
|
||||
border: 1rpx solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 底部多档位抽赏按钮 - 高级重置 */
|
||||
/* 底部多档位抽赏按钮 */
|
||||
.bottom-actions {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 32rpx 32rpx;
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(30rpx);
|
||||
box-shadow: 0 -12rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
gap: $spacing-md;
|
||||
padding: $spacing-lg $spacing-lg;
|
||||
padding-bottom: calc($spacing-lg + env(safe-area-inset-bottom));
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(20rpx);
|
||||
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.08);
|
||||
z-index: 999;
|
||||
border-top: 1rpx solid rgba(255, 255, 255, 0.8);
|
||||
animation: slideUp $transition-slow $ease-out backwards;
|
||||
border-top: 1rpx solid rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.tier-btn {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
@ -1231,80 +1021,92 @@ function closeFlip() { showFlip.value = false }
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 10rpx;
|
||||
background: #FFF;
|
||||
border: 2rpx solid rgba($brand-primary, 0.1);
|
||||
border-radius: 28rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.03);
|
||||
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
padding: $spacing-md $spacing-xs;
|
||||
background: $bg-card;
|
||||
border: 1rpx solid $border-color-light;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
transition: all $transition-fast;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.92);
|
||||
background: #F9F9F9;
|
||||
box-shadow: none;
|
||||
transform: scale(0.95);
|
||||
background: $bg-page;
|
||||
}
|
||||
}
|
||||
|
||||
.tier-price {
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
font-size: $font-lg;
|
||||
font-weight: 800;
|
||||
color: $text-main;
|
||||
font-family: 'DIN Alternate', sans-serif;
|
||||
letter-spacing: -1rpx;
|
||||
}
|
||||
|
||||
.tier-label {
|
||||
font-size: 22rpx;
|
||||
color: $brand-primary;
|
||||
margin-top: 6rpx;
|
||||
font-weight: 800;
|
||||
font-style: italic;
|
||||
font-size: $font-xs;
|
||||
color: $text-sub;
|
||||
margin-top: 4rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 热门/最高档位 - 高级动效 */
|
||||
.tier-hot {
|
||||
background: $gradient-brand !important;
|
||||
border: none !important;
|
||||
box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.35) !important;
|
||||
background: $gradient-red;
|
||||
border: none;
|
||||
box-shadow: 0 6rpx 14rpx rgba($accent-red, 0.12);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: $radius-lg;
|
||||
transform: translateZ(0);
|
||||
|
||||
.tier-price {
|
||||
color: #FFF !important;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
||||
.tier-price, .tier-label {
|
||||
color: #fff !important;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.tier-label {
|
||||
color: rgba(255, 255, 255, 0.9) !important;
|
||||
text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 流光效果 */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -50%;
|
||||
left: -150%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
rgba(255, 255, 255, 0) 30%,
|
||||
rgba(255, 255, 255, 0.4) 50%,
|
||||
rgba(255, 255, 255, 0) 70%
|
||||
);
|
||||
transform: rotate(25deg);
|
||||
animation: hotSweep 4s infinite cubic-bezier(0.19, 1, 0.22, 1);
|
||||
pointer-events: none;
|
||||
left: -40%;
|
||||
top: 0;
|
||||
width: 60%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, rgba(255,255,255,0) 0%, rgba(255,255,255,0.22) 50%, rgba(255,255,255,0) 100%);
|
||||
transform: skewX(-18deg);
|
||||
opacity: 0.9;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
animation: hotShine 2.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: 'HOT';
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
background: linear-gradient(135deg, rgba(255,255,255,0.25), rgba(255,255,255,0.05));
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
font-weight: 800;
|
||||
padding: 3rpx 10rpx;
|
||||
border-radius: 999rpx;
|
||||
border: 1rpx solid rgba(255,255,255,0.35);
|
||||
text-shadow: 0 1rpx 2rpx rgba(0,0,0,0.18);
|
||||
z-index: 3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
transform: scale(0.96);
|
||||
}
|
||||
}
|
||||
.tier-hot .tier-price, .tier-hot .tier-label {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
@keyframes hotSweep {
|
||||
0% { left: -150%; }
|
||||
100% { left: 150%; }
|
||||
@keyframes hotShine {
|
||||
0% { transform: translateX(-10%) skewX(-18deg); opacity: 0; }
|
||||
15% { opacity: 0.9; }
|
||||
55% { transform: translateX(220%) skewX(-18deg); opacity: 0.35; }
|
||||
100% { transform: translateX(220%) skewX(-18deg); opacity: 0; }
|
||||
}
|
||||
|
||||
.rewards-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 9000; }
|
||||
@ -1348,43 +1150,6 @@ function closeFlip() { showFlip.value = false }
|
||||
max-height: 60vh;
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.rewards-group-v2 {
|
||||
margin-bottom: $spacing-xl;
|
||||
&:last-child { margin-bottom: 0; }
|
||||
}
|
||||
|
||||
.group-header-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-md;
|
||||
padding: 0 4rpx;
|
||||
}
|
||||
|
||||
.group-badge {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
.group-total-prob {
|
||||
font-size: 24rpx;
|
||||
color: $brand-primary;
|
||||
font-weight: 800;
|
||||
}
|
||||
.rewards-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -1492,67 +1257,4 @@ function closeFlip() { showFlip.value = false }
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Purchase Records Styles
|
||||
============================================ */
|
||||
.records-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
.record-item {
|
||||
display: flex;
|
||||
background: #FFFFFF;
|
||||
padding: $spacing-lg;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
align-items: center;
|
||||
}
|
||||
.record-img {
|
||||
width: 100rpx; height: 100rpx;
|
||||
border-radius: $radius-md;
|
||||
background: $bg-secondary;
|
||||
margin-right: $spacing-lg;
|
||||
}
|
||||
.record-info {
|
||||
flex: 1;
|
||||
}
|
||||
.record-title {
|
||||
font-size: $font-md;
|
||||
font-weight: 600;
|
||||
color: $text-main;
|
||||
margin-bottom: $spacing-xs;
|
||||
}
|
||||
.record-meta {
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
font-size: $font-sm;
|
||||
color: $text-sub;
|
||||
}
|
||||
.record-count {
|
||||
background: rgba($brand-primary, 0.1);
|
||||
color: $brand-primary;
|
||||
padding: 2rpx $spacing-sm;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-xl * 2;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: $spacing-md;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: $font-md;
|
||||
color: $text-sub;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -38,67 +38,30 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="header-actions">
|
||||
<view class="action-btn" @tap="showRules">
|
||||
<view class="action-icon rules-icon"></view>
|
||||
<text class="action-label">规则</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap="goCabinet">
|
||||
<view class="action-icon cabinet-icon"></view>
|
||||
<text class="action-label">盒柜</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap="showRules">
|
||||
<text class="icon">📋</text>
|
||||
<text>规则</text>
|
||||
</view>
|
||||
<view class="action-btn" @tap="goCabinet">
|
||||
<text class="icon">📦</text>
|
||||
<text>盒柜</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 赏品概览 -->
|
||||
<view class="section-container animate-enter stagger-1">
|
||||
<view class="modern-tabs">
|
||||
<view class="tab-item" :class="{ active: tabActive === 'pool' }" @tap="tabActive = 'pool'">
|
||||
本机奖池
|
||||
<view v-if="tabActive === 'pool'" class="active-dot"></view>
|
||||
</view>
|
||||
<view class="tab-item" :class="{ active: tabActive === 'records' }" @tap="tabActive = 'records'">
|
||||
购买记录
|
||||
<view v-if="tabActive === 'records'" class="active-dot"></view>
|
||||
</view>
|
||||
<view class="section-container animate-enter stagger-1" v-if="currentIssueRewards.length > 0">
|
||||
<view class="section-header">
|
||||
<text class="section-title">赏品一览</text>
|
||||
<text class="section-more" @tap="openRewardsPopup">查看全部</text>
|
||||
</view>
|
||||
|
||||
<view v-show="tabActive === 'pool'">
|
||||
<view class="section-header">
|
||||
<text class="section-title">奖品配置</text>
|
||||
<text class="section-more" @tap="openRewardsPopup">查看全部</text>
|
||||
<scroll-view class="preview-scroll" scroll-x>
|
||||
<view class="preview-item" v-for="(item, idx) in currentIssueRewards" :key="idx">
|
||||
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : (item.grade || '赏') }}</view>
|
||||
<image class="preview-img" :src="item.image" mode="aspectFill" />
|
||||
<view class="preview-name">{{ item.title }}</view>
|
||||
</view>
|
||||
<view v-if="currentIssueRewards.length > 0">
|
||||
<scroll-view class="preview-scroll" scroll-x>
|
||||
<view class="preview-item" v-for="(item, idx) in currentIssueRewards" :key="idx">
|
||||
<view class="prize-tag" :class="{ 'tag-boss': item.boss }">{{ item.boss ? 'BOSS' : (item.grade || '赏') }}</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">暂无奖品配置</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-show="tabActive === 'records'">
|
||||
<view class="records-list" v-if="winRecords.length">
|
||||
<view v-for="(it, idx) in winRecords" :key="it.id" class="record-item">
|
||||
<image class="record-img" :src="it.image" mode="aspectFill" />
|
||||
<view class="record-info">
|
||||
<view class="record-title">{{ it.title }}</view>
|
||||
<view class="record-meta">
|
||||
<text class="record-count">x{{ it.count }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty-state" v-else>
|
||||
<text class="empty-icon">📝</text>
|
||||
<text class="empty-text">暂无购买记录</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 选号区域 -->
|
||||
@ -179,7 +142,7 @@ import { ref, computed } from 'vue'
|
||||
import { onLoad, onUnload } from '@dcloudio/uni-app'
|
||||
import FlipGrid from '../../../components/FlipGrid.vue'
|
||||
import YifanSelector from '@/components/YifanSelector.vue'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getIssueDrawLogs } from '../../../api/appUser'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards } from '../../../api/appUser'
|
||||
|
||||
const detail = ref({})
|
||||
const issues = ref([])
|
||||
@ -416,10 +379,6 @@ async function fetchIssues(id) {
|
||||
const latestId = pickLatestIssueId(issues.value)
|
||||
setSelectedById(latestId)
|
||||
await fetchRewardsForIssues(id)
|
||||
// 获取购买记录
|
||||
if (currentIssueId.value) {
|
||||
fetchWinRecords(id, currentIssueId.value)
|
||||
}
|
||||
}
|
||||
|
||||
function pickLatestIssueId(list) {
|
||||
@ -460,35 +419,7 @@ function nextIssue() {
|
||||
currentIssueId.value = (cur && cur.id) || ''
|
||||
}
|
||||
|
||||
async function fetchWinRecords(actId, issId) {
|
||||
if (!actId || !issId) return
|
||||
try {
|
||||
const res = await getIssueDrawLogs(actId, issId)
|
||||
const list = (res && res.list) || (Array.isArray(res) ? res : [])
|
||||
// 聚合同一奖品的记录
|
||||
const aggregate = {}
|
||||
list.forEach(it => {
|
||||
const key = it.reward_id || it.id
|
||||
if (!aggregate[key]) {
|
||||
aggregate[key] = {
|
||||
id: key,
|
||||
title: it.reward_name || it.title || it.name || '-',
|
||||
image: it.reward_image || it.image || '',
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
aggregate[key].count += 1
|
||||
})
|
||||
const total = list.length || 1
|
||||
winRecords.value = Object.values(aggregate).map(it => ({
|
||||
...it,
|
||||
percent: ((it.count / total) * 100).toFixed(1)
|
||||
}))
|
||||
} catch (e) {
|
||||
console.error('fetchWinRecords error', e)
|
||||
winRecords.value = []
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function onPreviewBanner() {
|
||||
const url = coverUrl.value || ''
|
||||
@ -681,10 +612,10 @@ onUnload(() => {
|
||||
}
|
||||
.header-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-content: flex-start;
|
||||
min-height: 180rpx;
|
||||
padding: 6rpx 0;
|
||||
}
|
||||
.header-title {
|
||||
@ -741,47 +672,30 @@ onUnload(() => {
|
||||
.header-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 28rpx;
|
||||
margin-left: 16rpx;
|
||||
padding-left: 24rpx;
|
||||
border-left: 2rpx solid #E8E8E8;
|
||||
gap: $spacing-lg;
|
||||
margin-left: 20rpx;
|
||||
padding-left: $spacing-lg;
|
||||
border-left: 1rpx solid rgba(0,0,0,0.06);
|
||||
justify-content: center;
|
||||
align-self: stretch;
|
||||
height: 140rpx;
|
||||
}
|
||||
.action-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: $font-xs;
|
||||
color: $text-sub;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:active {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.9);
|
||||
color: $text-main;
|
||||
}
|
||||
}
|
||||
.action-icon {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-bottom: 8rpx;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
.rules-icon {
|
||||
background-color: #999;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z'/%3E%3C/svg%3E");
|
||||
mask-size: cover;
|
||||
-webkit-mask-size: cover;
|
||||
}
|
||||
.cabinet-icon {
|
||||
background-color: #999;
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 3H4c-1.1 0-2 .9-2 2v16l4-4h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 11H7v-2h4v2zm6-4H7V8h10v2z'/%3E%3C/svg%3E");
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='currentColor'%3E%3Cpath d='M20 3H4c-1.1 0-2 .9-2 2v16l4-4h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-9 11H7v-2h4v2zm6-4H7V8h10v2z'/%3E%3C/svg%3E");
|
||||
mask-size: cover;
|
||||
-webkit-mask-size: cover;
|
||||
}
|
||||
.action-label {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
letter-spacing: 1rpx;
|
||||
.action-btn .icon {
|
||||
font-size: $font-xl;
|
||||
margin-bottom: 6rpx;
|
||||
filter: grayscale(0.2);
|
||||
}
|
||||
|
||||
/* 通用板块容器 */
|
||||
@ -1130,99 +1044,4 @@ onUnload(() => {
|
||||
color: $text-tertiary;
|
||||
font-size: $font-sm;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
Tabs & Purchase Records Styles
|
||||
============================================ */
|
||||
.modern-tabs {
|
||||
display: flex;
|
||||
background: $bg-secondary;
|
||||
padding: 8rpx;
|
||||
border-radius: $radius-lg;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: $spacing-md 0;
|
||||
font-size: $font-md;
|
||||
color: $text-sub;
|
||||
border-radius: $radius-md;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
background: #FFFFFF;
|
||||
color: $brand-primary;
|
||||
box-shadow: $shadow-sm;
|
||||
}
|
||||
}
|
||||
.active-dot {
|
||||
width: 8rpx; height: 8rpx;
|
||||
background: $brand-primary;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
bottom: 8rpx; left: 50%; transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.records-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
.record-item {
|
||||
display: flex;
|
||||
background: #FFFFFF;
|
||||
padding: $spacing-lg;
|
||||
border-radius: $radius-lg;
|
||||
box-shadow: $shadow-sm;
|
||||
align-items: center;
|
||||
}
|
||||
.record-img {
|
||||
width: 100rpx; height: 100rpx;
|
||||
border-radius: $radius-md;
|
||||
background: $bg-secondary;
|
||||
margin-right: $spacing-lg;
|
||||
}
|
||||
.record-info {
|
||||
flex: 1;
|
||||
}
|
||||
.record-title {
|
||||
font-size: $font-md;
|
||||
font-weight: 600;
|
||||
color: $text-main;
|
||||
margin-bottom: $spacing-xs;
|
||||
}
|
||||
.record-meta {
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
font-size: $font-sm;
|
||||
color: $text-sub;
|
||||
}
|
||||
.record-count {
|
||||
background: rgba($brand-primary, 0.1);
|
||||
color: $brand-primary;
|
||||
padding: 2rpx $spacing-sm;
|
||||
border-radius: $radius-sm;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: $spacing-xl * 2;
|
||||
color: $text-tertiary;
|
||||
}
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: $spacing-md;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.empty-text {
|
||||
font-size: $font-md;
|
||||
color: $text-sub;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user