feat:新增开屏动画,新增支付祝福动画,奖品目前按照权重升序,避免了S赏放最后的问题。
This commit is contained in:
parent
0bd10c6a0d
commit
28e0721e3f
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@ node_modules/
|
|||||||
*.log
|
*.log
|
||||||
*.tmp
|
*.tmp
|
||||||
*.swp
|
*.swp
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
28
App.vue
28
App.vue
@ -1,20 +1,20 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
onLaunch: function(options) {
|
onLaunch: function(options) {
|
||||||
console.log('App Launch', options)
|
console.log('App Launch', options)
|
||||||
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
try { uni.setStorageSync('app_session_id', String(Date.now())) } catch (_) {}
|
||||||
if (options && options.query && options.query.invite_code) {
|
if (options && options.query && options.query.invite_code) {
|
||||||
console.log('App Launch captured invite_code:', options.query.invite_code)
|
console.log('App Launch captured invite_code:', options.query.invite_code)
|
||||||
try { uni.setStorageSync('inviter_code', options.query.invite_code) } catch (e) { console.error('Save invite code failed', e) }
|
try { uni.setStorageSync('inviter_code', options.query.invite_code) } catch (e) { console.error('Save invite code failed', e) }
|
||||||
}
|
|
||||||
},
|
|
||||||
onShow: function() {
|
|
||||||
console.log('App Show')
|
|
||||||
},
|
|
||||||
onHide: function() {
|
|
||||||
console.log('App Hide')
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
onShow: function() {
|
||||||
|
console.log('App Show')
|
||||||
|
},
|
||||||
|
onHide: function() {
|
||||||
|
console.log('App Hide')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|||||||
251
components/BlessingAnimation.vue
Normal file
251
components/BlessingAnimation.vue
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="blessing-container">
|
||||||
|
<view class="blessing-animation" :class="currentBlessing.type">
|
||||||
|
<view class="blessing-emoji">{{ currentBlessing.emoji }}</view>
|
||||||
|
<view v-if="currentBlessing.type === 'sheep'" class="blessing-subtitle">小羊祝你</view>
|
||||||
|
<view class="blessing-text">
|
||||||
|
<text v-for="(char, index) in currentBlessing.chars"
|
||||||
|
:key="index"
|
||||||
|
class="char"
|
||||||
|
:class="{ 'from-left': index % 2 === 0, 'from-right': index % 2 === 1 }"
|
||||||
|
:style="{ animationDelay: index * 0.15 + 's' }">
|
||||||
|
{{ char }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { registerBlessing, unregisterBlessing } from '@/utils/blessing.js'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const blessings = [
|
||||||
|
{
|
||||||
|
emoji: '🐏',
|
||||||
|
chars: ['三', '羊', '开', '泰'],
|
||||||
|
type: 'sheep'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: '🐴',
|
||||||
|
chars: ['马', '到', '功', '成'],
|
||||||
|
type: 'horse'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const currentBlessing = ref(blessings[0])
|
||||||
|
|
||||||
|
const handleShow = ({ type }) => {
|
||||||
|
console.log('[BlessingAnimation] handleShow 被调用, type:', type)
|
||||||
|
|
||||||
|
// 根据类型选择祝福语
|
||||||
|
let index = 0
|
||||||
|
if (type === 'random') {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
} else if (type === 'sheep') {
|
||||||
|
index = 0
|
||||||
|
} else if (type === 'horse') {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlessing.value = blessings[index]
|
||||||
|
visible.value = true
|
||||||
|
|
||||||
|
console.log('[BlessingAnimation] 显示祝福:', currentBlessing.value)
|
||||||
|
|
||||||
|
// 3秒后自动隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
console.log('[BlessingAnimation] 隐藏祝福')
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('[BlessingAnimation] 组件已挂载,准备注册监听器')
|
||||||
|
registerBlessing(handleShow)
|
||||||
|
console.log('[BlessingAnimation] 监听器注册完成')
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
console.log('[BlessingAnimation] 组件即将卸载')
|
||||||
|
unregisterBlessing()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.blessing-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 10000; // 提高到比支付弹窗(999)更高的层级
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-animation {
|
||||||
|
text-align: center;
|
||||||
|
animation: blessingFadeIn 0.5s ease-out;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(255, 248, 243, 0.98));
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 48rpx rgba(255, 107, 0, 0.3);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
border: 2rpx solid rgba(255, 159, 67, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blessingFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-emoji {
|
||||||
|
font-size: 100rpx;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小羊动画 - 弹跳出现
|
||||||
|
.blessing-animation.sheep .blessing-emoji {
|
||||||
|
animation: emojiBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小马动画 - 从左边跑步进场
|
||||||
|
.blessing-animation.horse .blessing-emoji {
|
||||||
|
animation: emojiRun 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) rotate(-180deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2) rotate(10deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiRun {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-300rpx) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(30rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateX(-15rpx) scale(0.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-subtitle {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #FF9500;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
opacity: 0;
|
||||||
|
animation: subtitleFadeIn 0.5s ease-out 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subtitleFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
|
||||||
|
.char {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #FF6B00;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3);
|
||||||
|
opacity: 0;
|
||||||
|
animation: charAppear 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从左边出现
|
||||||
|
.char.from-left {
|
||||||
|
animation-name: charAppearFromLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从右边出现
|
||||||
|
.char.from-right {
|
||||||
|
animation-name: charAppearFromRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppear {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-8rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(-10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
233
components/BlessingFloat.vue
Normal file
233
components/BlessingFloat.vue
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="blessing-float" :class="{ 'hide': shouldHide }" @tap="handleClose">
|
||||||
|
<view class="blessing-content">
|
||||||
|
<view class="blessing-emoji">{{ currentBlessing.emoji }}</view>
|
||||||
|
<view v-if="currentBlessing.type === 'sheep'" class="blessing-subtitle">小羊祝你</view>
|
||||||
|
<view class="blessing-text">
|
||||||
|
<text v-for="(char, index) in currentBlessing.chars"
|
||||||
|
:key="index"
|
||||||
|
class="char"
|
||||||
|
:class="{ 'from-left': index % 2 === 0, 'from-right': index % 2 === 1 }"
|
||||||
|
:style="{ animationDelay: index * 0.15 + 's' }">
|
||||||
|
{{ char }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { registerBlessing, unregisterBlessing } from '@/utils/blessing.js'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const shouldHide = ref(false)
|
||||||
|
const blessings = [
|
||||||
|
{
|
||||||
|
emoji: '🐏',
|
||||||
|
chars: ['三', '羊', '开', '泰'],
|
||||||
|
type: 'sheep'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: '🐴',
|
||||||
|
chars: ['马', '到', '功', '成'],
|
||||||
|
type: 'horse'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const currentBlessing = ref(blessings[0])
|
||||||
|
|
||||||
|
const handleShow = ({ type }) => {
|
||||||
|
console.log('[BlessingFloat] handleShow 被调用, type:', type)
|
||||||
|
|
||||||
|
// 根据类型选择祝福语
|
||||||
|
let index = 0
|
||||||
|
if (type === 'random') {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
} else if (type === 'sheep') {
|
||||||
|
index = 0
|
||||||
|
} else if (type === 'horse') {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlessing.value = blessings[index]
|
||||||
|
visible.value = true
|
||||||
|
shouldHide.value = false
|
||||||
|
|
||||||
|
console.log('[BlessingFloat] 显示祝福:', currentBlessing.value)
|
||||||
|
|
||||||
|
// 3秒后自动隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
shouldHide.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
console.log('[BlessingFloat] 隐藏祝福')
|
||||||
|
}, 300)
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
shouldHide.value = true
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
console.log('[BlessingFloat] 用户点击关闭')
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('[BlessingFloat] 组件已挂载,准备注册监听器')
|
||||||
|
registerBlessing(handleShow)
|
||||||
|
console.log('[BlessingFloat] 监听器注册完成')
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
console.log('[BlessingFloat] 组件即将卸载')
|
||||||
|
unregisterBlessing()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.blessing-float {
|
||||||
|
position: fixed;
|
||||||
|
top: 200rpx;
|
||||||
|
right: 30rpx;
|
||||||
|
z-index: 99999;
|
||||||
|
animation: floatIn 0.5s ease-out;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&.hide {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50rpx) scale(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes floatIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-50rpx) scale(0.8);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-content {
|
||||||
|
background: linear-gradient(135deg, #FF6B00 0%, #FF9500 100%);
|
||||||
|
padding: 24rpx 20rpx;
|
||||||
|
border-radius: 20rpx;
|
||||||
|
box-shadow: 0 8rpx 24rpx rgba(255, 107, 0, 0.4);
|
||||||
|
text-align: center;
|
||||||
|
min-width: 200rpx;
|
||||||
|
backdrop-filter: blur(10rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-emoji {
|
||||||
|
font-size: 60rpx;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: block;
|
||||||
|
animation: emojiBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) rotate(-180deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2) rotate(10deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-subtitle {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 6rpx;
|
||||||
|
opacity: 0;
|
||||||
|
animation: subtitleFadeIn 0.5s ease-out 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subtitleFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8rpx;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
|
||||||
|
.char {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #FFFFFF;
|
||||||
|
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||||
|
opacity: 0;
|
||||||
|
animation: charAppear 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char.from-left {
|
||||||
|
animation-name: charAppearFromLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char.from-right {
|
||||||
|
animation-name: charAppearFromRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppear {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-4rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-40rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(6rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(40rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(-6rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
267
components/BlessingPopup.vue
Normal file
267
components/BlessingPopup.vue
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="blessing-popup-mask" @tap="handleClose">
|
||||||
|
<view class="blessing-popup-content" @tap.stop>
|
||||||
|
<view class="blessing-emoji">{{ currentBlessing.emoji }}</view>
|
||||||
|
<view v-if="currentBlessing.type === 'sheep'" class="blessing-subtitle">小羊祝你</view>
|
||||||
|
<view class="blessing-text">
|
||||||
|
<text v-for="(char, index) in currentBlessing.chars"
|
||||||
|
:key="index"
|
||||||
|
class="char"
|
||||||
|
:class="{ 'from-left': index % 2 === 0, 'from-right': index % 2 === 1 }"
|
||||||
|
:style="{ animationDelay: index * 0.15 + 's' }">
|
||||||
|
{{ char }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { registerBlessing, unregisterBlessing } from '@/utils/blessing.js'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const blessings = [
|
||||||
|
{
|
||||||
|
emoji: '🐏',
|
||||||
|
chars: ['三', '羊', '开', '泰'],
|
||||||
|
type: 'sheep'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: '🐴',
|
||||||
|
chars: ['马', '到', '功', '成'],
|
||||||
|
type: 'horse'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const currentBlessing = ref(blessings[0])
|
||||||
|
|
||||||
|
const handleShow = ({ type }) => {
|
||||||
|
console.log('[BlessingPopup] handleShow 被调用, type:', type)
|
||||||
|
|
||||||
|
// 根据类型选择祝福语
|
||||||
|
let index = 0
|
||||||
|
if (type === 'random') {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
} else if (type === 'sheep') {
|
||||||
|
index = 0
|
||||||
|
} else if (type === 'horse') {
|
||||||
|
index = 1
|
||||||
|
} else {
|
||||||
|
index = Math.floor(Math.random() * blessings.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBlessing.value = blessings[index]
|
||||||
|
visible.value = true
|
||||||
|
|
||||||
|
console.log('[BlessingPopup] 显示祝福:', currentBlessing.value)
|
||||||
|
|
||||||
|
// 3秒后自动隐藏
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
console.log('[BlessingPopup] 隐藏祝福')
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
visible.value = false
|
||||||
|
console.log('[BlessingPopup] 用户点击关闭')
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
console.log('[BlessingPopup] 组件已挂载,准备注册监听器')
|
||||||
|
registerBlessing(handleShow)
|
||||||
|
console.log('[BlessingPopup] 监听器注册完成')
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
console.log('[BlessingPopup] 组件即将卸载')
|
||||||
|
unregisterBlessing()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.blessing-popup-mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 10000; // 比支付弹窗(999)高
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: maskFadeIn 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes maskFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-popup-content {
|
||||||
|
width: 600rpx;
|
||||||
|
max-width: 90%;
|
||||||
|
background: rgba(255, 255, 255, 0.98);
|
||||||
|
border-radius: 32rpx;
|
||||||
|
padding: 60rpx 40rpx;
|
||||||
|
text-align: center;
|
||||||
|
animation: popupScaleIn 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.2);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes popupScaleIn {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-emoji {
|
||||||
|
font-size: 140rpx;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 24rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小羊动画 - 弹跳出现
|
||||||
|
.blessing-popup-content:has(.blessing-subtitle) .blessing-emoji {
|
||||||
|
animation: emojiBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小马动画 - 从左边跑步进场
|
||||||
|
.blessing-popup-content:not(:has(.blessing-subtitle)) .blessing-emoji {
|
||||||
|
animation: emojiRun 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) rotate(-180deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2) rotate(10deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiRun {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-300rpx) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(30rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateX(-15rpx) scale(0.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-subtitle {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #FF9500;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
opacity: 0;
|
||||||
|
animation: subtitleFadeIn 0.5s ease-out 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subtitleFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16rpx;
|
||||||
|
margin-top: 24rpx;
|
||||||
|
|
||||||
|
.char {
|
||||||
|
font-size: 56rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #FF6B00;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3);
|
||||||
|
opacity: 0;
|
||||||
|
animation: charAppear 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从左边出现
|
||||||
|
.char.from-left {
|
||||||
|
animation-name: charAppearFromLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从右边出现
|
||||||
|
.char.from-right {
|
||||||
|
animation-name: charAppearFromRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppear {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-8rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(-10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,74 +1,94 @@
|
|||||||
<template>
|
<template>
|
||||||
<view v-if="visible" class="payment-popup-mask" @tap="handleMaskClick">
|
<view>
|
||||||
<view class="payment-popup-content" @tap.stop>
|
<!-- 祝福动画 -->
|
||||||
<!-- 顶部提示 -->
|
<view v-if="showBlessing" class="blessing-container">
|
||||||
<view class="risk-warning">
|
<view class="blessing-animation">
|
||||||
<text>盲盒具有随机性,请理性消费,购买即表示同意</text>
|
<view class="blessing-emoji">{{ currentBlessing.emoji }}</view>
|
||||||
<text class="agreement-link" @tap="openAgreement">《购买协议》</text>
|
<view v-if="currentBlessing.type === 'sheep'" class="blessing-subtitle">小羊祝你</view>
|
||||||
</view>
|
<view class="blessing-text">
|
||||||
|
<text v-for="(char, index) in currentBlessing.chars"
|
||||||
<view class="popup-header">
|
:key="index"
|
||||||
<text class="popup-title">确认支付</text>
|
class="char"
|
||||||
<view class="close-icon" @tap="handleClose">×</view>
|
:class="{ 'from-left': index % 2 === 0, 'from-right': index % 2 === 1 }"
|
||||||
</view>
|
:style="{ animationDelay: index * 0.15 + 's' }">
|
||||||
|
{{ char }}
|
||||||
<view class="popup-body">
|
</text>
|
||||||
<view class="amount-section" v-if="amount !== undefined && amount !== null">
|
|
||||||
<text class="label">支付金额</text>
|
|
||||||
<text class="amount">¥{{ finalPayAmount }}</text>
|
|
||||||
<text v-if="finalPayAmount < amount" class="original-amount" style="text-decoration: line-through; color: #999; font-size: 24rpx; margin-left: 10rpx;">¥{{ amount }}</text>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item">
|
|
||||||
<text class="label">优惠券</text>
|
|
||||||
<picker
|
|
||||||
class="picker-full"
|
|
||||||
mode="selector"
|
|
||||||
:range="coupons"
|
|
||||||
range-key="name"
|
|
||||||
@change="onCouponChange"
|
|
||||||
:value="couponIndex"
|
|
||||||
:disabled="!coupons || coupons.length === 0"
|
|
||||||
>
|
|
||||||
<view class="picker-display">
|
|
||||||
<text v-if="selectedCoupon" class="selected-text">
|
|
||||||
{{ selectedCoupon.name }} (-¥{{ effectiveCouponDiscount.toFixed(2) }})
|
|
||||||
<text v-if="selectedCoupon.amount > maxDeductible" style="font-size: 20rpx; color: #FF9800;">(最高抵扣50%)</text>
|
|
||||||
</text>
|
|
||||||
<text v-else-if="!coupons || coupons.length === 0" class="placeholder">暂无优惠券可用</text>
|
|
||||||
<text v-else class="placeholder">请选择优惠券</text>
|
|
||||||
<text class="arrow"></text>
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<view class="form-item" v-if="showCards">
|
|
||||||
<text class="label">道具卡</text>
|
|
||||||
<picker
|
|
||||||
class="picker-full"
|
|
||||||
mode="selector"
|
|
||||||
:range="displayCards"
|
|
||||||
range-key="displayName"
|
|
||||||
@change="onCardChange"
|
|
||||||
:value="cardIndex"
|
|
||||||
:disabled="!displayCards || displayCards.length === 0"
|
|
||||||
>
|
|
||||||
<view class="picker-display">
|
|
||||||
<text v-if="selectedCard" class="selected-text">
|
|
||||||
{{ selectedCard.name }}
|
|
||||||
<text v-if="Number(selectedCard.count) > 1" style="color: #999; font-size: 24rpx; margin-left: 6rpx;">(拥有: {{ selectedCard.count }})</text>
|
|
||||||
</text>
|
|
||||||
<text v-else-if="!displayCards || displayCards.length === 0" class="placeholder">暂无道具卡可用</text>
|
|
||||||
<text v-else class="placeholder">请选择道具卡</text>
|
|
||||||
<text class="arrow"></text>
|
|
||||||
</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
<view class="popup-footer">
|
<!-- 支付弹窗 -->
|
||||||
<button class="btn-cancel" @tap="handleClose">取消</button>
|
<view v-if="visible" class="payment-popup-mask" @tap="handleMaskClick">
|
||||||
<button class="btn-confirm" @tap="handleConfirm">确认支付</button>
|
<view class="payment-popup-content" @tap.stop>
|
||||||
|
<!-- 顶部提示 -->
|
||||||
|
<view class="risk-warning">
|
||||||
|
<text>盲盒具有随机性,请理性消费,购买即表示同意</text>
|
||||||
|
<text class="agreement-link" @tap="openAgreement">《购买协议》</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-header">
|
||||||
|
<text class="popup-title">确认支付</text>
|
||||||
|
<view class="close-icon" @tap="handleClose">×</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-body">
|
||||||
|
<view class="amount-section" v-if="amount !== undefined && amount !== null">
|
||||||
|
<text class="label">支付金额</text>
|
||||||
|
<text class="amount">¥{{ finalPayAmount }}</text>
|
||||||
|
<text v-if="finalPayAmount < amount" class="original-amount" style="text-decoration: line-through; color: #999; font-size: 24rpx; margin-left: 10rpx;">¥{{ amount }}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item">
|
||||||
|
<text class="label">优惠券</text>
|
||||||
|
<picker
|
||||||
|
class="picker-full"
|
||||||
|
mode="selector"
|
||||||
|
:range="coupons"
|
||||||
|
range-key="name"
|
||||||
|
@change="onCouponChange"
|
||||||
|
:value="couponIndex"
|
||||||
|
:disabled="!coupons || coupons.length === 0"
|
||||||
|
>
|
||||||
|
<view class="picker-display">
|
||||||
|
<text v-if="selectedCoupon" class="selected-text">
|
||||||
|
{{ selectedCoupon.name }} (-¥{{ effectiveCouponDiscount.toFixed(2) }})
|
||||||
|
<text v-if="selectedCoupon.amount > maxDeductible" style="font-size: 20rpx; color: #FF9800;">(最高抵扣50%)</text>
|
||||||
|
</text>
|
||||||
|
<text v-else-if="!coupons || coupons.length === 0" class="placeholder">暂无优惠券可用</text>
|
||||||
|
<text v-else class="placeholder">请选择优惠券</text>
|
||||||
|
<text class="arrow"></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="form-item" v-if="showCards">
|
||||||
|
<text class="label">道具卡</text>
|
||||||
|
<picker
|
||||||
|
class="picker-full"
|
||||||
|
mode="selector"
|
||||||
|
:range="displayCards"
|
||||||
|
range-key="displayName"
|
||||||
|
@change="onCardChange"
|
||||||
|
:value="cardIndex"
|
||||||
|
:disabled="!displayCards || displayCards.length === 0"
|
||||||
|
>
|
||||||
|
<view class="picker-display">
|
||||||
|
<text v-if="selectedCard" class="selected-text">
|
||||||
|
{{ selectedCard.name }}
|
||||||
|
<text v-if="Number(selectedCard.count) > 1" style="color: #999; font-size: 24rpx; margin-left: 6rpx;">(拥有: {{ selectedCard.count }})</text>
|
||||||
|
</text>
|
||||||
|
<text v-else-if="!displayCards || displayCards.length === 0" class="placeholder">暂无道具卡可用</text>
|
||||||
|
<text v-else class="placeholder">请选择道具卡</text>
|
||||||
|
<text class="arrow"></text>
|
||||||
|
</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="popup-footer">
|
||||||
|
<button class="btn-cancel" @tap="handleClose">取消</button>
|
||||||
|
<button class="btn-confirm" @tap="handleConfirm">确认支付</button>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -87,6 +107,46 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
|
const emit = defineEmits(['update:visible', 'confirm', 'cancel'])
|
||||||
|
|
||||||
|
// 祝福动画相关
|
||||||
|
const showBlessing = ref(false)
|
||||||
|
const blessings = [
|
||||||
|
{
|
||||||
|
emoji: '🐏',
|
||||||
|
chars: ['三', '羊', '开', '泰'],
|
||||||
|
type: 'sheep'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: '🐴',
|
||||||
|
chars: ['马', '到', '功', '成'],
|
||||||
|
type: 'horse'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
const currentBlessing = ref(blessings[0])
|
||||||
|
|
||||||
|
// 监听弹窗打开,显示祝福动画
|
||||||
|
watch(() => props.visible, (newVal) => {
|
||||||
|
console.log('[PaymentPopup] visible changed:', newVal)
|
||||||
|
if (newVal) {
|
||||||
|
// 随机选择祝福语
|
||||||
|
const index = Math.floor(Math.random() * blessings.length)
|
||||||
|
currentBlessing.value = blessings[index]
|
||||||
|
|
||||||
|
// 延迟显示祝福动画
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[PaymentPopup] 显示祝福动画')
|
||||||
|
showBlessing.value = true
|
||||||
|
|
||||||
|
// 3秒后隐藏祝福动画
|
||||||
|
setTimeout(() => {
|
||||||
|
showBlessing.value = false
|
||||||
|
console.log('[PaymentPopup] 隐藏祝福动画')
|
||||||
|
}, 3000)
|
||||||
|
}, 300) // 延迟300ms,让支付弹窗先滑入
|
||||||
|
} else {
|
||||||
|
showBlessing.value = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const couponIndex = ref(-1)
|
const couponIndex = ref(-1)
|
||||||
const cardIndex = ref(-1)
|
const cardIndex = ref(-1)
|
||||||
|
|
||||||
@ -97,8 +157,6 @@ const selectedCoupon = computed(() => {
|
|||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const maxDeductible = computed(() => {
|
const maxDeductible = computed(() => {
|
||||||
const amt = Number(props.amount) || 0
|
const amt = Number(props.amount) || 0
|
||||||
return amt * 0.5
|
return amt * 0.5
|
||||||
@ -118,9 +176,6 @@ const displayCards = computed(() => {
|
|||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
// Auto-select if only one card available? Or existing logic is fine.
|
|
||||||
// The watch logic handles index reset.
|
|
||||||
|
|
||||||
const selectedCard = computed(() => {
|
const selectedCard = computed(() => {
|
||||||
if (cardIndex.value >= 0 && displayCards.value[cardIndex.value]) {
|
if (cardIndex.value >= 0 && displayCards.value[cardIndex.value]) {
|
||||||
return displayCards.value[cardIndex.value]
|
return displayCards.value[cardIndex.value]
|
||||||
@ -138,10 +193,6 @@ watch(
|
|||||||
([vis, len]) => {
|
([vis, len]) => {
|
||||||
if (!vis) return
|
if (!vis) return
|
||||||
cardIndex.value = -1
|
cardIndex.value = -1
|
||||||
// Auto-select best coupon?
|
|
||||||
// Current logic just selects the first one if none selected and count > 0?
|
|
||||||
// "if (couponIndex.value < 0) couponIndex.value = 0"
|
|
||||||
// This logic is preserved below.
|
|
||||||
if (len <= 0) {
|
if (len <= 0) {
|
||||||
couponIndex.value = -1
|
couponIndex.value = -1
|
||||||
return
|
return
|
||||||
@ -163,7 +214,7 @@ function onCardChange(e) {
|
|||||||
|
|
||||||
function openAgreement() {
|
function openAgreement() {
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages-user/agreement/purchase' // 假设协议页面路径,如果没有请替换为实际路径
|
url: '/pages-user/agreement/purchase'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +236,182 @@ function handleConfirm() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
/* ============================================
|
||||||
|
祝福动画样式
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.blessing-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 10000;
|
||||||
|
pointer-events: none;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-animation {
|
||||||
|
text-align: center;
|
||||||
|
animation: blessingFadeIn 0.5s ease-out;
|
||||||
|
background: linear-gradient(135deg, rgba(255, 255, 255, 0.98), rgba(255, 248, 243, 0.98));
|
||||||
|
padding: 40rpx 30rpx;
|
||||||
|
border-radius: 24rpx;
|
||||||
|
box-shadow: 0 12rpx 48rpx rgba(255, 107, 0, 0.3);
|
||||||
|
backdrop-filter: blur(20rpx);
|
||||||
|
border: 2rpx solid rgba(255, 159, 67, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blessingFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-30rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-emoji {
|
||||||
|
font-size: 100rpx;
|
||||||
|
line-height: 1;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小羊动画 - 弹跳出现
|
||||||
|
.blessing-animation:has(.blessing-subtitle) .blessing-emoji {
|
||||||
|
animation: emojiBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 小马动画 - 从左边跑步进场
|
||||||
|
.blessing-animation:not(:has(.blessing-subtitle)) .blessing-emoji {
|
||||||
|
animation: emojiRun 0.8s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiBounce {
|
||||||
|
0% {
|
||||||
|
transform: scale(0) rotate(-180deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.2) rotate(10deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1) rotate(0deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes emojiRun {
|
||||||
|
0% {
|
||||||
|
transform: translateX(-300rpx) scale(0.8);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(30rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
80% {
|
||||||
|
transform: translateX(-15rpx) scale(0.95);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-subtitle {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #FF9500;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 12rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
opacity: 0;
|
||||||
|
animation: subtitleFadeIn 0.5s ease-out 0.3s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subtitleFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.blessing-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12rpx;
|
||||||
|
margin-top: 16rpx;
|
||||||
|
|
||||||
|
.char {
|
||||||
|
font-size: 48rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #FF6B00;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(255, 107, 0, 0.3);
|
||||||
|
opacity: 0;
|
||||||
|
animation: charAppear 0.6s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char.from-left {
|
||||||
|
animation-name: charAppearFromLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
.char.from-right {
|
||||||
|
animation-name: charAppearFromRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppear {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateY(-8rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromLeft {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes charAppearFromRight {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(80rpx) scale(0.5);
|
||||||
|
}
|
||||||
|
60% {
|
||||||
|
transform: translateX(-10rpx) scale(1.1);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
柯大鸭潮玩 - 支付弹窗组件
|
柯大鸭潮玩 - 支付弹窗组件
|
||||||
采用暖橙色调的底部弹窗设计
|
采用暖橙色调的底部弹窗设计
|
||||||
@ -210,7 +437,9 @@ function handleConfirm() {
|
|||||||
padding-bottom: calc($spacing-lg + constant(safe-area-inset-bottom));
|
padding-bottom: calc($spacing-lg + constant(safe-area-inset-bottom));
|
||||||
padding-bottom: calc($spacing-lg + env(safe-area-inset-bottom));
|
padding-bottom: calc($spacing-lg + env(safe-area-inset-bottom));
|
||||||
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes slideUp {
|
@keyframes slideUp {
|
||||||
from { transform: translateY(100%); }
|
from { transform: translateY(100%); }
|
||||||
to { transform: translateY(0); }
|
to { transform: translateY(0); }
|
||||||
|
|||||||
188
components/SplashScreen.vue
Normal file
188
components/SplashScreen.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<view v-if="visible" class="splash-screen" :class="{ 'fade-out': fadingOut }">
|
||||||
|
<view class="splash-content">
|
||||||
|
<view class="logo-wrapper">
|
||||||
|
<image class="logo-img" :src="logoUrl" mode="aspectFit" />
|
||||||
|
</view>
|
||||||
|
<view class="slogan-wrapper">
|
||||||
|
<text class="slogan-text">{{ sloganText }}</text>
|
||||||
|
</view>
|
||||||
|
<view class="loading-dots">
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
<view class="dot"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const visible = ref(false)
|
||||||
|
const fadingOut = ref(false)
|
||||||
|
const sloganText = ref('没有套路的真盲盒,就在柯大鸭')
|
||||||
|
const logoUrl = ref('/static/logo.png')
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 获取今天的日期字符串(YYYY-MM-DD格式)
|
||||||
|
const today = new Date()
|
||||||
|
const dateStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
|
||||||
|
|
||||||
|
// 获取今天的显示记录
|
||||||
|
const splashKey = `splash_count_${dateStr}`
|
||||||
|
const todayCount = uni.getStorageSync(splashKey) || 0
|
||||||
|
|
||||||
|
// 每天前10次启动显示开屏动画
|
||||||
|
if (todayCount < 10) {
|
||||||
|
// 显示开屏动画
|
||||||
|
visible.value = true
|
||||||
|
|
||||||
|
// 更新今天的显示次数
|
||||||
|
uni.setStorageSync(splashKey, todayCount + 1)
|
||||||
|
|
||||||
|
// 停留5秒后开始淡出动画
|
||||||
|
setTimeout(() => {
|
||||||
|
fadingOut.value = true
|
||||||
|
// 淡出动画持续0.6秒,然后隐藏组件
|
||||||
|
setTimeout(() => {
|
||||||
|
visible.value = false
|
||||||
|
}, 600)
|
||||||
|
}, 5000)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.splash-screen {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
background: linear-gradient(135deg, #FF6B00 0%, #FF9500 100%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: opacity 0.6s ease-out, visibility 0.6s ease-out;
|
||||||
|
|
||||||
|
&.fade-out {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 60rpx;
|
||||||
|
animation: splashContentIn 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes splashContentIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8) translateY(40rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-wrapper {
|
||||||
|
margin-bottom: 60rpx;
|
||||||
|
animation: logoFloat 2s ease-in-out infinite;
|
||||||
|
|
||||||
|
.logo-img {
|
||||||
|
width: 200rpx;
|
||||||
|
height: 200rpx;
|
||||||
|
border-radius: 40rpx;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes logoFloat {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-15rpx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan-wrapper {
|
||||||
|
margin-bottom: 80rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
.slogan-text {
|
||||||
|
font-size: 44rpx;
|
||||||
|
font-weight: 900;
|
||||||
|
color: #ffffff;
|
||||||
|
letter-spacing: 2rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.15);
|
||||||
|
animation: sloganFadeIn 1s ease-out 0.3s both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sloganFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20rpx);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-dots {
|
||||||
|
display: flex;
|
||||||
|
gap: 16rpx;
|
||||||
|
animation: dotsFadeIn 0.6s ease-out 0.6s both;
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 16rpx;
|
||||||
|
height: 16rpx;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
box-shadow: 0 4rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
animation: dotBounce 1.4s ease-in-out infinite;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
&:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
&:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotsFadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dotBounce {
|
||||||
|
0%, 80%, 100% {
|
||||||
|
transform: scale(0.6);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -327,14 +327,14 @@ const rewardGroups = computed(() => {
|
|||||||
return Object.keys(groups).sort((a, b) => {
|
return Object.keys(groups).sort((a, b) => {
|
||||||
if (a === 'Last' || a === 'BOSS') return -1
|
if (a === 'Last' || a === 'BOSS') return -1
|
||||||
if (b === 'Last' || b === 'BOSS') return 1
|
if (b === 'Last' || b === 'BOSS') return 1
|
||||||
const numA = parseInt(a)
|
// 分组之间按该组最小 weight 排序(升序)
|
||||||
const numB = parseInt(b)
|
const minWeightA = Math.min(...groups[a].map(item => item.weight || 0))
|
||||||
if (!isNaN(numA) && !isNaN(numB)) {
|
const minWeightB = Math.min(...groups[b].map(item => item.weight || 0))
|
||||||
return numB - numA
|
return minWeightA - minWeightB
|
||||||
}
|
|
||||||
return a.localeCompare(b)
|
|
||||||
}).map(key => {
|
}).map(key => {
|
||||||
const rewards = groups[key]
|
const rewards = groups[key]
|
||||||
|
// 分组内按 weight 升序排列
|
||||||
|
rewards.sort((a, b) => (a.weight - b.weight))
|
||||||
const total = rewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
const total = rewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
||||||
return {
|
return {
|
||||||
level: key,
|
level: key,
|
||||||
@ -528,8 +528,8 @@ function normalizeRewards(list) {
|
|||||||
...it,
|
...it,
|
||||||
percent: total > 0 ? Math.round((it.weight / total) * 1000) / 10 : 0
|
percent: total > 0 ? Math.round((it.weight / total) * 1000) / 10 : 0
|
||||||
}))
|
}))
|
||||||
// 移除前端按百分比强制排序逻辑,保留后端原始排序
|
// 按 weight 升序排列(从小到大)
|
||||||
// enriched.sort((a, b) => (b.percent - a.percent))
|
enriched.sort((a, b) => (a.weight - b.weight))
|
||||||
return enriched
|
return enriched
|
||||||
}
|
}
|
||||||
async function fetchRewardsForIssues(activityId) {
|
async function fetchRewardsForIssues(activityId) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
:price="detail.price_draw"
|
:price="detail.price_draw"
|
||||||
price-unit="/发"
|
price-unit="/发"
|
||||||
:cover-url="coverUrl"
|
:cover-url="coverUrl"
|
||||||
:tags="['公开透明', '随机掉落']"
|
:tags="['公开透明', '可验证']"
|
||||||
@show-rules="showRules"
|
@show-rules="showRules"
|
||||||
@go-cabinet="goCabinet"
|
@go-cabinet="goCabinet"
|
||||||
/>
|
/>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<!-- 底部多档位抽赏按钮 -->
|
<!-- 底部多档位抽赏按钮 -->
|
||||||
<view class="bottom-actions" v-if="!isDevMode">
|
<view class="bottom-actions">
|
||||||
<view class="tier-btn" @tap="openPayment(1)">
|
<view class="tier-btn" @tap="openPayment(1)">
|
||||||
<text class="tier-price">¥{{ (pricePerDraw * 1).toFixed(2) }}</text>
|
<text class="tier-price">¥{{ (pricePerDraw * 1).toFixed(2) }}</text>
|
||||||
<text class="tier-label">抽1发</text>
|
<text class="tier-label">抽1发</text>
|
||||||
@ -48,30 +48,6 @@
|
|||||||
<text class="tier-label">抽10发</text>
|
<text class="tier-label">抽10发</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 开发者模式 -->
|
|
||||||
<view class="bottom-actions dev-actions" v-else>
|
|
||||||
<view class="dev-input-wrapper">
|
|
||||||
<text class="dev-label">自定义发数:</text>
|
|
||||||
<input
|
|
||||||
class="dev-input"
|
|
||||||
type="number"
|
|
||||||
v-model="customDrawCount"
|
|
||||||
placeholder="输入数量"
|
|
||||||
/>
|
|
||||||
</view>
|
|
||||||
<view class="tier-btn tier-hot" @tap="openPayment(customDrawCount)">
|
|
||||||
<text class="tier-price">Go</text>
|
|
||||||
<text class="tier-label">抽{{ customDrawCount }}发</text>
|
|
||||||
</view>
|
|
||||||
<view class="tier-btn" @tap="isDevMode = false">
|
|
||||||
<text class="tier-price">Exit</text>
|
|
||||||
<text class="tier-label">退出</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
|
||||||
|
|
||||||
<!-- Dev Toggle Button -->
|
|
||||||
<view class="dev-fab" @tap="isDevMode = !isDevMode">Dev</view>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #modals>
|
<template #modals>
|
||||||
@ -176,9 +152,6 @@ const showDrawLoading = ref(false)
|
|||||||
const drawProgress = ref(0)
|
const drawProgress = ref(0)
|
||||||
const drawTotal = ref(1)
|
const drawTotal = ref(1)
|
||||||
|
|
||||||
const isDevMode = ref(false)
|
|
||||||
const customDrawCount = ref(1)
|
|
||||||
|
|
||||||
// 支付相关
|
// 支付相关
|
||||||
const paymentVisible = ref(false)
|
const paymentVisible = ref(false)
|
||||||
const paymentAmount = ref('0.00')
|
const paymentAmount = ref('0.00')
|
||||||
@ -580,61 +553,4 @@ watch(currentIssueId, (newId) => {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dev-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-input-wrapper {
|
|
||||||
flex: 2;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f5f5f5;
|
|
||||||
border-radius: 20rpx;
|
|
||||||
padding: 10rpx 20rpx;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-label {
|
|
||||||
font-size: 20rpx;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 4rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-input {
|
|
||||||
font-size: 32rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
color: #333;
|
|
||||||
height: 40rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-fab {
|
|
||||||
position: fixed;
|
|
||||||
right: 32rpx;
|
|
||||||
bottom: 280rpx;
|
|
||||||
width: 80rpx;
|
|
||||||
height: 80rpx;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
backdrop-filter: blur(10rpx);
|
|
||||||
color: #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 24rpx;
|
|
||||||
font-weight: bold;
|
|
||||||
z-index: 990;
|
|
||||||
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.15);
|
|
||||||
border: 1rpx solid rgba(255,255,255,0.2);
|
|
||||||
transition: all 0.3s;
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
transform: scale(0.9);
|
|
||||||
background: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -226,5 +226,11 @@
|
|||||||
"navigationBarBackgroundColor": "#F8F8F8",
|
"navigationBarBackgroundColor": "#F8F8F8",
|
||||||
"backgroundColor": "#F8F8F8"
|
"backgroundColor": "#F8F8F8"
|
||||||
},
|
},
|
||||||
|
"easycom": {
|
||||||
|
"autoscan": true,
|
||||||
|
"custom": {
|
||||||
|
"^BlessingAnimation": "@/components/BlessingAnimation.vue"
|
||||||
|
}
|
||||||
|
},
|
||||||
"uniIdRouter": {}
|
"uniIdRouter": {}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<view class="page">
|
<view class="page">
|
||||||
|
<!-- 开屏动画 -->
|
||||||
|
<SplashScreen />
|
||||||
|
|
||||||
<!-- 品牌级背景装饰系统 - 统一漂浮光球 -->
|
<!-- 品牌级背景装饰系统 - 统一漂浮光球 -->
|
||||||
<view class="bg-decoration"></view>
|
<view class="bg-decoration"></view>
|
||||||
|
|
||||||
@ -128,7 +131,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { authRequest, request } from '../../utils/request.js'
|
import { authRequest, request } from '../../utils/request.js'
|
||||||
|
import SplashScreen from '@/components/SplashScreen.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
SplashScreen
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
notices: [],
|
notices: [],
|
||||||
|
|||||||
@ -120,8 +120,8 @@ export function normalizeRewards(list, cleanUrl = (u) => u) {
|
|||||||
percent: parseFloat(rawPercent.toFixed(2)) // 统一保留2位小数
|
percent: parseFloat(rawPercent.toFixed(2)) // 统一保留2位小数
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// 移除前端按百分比强制排序逻辑,保留后端原始排序
|
// 按 weight 升序排列(从小到大)
|
||||||
// enriched.sort((a, b) => (b.percent - a.percent))
|
enriched.sort((a, b) => (a.weight - b.weight))
|
||||||
return enriched
|
return enriched
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,15 +163,14 @@ export function groupRewardsByLevel(rewards) {
|
|||||||
return Object.keys(groups).sort((a, b) => {
|
return Object.keys(groups).sort((a, b) => {
|
||||||
if (a === 'Last' || a === 'BOSS') return -1
|
if (a === 'Last' || a === 'BOSS') return -1
|
||||||
if (b === 'Last' || b === 'BOSS') return 1
|
if (b === 'Last' || b === 'BOSS') return 1
|
||||||
// 数字对照组(比如 "9对子")需要按数字降序排列
|
// 分组之间按该组最小 weight 排序(升序)
|
||||||
const numA = parseInt(a)
|
const minWeightA = Math.min(...groups[a].map(item => item.weight || 0))
|
||||||
const numB = parseInt(b)
|
const minWeightB = Math.min(...groups[b].map(item => item.weight || 0))
|
||||||
if (!isNaN(numA) && !isNaN(numB)) {
|
return minWeightA - minWeightB
|
||||||
return numB - numA
|
|
||||||
}
|
|
||||||
return a.localeCompare(b)
|
|
||||||
}).map(key => {
|
}).map(key => {
|
||||||
const levelRewards = groups[key]
|
const levelRewards = groups[key]
|
||||||
|
// 确保分组内的奖品按 weight 升序排列(从小到大)
|
||||||
|
levelRewards.sort((a, b) => (a.weight - b.weight))
|
||||||
const total = levelRewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
const total = levelRewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
||||||
return {
|
return {
|
||||||
level: key,
|
level: key,
|
||||||
|
|||||||
40
utils/blessing.js
Normal file
40
utils/blessing.js
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* 祝福动画工具
|
||||||
|
* 用于在支付弹窗等场景显示祝福动画
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 使用简单的全局变量来存储回调函数
|
||||||
|
let blessingCallback = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示祝福动画
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string} options.type - 祝福类型 'sheep' | 'horse' | 'random'
|
||||||
|
*/
|
||||||
|
export function showBlessing(options = {}) {
|
||||||
|
const type = options.type || 'random'
|
||||||
|
console.log('[showBlessing] 触发祝福动画, type:', type)
|
||||||
|
|
||||||
|
if (blessingCallback) {
|
||||||
|
blessingCallback({ type })
|
||||||
|
} else {
|
||||||
|
console.warn('[showBlessing] 没有注册的监听器')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册祝福动画监听
|
||||||
|
* @param {Function} callback - 回调函数
|
||||||
|
*/
|
||||||
|
export function registerBlessing(callback) {
|
||||||
|
blessingCallback = callback
|
||||||
|
console.log('[registerBlessing] 祝福动画监听器已注册')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除祝福动画监听
|
||||||
|
*/
|
||||||
|
export function unregisterBlessing() {
|
||||||
|
blessingCallback = null
|
||||||
|
console.log('[unregisterBlessing] 祝福动画监听器已移除')
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user