From 148c62a98356ef4dfc9f1c0380ba46f9fb83698a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=96=B9=E6=88=90?= Date: Thu, 25 Dec 2025 19:17:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=8A=BD=E5=A5=96?= =?UTF-8?q?=E6=B4=BB=E5=8A=A8=E9=A1=B5=E9=9D=A2UI=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=A5=96=E6=B1=A0=E5=88=86=E7=BA=A7=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E5=92=8C=E8=B4=AD=E4=B9=B0=E8=AE=B0=E5=BD=95=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/appUser.js | 4 + components/YifanSelector.vue | 100 +++-- pages/activity/duiduipeng/index.vue | 391 ++++++++++++++---- pages/activity/pata/index.vue | 65 +-- pages/activity/wuxianshang/index.vue | 570 ++++++++++++++++++++------- pages/activity/yifanshang/index.vue | 257 ++++++++++-- pages/game/minesweeper/index.vue | 9 +- pages/index/index.vue | 543 +++++++++++-------------- 8 files changed, 1310 insertions(+), 629 deletions(-) diff --git a/api/appUser.js b/api/appUser.js index b5c2114..7eac8e3 100644 --- a/api/appUser.js +++ b/api/appUser.js @@ -74,6 +74,10 @@ export function getActivityIssueRewards(activity_id, issue_id) { return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/rewards`, method: 'GET' }) } +export function getIssueDrawLogs(activity_id, issue_id) { + return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/draw_logs`, method: 'GET' }) +} + export function drawActivityIssue(activity_id, issue_id) { return authRequest({ url: `/api/app/activities/${activity_id}/issues/${issue_id}/draw`, method: 'POST' }) } diff --git a/components/YifanSelector.vue b/components/YifanSelector.vue index 255684a..791d1a9 100644 --- a/components/YifanSelector.vue +++ b/components/YifanSelector.vue @@ -476,39 +476,40 @@ async function onPaymentConfirm(paymentData) { text-shadow: 0 2rpx 4rpx rgba(0,0,0,0.1); } -/* ============= 底部操作栏 ============= */ +/* ============= 底部操作栏 - 高级重置 ============= */ .action-bar { position: fixed; bottom: calc(40rpx + env(safe-area-inset-bottom)); left: 30rpx; right: 30rpx; - background: rgba($bg-card, 0.9); - backdrop-filter: blur(20rpx); - padding: 20rpx 30rpx; - box-shadow: $shadow-lg; + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(30rpx); + padding: 24rpx 40rpx; + box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12); border-radius: 999rpx; display: flex; flex-direction: row; justify-content: space-between; align-items: center; z-index: 100; - border: 1rpx solid rgba($bg-card, 0.5); - animation: slideUp 0.4s ease-out backwards; + border: 1rpx solid rgba(255, 255, 255, 0.6); + animation: slideUp 0.4s cubic-bezier(0.23, 1, 0.32, 1) backwards; } /* 选择信息行 */ .selection-info { - font-size: 26rpx; + font-size: 28rpx; color: $text-main; display: flex; - align-items: center; - font-weight: 600; + align-items: baseline; + font-weight: 800; } .highlight { color: $brand-primary; - font-weight: 800; - font-size: 36rpx; + font-weight: 900; + font-size: 40rpx; margin: 0 8rpx; + font-family: 'DIN Alternate', sans-serif; } /* 按钮组 */ @@ -519,54 +520,75 @@ async function onPaymentConfirm(paymentData) { /* 通用按钮样式 */ .btn-common { - height: 80rpx; - line-height: 80rpx; - padding: 0 48rpx; + height: 88rpx; + line-height: 88rpx; + padding: 0 56rpx; border-radius: 999rpx; - font-size: 28rpx; - font-weight: 700; + font-size: 30rpx; + font-weight: 900; margin: 0; display: flex; align-items: center; justify-content: center; - transition: all 0.2s; + transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); + border: none; &:active { - transform: scale(0.96); + transform: scale(0.92); } } -/* 购买按钮 */ +/* 购买按钮 - 品牌渐变 + 流光 */ .btn-buy { background: $gradient-brand !important; color: #FFFFFF !important; - box-shadow: 0 8rpx 20rpx rgba($brand-primary, 0.3); + box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.35); + position: relative; + overflow: hidden; - /* 脉冲动画 */ - animation: pulse 2s infinite; -} - -/* 随机按钮 */ -.btn-random { - background: $bg-secondary !important; - color: $text-main !important; - box-shadow: none; - border: 1rpx solid transparent; - - &:active { - background: #E5E7EB !important; + &::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: btnShine 4s infinite cubic-bezier(0.19, 1, 0.22, 1); + pointer-events: none; } } +/* 随机按钮 - 轻量化设计 */ +.btn-random { + background: #1A1A1A !important; + color: $accent-gold !important; + box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15); + + &:active { + background: #333 !important; + } +} + +@keyframes btnShine { + 0% { left: -150%; } + 100% { left: 150%; } +} + @keyframes slideUp { - from { transform: translateY(100%); opacity: 0; } + from { transform: translateY(120rpx); opacity: 0; } to { transform: translateY(0); opacity: 1; } } -@keyframes pulse { - 0% { box-shadow: 0 0 0 0 rgba($brand-primary, 0.4); } - 70% { box-shadow: 0 0 0 20rpx rgba($brand-primary, 0); } - 100% { box-shadow: 0 0 0 0 rgba($brand-primary, 0); } +@keyframes scaleIn { + from { transform: scale(0.95); opacity: 0; } + to { transform: scale(1); opacity: 1; } } @keyframes float { diff --git a/pages/activity/duiduipeng/index.vue b/pages/activity/duiduipeng/index.vue index 564caf1..ca95bcd 100644 --- a/pages/activity/duiduipeng/index.vue +++ b/pages/activity/duiduipeng/index.vue @@ -25,6 +25,16 @@ 理性消费 + + + + 规则 + + + + 盒柜 + + @@ -51,18 +61,6 @@ - - - - - - {{ currentIssueTitle }} - {{ currentIssueStatusText }} - - - - - @@ -80,13 +78,21 @@ 奖池配置 查看全部 - - - {{ item.boss ? 'BOSS' : '赏' }} - - {{ item.title }} + + + + {{ group.level }}赏 + 总概率 {{ group.totalPercent }}% + + + + {{ item.boss ? 'BOSS' : group.level }} + + {{ item.title }} + + - + 📭 暂无奖励配置 @@ -101,7 +107,6 @@ {{ it.title }} x{{ it.count }} - 占比 {{ it.percent }}% @@ -185,17 +190,25 @@ × - - - - - {{ item.title || '-' }} - BOSS + + + + {{ group.level }}赏 + 该档总概率 {{ group.totalPercent }}% + + + + + + {{ item.title || '-' }} + BOSS + + 单项概率 {{ formatPercent(item.percent) }} + - 概率 {{ formatPercent(item.percent) }} - 暂无奖品数据 + 暂无奖品数据 @@ -214,7 +227,7 @@ import { ref, computed } from 'vue' import { onLoad } from '@dcloudio/uni-app' import PaymentPopup from '../../../components/PaymentPopup.vue' -import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame } from '../../../api/appUser' +import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame, getIssueDrawLogs } from '../../../api/appUser' const detail = ref({}) const statusText = ref('') @@ -328,10 +341,33 @@ const currentIssueRewards = computed(() => { const m = rewardsMap.value || {} return (iid && Array.isArray(m[iid])) ? m[iid] : [] }) + +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 + 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 currentIssueTitle = computed(() => { const arr = issues.value || [] const cur = arr[selectedIssueIndex.value] - return (cur && (cur.title || ('第' + (cur.no || '-') + '期'))) || '-' + // 对对碰不显示“期数”,优先显示标题,兜底显示“奖池” + return (cur && (cur.title || '奖池')) || '-' }) const currentIssueStatusText = computed(() => { const arr = issues.value || [] @@ -502,7 +538,8 @@ 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) + boss: detectBoss(i), + level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏')) })) const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0) const enriched = items.map(it => ({ @@ -555,6 +592,10 @@ async function fetchIssues(id) { const latestId = pickLatestIssueId(issues.value) setSelectedById(latestId) await fetchRewardsForIssues(id) + // 获取购买记录 + if (currentIssueId.value) { + fetchWinRecords(id, currentIssueId.value) + } } function pickLatestIssueId(list) { @@ -596,12 +637,50 @@ function nextIssue() { syncResumeGame(activityId.value) } +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 formatPercent(v) { const n = Number(v) if (!Number.isFinite(n)) return '0%' 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 + return String.fromCharCode(64 + n) +} + function openRewardsPopup() { rewardsVisible.value = true } @@ -609,6 +688,18 @@ function closeRewardsPopup() { rewardsVisible.value = false } +function showRules() { + uni.showModal({ + title: '活动规则', + content: detail.value.rules || '1. 选择卡牌类型进行对对碰\\n2. 每次抽取随机获得奖品\\n3. 奖池与概率以页面展示为准', + showCancel: false + }) +} + +function goCabinet() { + uni.switchTab({ url: '/pages/cabinet/index' }) +} + function onPreviewBanner() { const url = detail.value.banner || '' if (url) uni.previewImage({ urls: [url], current: url }) @@ -1266,10 +1357,10 @@ onLoad((opts) => { } .header-info { flex: 1; + min-width: 0; display: flex; flex-direction: column; - justify-content: flex-start; - min-height: 180rpx; + justify-content: center; padding: 6rpx 0; } .header-title { @@ -1306,6 +1397,52 @@ onLoad((opts) => { border: 1rpx solid rgba($brand-primary, 0.1); } +.header-actions { + display: flex; + flex-direction: column; + gap: 28rpx; + margin-left: 16rpx; + padding-left: 24rpx; + border-left: 2rpx solid #E8E8E8; + justify-content: center; + align-self: stretch; +} +.action-btn { + display: flex; + flex-direction: column; + align-items: center; + &:active { + opacity: 0.6; + } +} +.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; +} + .section-container { margin: 0 $spacing-lg $spacing-lg; background: rgba(255, 255, 255, 0.9); @@ -1384,14 +1521,12 @@ onLoad((opts) => { .preview-scroll { white-space: nowrap; - margin: 0 -$spacing-lg; - padding: 0 $spacing-lg; - width: calc(100% + 40rpx); + width: 100%; } .preview-item { display: inline-block; - width: 160rpx; - margin-right: $spacing-lg; + width: 180rpx; + margin-right: $spacing-md; vertical-align: top; position: relative; transition: transform 0.2s; @@ -1401,12 +1536,12 @@ onLoad((opts) => { } &:last-child { - margin-right: 40rpx; + margin-right: 0; } } .preview-img { - width: 160rpx; - height: 160rpx; + width: 180rpx; + height: 180rpx; border-radius: $radius-lg; background: $bg-secondary; margin-bottom: $spacing-sm; @@ -1414,7 +1549,7 @@ onLoad((opts) => { border: 1rpx solid rgba(0,0,0,0.03); } .preview-name { - font-size: $font-sm; + font-size: $font-xs; color: $text-secondary; width: 100%; overflow: hidden; @@ -1423,6 +1558,47 @@ onLoad((opts) => { text-align: center; font-weight: 500; } + +.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; +} .prize-tag { position: absolute; top: 10rpx; @@ -1542,6 +1718,7 @@ onLoad((opts) => { font-weight: 900; background: $gradient-brand; -webkit-background-clip: text; + background-clip: text; -webkit-text-fill-color: transparent; display: inline-block; } @@ -1831,24 +2008,28 @@ onLoad((opts) => { .empty-icon { font-size: 80rpx; margin-bottom: $spacing-lg; opacity: 0.5; } .empty-text { font-size: $font-md; } -/* Float Bar */ +/* 底部悬浮操作栏 - 高级重置 */ .float-bar { position: fixed; - left: 0; right: 0; bottom: 0; - padding: $spacing-lg $spacing-xl; - padding-bottom: calc($spacing-lg + env(safe-area-inset-bottom)); - background: rgba(255, 255, 255, 0.9); - backdrop-filter: blur(20px); - box-shadow: 0 -8rpx 30rpx rgba(0, 0, 0, 0.05); + left: 32rpx; + right: 32rpx; + bottom: calc(40rpx + env(safe-area-inset-bottom)); z-index: 100; - animation: slideUp 0.4s ease-out backwards; + animation: slideUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) backwards; } + .float-bar-inner { + background: rgba(255, 255, 255, 0.85); + backdrop-filter: blur(30rpx); + padding: 24rpx 40rpx; + border-radius: 999rpx; display: flex; align-items: center; justify-content: space-between; - gap: $spacing-lg; + box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.12); + border: 1rpx solid rgba(255, 255, 255, 0.6); } + .float-price { display: flex; align-items: baseline; @@ -1856,9 +2037,9 @@ onLoad((opts) => { font-weight: 800; min-width: 0; } -.currency { font-size: $font-md; color: $brand-primary; } -.amount { font-size: 44rpx; margin: 0 6rpx; color: $brand-primary; font-family: 'DIN Alternate', sans-serif; } -.unit { font-size: $font-sm; color: $text-tertiary; font-weight: 600; } +.float-price .currency { font-size: 26rpx; margin-right: 4rpx; color: $brand-primary; } +.float-price .amount { font-size: 44rpx; font-weight: 900; font-family: 'DIN Alternate', sans-serif; color: $brand-primary; } +.float-price .unit { font-size: 24rpx; color: $text-sub; margin-left: 4rpx; font-weight: 600; } .rewards-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 9000; } .rewards-mask { @@ -1901,6 +2082,43 @@ onLoad((opts) => { 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; @@ -1978,44 +2196,57 @@ onLoad((opts) => { to { transform: translateY(0); opacity: 1; } } .action-btn { - height: 96rpx; - border-radius: $radius-round; + height: 88rpx; + line-height: 88rpx; + padding: 0 56rpx; + border-radius: 999rpx; + font-size: 30rpx; + font-weight: 900; display: flex; align-items: center; justify-content: center; - font-size: $font-xl; - font-weight: 800; + transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); position: relative; overflow: hidden; - transition: all 0.2s; &.primary { - background: $gradient-brand; - color: #fff; - box-shadow: $shadow-warm; - } - - &.secondary { - background: rgba($bg-card, 0.9); - color: $text-main; - border: 2rpx solid rgba($brand-primary, 0.25); - box-shadow: $shadow-sm; - padding: 0 40rpx; + background: $gradient-brand !important; + color: #fff !important; + box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.35); + + &::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: btnShine 4s infinite cubic-bezier(0.19, 1, 0.22, 1); + pointer-events: none; + } } - &:active { transform: scale(0.98); } -} -.btn-shine { - position: absolute; - top: 0; left: -100%; width: 50%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); - transform: skewX(-20deg); - animation: shine 3s infinite; + &.secondary { + background: #1A1A1A !important; + color: $accent-gold !important; + box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.15); + } + + &:active { + transform: scale(0.92); + } } -@keyframes shine { - 0% { left: -100%; } - 50%, 100% { left: 200%; } +@keyframes btnShine { + 0% { left: -150%; } + 100% { left: 150%; } } .flip-overlay { diff --git a/pages/activity/pata/index.vue b/pages/activity/pata/index.vue index 7f25c2f..79703a6 100644 --- a/pages/activity/pata/index.vue +++ b/pages/activity/pata/index.vue @@ -214,6 +214,7 @@ $local-gold: #FFD700; // 特殊金色,比全局更亮 text-shadow: 0 4rpx 16rpx rgba(0,0,0,0.6); background: linear-gradient(180deg, #fff, #b3b3b3); -webkit-background-clip: text; + background-clip: text; color: transparent; letter-spacing: 2rpx; } @@ -327,59 +328,71 @@ $local-gold: #FFD700; // 特殊金色,比全局更亮 } .action-area { - background: $bg-dark-card; - backdrop-filter: blur(40rpx); - padding: 24rpx 32rpx; - border-radius: 100rpx; + position: fixed; + left: 40rpx; + right: 40rpx; + bottom: calc(40rpx + env(safe-area-inset-bottom)); + background: rgba(26, 26, 26, 0.85); + backdrop-filter: blur(30rpx); + padding: 24rpx 40rpx; + border-radius: 999rpx; display: flex; align-items: center; justify-content: center; - border: 1px solid $border-dark; - box-shadow: 0 20rpx 60rpx rgba(0,0,0,0.5); - margin-bottom: calc(env(safe-area-inset-bottom) + 20rpx); - animation: slideUp 0.6s ease-out backwards; - animation-delay: 0.3s; + border: 1rpx solid rgba(255, 255, 255, 0.1); + box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.4); + z-index: 100; + animation: slideUp 0.6s cubic-bezier(0.23, 1, 0.32, 1) backwards; } .challenge-btn { - background: $gradient-brand; - color: #fff; + background: $gradient-brand !important; + color: #fff !important; font-weight: 900; - border-radius: 100rpx; + border-radius: 999rpx; padding: 0 60rpx; height: 88rpx; line-height: 88rpx; font-size: 32rpx; - box-shadow: 0 8rpx 24rpx rgba(255, 107, 0, 0.3); + box-shadow: 0 12rpx 32rpx rgba(255, 107, 0, 0.3); border: none; position: relative; overflow: hidden; - transition: all 0.2s; + transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275); width: 100%; - &::after { + &::before { content: ''; position: absolute; - top: 0; left: -100%; width: 100%; height: 100%; - background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent); - animation: shimmer 3s infinite; + 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: btnShine 4s infinite cubic-bezier(0.19, 1, 0.22, 1); + pointer-events: none; } &:active { - transform: scale(0.96); - box-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.2); + transform: scale(0.94); } &.disabled { - background: #555; - color: #999; + background: #333 !important; + color: #666 !important; box-shadow: none; - &::after { display: none; } + &::before { display: none; } } } -@keyframes shimmer { - 0% { left: -100%; } - 50%, 100% { left: 200%; } +@keyframes btnShine { + 0% { left: -150%; } + 100% { left: 150%; } } diff --git a/pages/activity/wuxianshang/index.vue b/pages/activity/wuxianshang/index.vue index e422b76..8927bcf 100644 --- a/pages/activity/wuxianshang/index.vue +++ b/pages/activity/wuxianshang/index.vue @@ -27,28 +27,71 @@ - 📋 - 规则 + + 规则 - 📦 - 盒柜 + + 盒柜 - - - 奖池一览 - 查看全部 - - - - {{ item.boss ? 'BOSS' : '赏' }} - - {{ item.title }} + + + + 本机奖池 + - + + 购买记录 + + + + + + + 奖池配置 + 查看全部 + + + + + {{ group.level }}赏 + 总概率 {{ group.totalPercent }}% + + + + {{ item.boss ? 'BOSS' : group.level }} + + {{ item.title }} + + + + + + 📭 + 暂无奖池配置 + + + + + + + + + {{ it.title }} + + x{{ it.count }} + + + + + + 📝 + 暂无购买记录 + + @@ -83,17 +126,25 @@ × - - - - - {{ item.title || '-' }} - BOSS + + + + {{ group.level }}赏 + 该档总概率 {{ group.totalPercent }}% + + + + + + {{ item.title || '-' }} + BOSS + + 单项概率 {{ formatPercent(item.percent) }} + - 概率 {{ formatPercent(item.percent) }} - 暂无奖池数据 + 暂无奖池数据 @@ -120,11 +171,13 @@ 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 } from '../../../api/appUser' +import { getActivityDetail, getActivityIssues, getActivityIssueRewards, joinLottery, createWechatOrder, getLotteryResult, getItemCards, getUserCoupons, getIssueDrawLogs } 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('') @@ -137,8 +190,8 @@ const coverUrl = computed(() => cleanUrl(detail.value && (detail.value.image || const currentIssueTitle = computed(() => { const arr = issues.value || [] const cur = arr[selectedIssueIndex.value] - const t = (cur && (cur.title || ('第' + (cur.no || '-') + '期'))) || '-' - return t + // 不显示“期数”,优先显示标题,兜底显示“奖池” + return (cur && (cur.title || '奖池')) || '-' }) const points = ref(0) const flipRef = ref(null) @@ -147,6 +200,28 @@ 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([]) @@ -169,6 +244,15 @@ 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 } @@ -235,7 +319,8 @@ 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) + boss: detectBoss(i), + level: levelToAlpha(i.prize_level ?? i.level ?? (detectBoss(i) ? 'BOSS' : '赏')) })) const total = items.reduce((acc, it) => acc + (it.weight > 0 ? it.weight : 0), 0) const enriched = items.map(it => ({ @@ -288,6 +373,10 @@ async function fetchIssues(id) { const latestId = pickLatestIssueId(issues.value) setSelectedById(latestId) await fetchRewardsForIssues(id) + // 获取购买记录 + if (currentIssueId.value) { + fetchWinRecords(id, currentIssueId.value) + } } function pickLatestIssueId(list) { @@ -328,6 +417,36 @@ 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 }) @@ -618,10 +737,10 @@ function closeFlip() { showFlip.value = false } } .header-info { flex: 1; + min-width: 0; display: flex; flex-direction: column; - justify-content: flex-start; - min-height: 180rpx; + justify-content: center; padding: 6rpx 0; } .header-title { @@ -660,29 +779,47 @@ function closeFlip() { showFlip.value = false } .header-actions { display: flex; flex-direction: column; - gap: $spacing-lg; - margin-left: 20rpx; - padding-left: $spacing-lg; - border-left: 1rpx solid rgba(0,0,0,0.06); + gap: 28rpx; + margin-left: 16rpx; + padding-left: 24rpx; + border-left: 2rpx solid #E8E8E8; justify-content: center; - height: 140rpx; + align-self: stretch; } .action-btn { display: flex; flex-direction: column; align-items: center; - font-size: $font-xs; - color: $text-sub; - transition: all 0.2s; &:active { - transform: scale(0.9); - color: $text-main; + opacity: 0.6; } } -.action-btn .icon { - font-size: $font-xl; - margin-bottom: 6rpx; - filter: grayscale(0.2); +.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; } .section-container { @@ -693,6 +830,39 @@ 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; @@ -733,31 +903,29 @@ function closeFlip() { showFlip.value = false } .preview-scroll { white-space: nowrap; - margin: 0 -$spacing-lg; - padding: 0 $spacing-lg; - width: calc(100% + 40rpx); + width: 100%; } .preview-item { display: inline-block; - width: 200rpx; - margin-right: $spacing-lg; + width: 180rpx; + margin-right: $spacing-md; vertical-align: top; position: relative; transition: transform 0.2s; &:active { transform: scale(0.96); } - &:last-child { margin-right: 40rpx; } + &:last-child { margin-right: 0; } } .preview-img { - width: 200rpx; - height: 200rpx; + width: 180rpx; + height: 180rpx; border-radius: $radius-lg; background: $bg-secondary; - margin-bottom: $spacing-md; + margin-bottom: $spacing-sm; box-shadow: $shadow-sm; border: 1rpx solid rgba(0,0,0,0.03); } .preview-name { - font-size: $font-sm; + font-size: $font-xs; color: $text-secondary; width: 100%; overflow: hidden; @@ -768,16 +936,16 @@ function closeFlip() { showFlip.value = false } } .prize-tag { position: absolute; - top: 10rpx; - left: 10rpx; + top: 8rpx; + left: 8rpx; background: rgba(0,0,0,0.6); color: #fff; - font-size: $font-xs; - padding: 4rpx $spacing-sm; - border-radius: $radius-sm; + font-size: $font-xxs; + padding: 2rpx $spacing-xs; + border-radius: 4rpx; z-index: 10; font-weight: 700; - backdrop-filter: blur(4rpx); + backdrop-filter: blur(4px); transform: scale(0.9); transform-origin: top left; } @@ -786,6 +954,47 @@ 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; @@ -893,6 +1102,7 @@ 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; @@ -997,23 +1207,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: $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); + 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); z-index: 999; - animation: slideUp $transition-slow $ease-out backwards; - border-top: 1rpx solid rgba(0,0,0,0.05); + border-top: 1rpx solid rgba(255, 255, 255, 0.8); } + .tier-btn { flex: 1; min-width: 0; @@ -1021,92 +1231,80 @@ function closeFlip() { showFlip.value = false } flex-direction: column; align-items: center; justify-content: center; - 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; + 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; &:active { - transform: scale(0.95); - background: $bg-page; + transform: scale(0.92); + background: #F9F9F9; + box-shadow: none; } } -.tier-price { - font-size: $font-lg; - font-weight: 800; - color: $text-main; - font-family: 'DIN Alternate', sans-serif; -} -.tier-label { - font-size: $font-xs; - color: $text-sub; - margin-top: 4rpx; - font-weight: 500; -} +.tier-price { + font-size: 34rpx; + font-weight: 900; + 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; +} + +/* 热门/最高档位 - 高级动效 */ .tier-hot { - background: $gradient-red; - border: none; - box-shadow: 0 6rpx 14rpx rgba($accent-red, 0.12); + background: $gradient-brand !important; + border: none !important; + box-shadow: 0 12rpx 32rpx rgba($brand-primary, 0.35) !important; position: relative; overflow: hidden; - border-radius: $radius-lg; - transform: translateZ(0); - .tier-price, .tier-label { - color: #fff !important; - position: relative; - z-index: 2; + .tier-price { + color: #FFF !important; + text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1); } + .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; - 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; + 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; 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 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; } +@keyframes hotSweep { + 0% { left: -150%; } + 100% { left: 150%; } } .rewards-overlay { position: fixed; left: 0; right: 0; top: 0; bottom: 0; z-index: 9000; } @@ -1150,6 +1348,43 @@ 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; @@ -1257,4 +1492,67 @@ 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; +} diff --git a/pages/activity/yifanshang/index.vue b/pages/activity/yifanshang/index.vue index 4788479..e117d29 100644 --- a/pages/activity/yifanshang/index.vue +++ b/pages/activity/yifanshang/index.vue @@ -38,30 +38,67 @@ - - 📋 - 规则 - - - 📦 - 盒柜 - + + + 规则 + + + + 盒柜 + - - - 赏品一览 - 查看全部 - - - - {{ item.boss ? 'BOSS' : (item.grade || '赏') }} - - {{ item.title }} + + + + 本机奖池 + - + + 购买记录 + + + + + + + 奖品配置 + 查看全部 + + + + + {{ item.boss ? 'BOSS' : (item.grade || '赏') }} + + {{ item.title }} + + + + + 📭 + 暂无奖品配置 + + + + + + + + + {{ it.title }} + + x{{ it.count }} + + + + + + 📝 + 暂无购买记录 + + @@ -142,7 +179,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 } from '../../../api/appUser' +import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getIssueDrawLogs } from '../../../api/appUser' const detail = ref({}) const issues = ref([]) @@ -379,6 +416,10 @@ async function fetchIssues(id) { const latestId = pickLatestIssueId(issues.value) setSelectedById(latestId) await fetchRewardsForIssues(id) + // 获取购买记录 + if (currentIssueId.value) { + fetchWinRecords(id, currentIssueId.value) + } } function pickLatestIssueId(list) { @@ -419,7 +460,35 @@ 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 || '' @@ -612,10 +681,10 @@ onUnload(() => { } .header-info { flex: 1; + min-width: 0; display: flex; flex-direction: column; - justify-content: flex-start; - min-height: 180rpx; + justify-content: center; padding: 6rpx 0; } .header-title { @@ -672,30 +741,47 @@ onUnload(() => { .header-actions { display: flex; flex-direction: column; - gap: $spacing-lg; - margin-left: 20rpx; - padding-left: $spacing-lg; - border-left: 1rpx solid rgba(0,0,0,0.06); + gap: 28rpx; + margin-left: 16rpx; + padding-left: 24rpx; + border-left: 2rpx solid #E8E8E8; justify-content: center; - height: 140rpx; + align-self: stretch; } .action-btn { display: flex; flex-direction: column; align-items: center; - font-size: $font-xs; - color: $text-sub; - transition: all 0.2s; - &:active { - transform: scale(0.9); - color: $text-main; + opacity: 0.6; } } -.action-btn .icon { - font-size: $font-xl; - margin-bottom: 6rpx; - filter: grayscale(0.2); +.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; } /* 通用板块容器 */ @@ -1044,4 +1130,99 @@ 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; +} diff --git a/pages/game/minesweeper/index.vue b/pages/game/minesweeper/index.vue index 6e80179..656802c 100644 --- a/pages/game/minesweeper/index.vue +++ b/pages/game/minesweeper/index.vue @@ -115,13 +115,16 @@ export default { } }) - // 构建游戏URL + // 构建游戏URL,传递安全认证参数 const gameBaseUrl = 'https://game.1024tool.vip' - const gameUrl = `${gameBaseUrl}?ticket=${res.ticket_token}` + const gameToken = encodeURIComponent(res.game_token) + const nakamaServer = encodeURIComponent(res.nakama_server) + const nakamaKey = encodeURIComponent(res.nakama_key) + const gameUrl = `${gameBaseUrl}?game_token=${gameToken}&nakama_server=${nakamaServer}&nakama_key=${nakamaKey}` // 跳转到webview uni.navigateTo({ - url: `/pages/game/webview?url=${encodeURIComponent(gameUrl)}&ticket=${res.ticket_token}` + url: `/pages/game/webview?url=${encodeURIComponent(gameUrl)}` }) } catch (e) { uni.showToast({ diff --git a/pages/index/index.vue b/pages/index/index.vue index 9798364..4b87073 100644 --- a/pages/index/index.vue +++ b/pages/index/index.vue @@ -1,7 +1,11 @@