feat:新增动画,修复一番赏的逻辑错误,无限赏和一番赏目前按照权重升序排列
This commit is contained in:
parent
28e0721e3f
commit
a634c6caac
@ -1,251 +0,0 @@
|
||||
<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>
|
||||
@ -1,233 +0,0 @@
|
||||
<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>
|
||||
@ -1,267 +0,0 @@
|
||||
<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>
|
||||
@ -2,7 +2,7 @@
|
||||
<view>
|
||||
<!-- 祝福动画 -->
|
||||
<view v-if="showBlessing" class="blessing-container">
|
||||
<view class="blessing-animation">
|
||||
<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">
|
||||
@ -117,8 +117,33 @@ const blessings = [
|
||||
},
|
||||
{
|
||||
emoji: '🐴',
|
||||
chars: ['马', '到', '功', '成'],
|
||||
chars: ['一', '马', '当', '先'],
|
||||
type: 'horse'
|
||||
},
|
||||
{
|
||||
emoji: '🍊',
|
||||
chars: ['心', '想', '事', '橙'],
|
||||
type: 'orange'
|
||||
},
|
||||
{
|
||||
emoji: '🐵',
|
||||
chars: ['财', '源', '广', '进'],
|
||||
type: 'monkey'
|
||||
},
|
||||
{
|
||||
emoji: '🐮',
|
||||
chars: ['牛', '气', '冲', '天'],
|
||||
type: 'ox'
|
||||
},
|
||||
{
|
||||
emoji: '🐶',
|
||||
chars: ['旺', '旺', '旺', '旺'],
|
||||
type: 'dog'
|
||||
},
|
||||
{
|
||||
emoji: '🐔',
|
||||
chars: ['吉', '祥', '如', '意'],
|
||||
type: 'chicken'
|
||||
}
|
||||
]
|
||||
const currentBlessing = ref(blessings[0])
|
||||
@ -242,17 +267,16 @@ function handleConfirm() {
|
||||
|
||||
.blessing-container {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
|
||||
.blessing-animation {
|
||||
@ -285,13 +309,38 @@ function handleConfirm() {
|
||||
}
|
||||
|
||||
// 小羊动画 - 弹跳出现
|
||||
.blessing-animation:has(.blessing-subtitle) .blessing-emoji {
|
||||
animation: emojiBounce 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
.blessing-animation.sheep .blessing-emoji {
|
||||
animation: emojiBounce 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
// 小马动画 - 从左边跑步进场
|
||||
.blessing-animation:not(:has(.blessing-subtitle)) .blessing-emoji {
|
||||
animation: emojiRun 0.8s ease-out;
|
||||
.blessing-animation.horse .blessing-emoji {
|
||||
animation: emojiRun 1.5s ease-out;
|
||||
}
|
||||
|
||||
// 橙子动画 - 缩放旋转出现(微信小程序优化版)
|
||||
.blessing-animation.orange .blessing-emoji {
|
||||
animation: emojiRotate 1.5s ease-out;
|
||||
}
|
||||
|
||||
// 猴子动画 - 跳跃摇摆出现
|
||||
.blessing-animation.monkey .blessing-emoji {
|
||||
animation: emojiSwing 1.5s ease-out;
|
||||
}
|
||||
|
||||
// 牛动画 - 冲撞弹跳出现
|
||||
.blessing-animation.ox .blessing-emoji {
|
||||
animation: emojiCharge 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
|
||||
// 狗动画 - 摇尾巴跳动出现
|
||||
.blessing-animation.dog .blessing-emoji {
|
||||
animation: emojiWag 1.5s ease-in-out;
|
||||
}
|
||||
|
||||
// 鸡动画 - 啄米点头出现
|
||||
.blessing-animation.chicken .blessing-emoji {
|
||||
animation: emojiPeck 1.5s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes emojiBounce {
|
||||
@ -325,6 +374,120 @@ function handleConfirm() {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emojiRotate {
|
||||
0% {
|
||||
transform: scale(0) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.2) rotate(180deg);
|
||||
opacity: 1;
|
||||
}
|
||||
60% {
|
||||
transform: scale(0.95) rotate(360deg);
|
||||
}
|
||||
80% {
|
||||
transform: scale(1.05) rotate(360deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) rotate(360deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emojiSwing {
|
||||
0% {
|
||||
transform: scale(0) translateY(-50rpx) rotate(-30deg);
|
||||
opacity: 0;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.15) translateY(10rpx) rotate(20deg);
|
||||
opacity: 1;
|
||||
}
|
||||
60% {
|
||||
transform: scale(0.9) translateY(-5rpx) rotate(-10deg);
|
||||
}
|
||||
80% {
|
||||
transform: scale(1.05) translateY(3rpx) rotate(5deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translateY(0) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emojiCharge {
|
||||
0% {
|
||||
transform: scale(0) translateX(-100rpx) rotate(45deg);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.3) translateX(20rpx) rotate(-20deg);
|
||||
opacity: 1;
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.85) translateX(-10rpx) rotate(10deg);
|
||||
}
|
||||
85% {
|
||||
transform: scale(1.08) translateX(5rpx) rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translateX(0) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emojiWag {
|
||||
0% {
|
||||
transform: scale(0) translateY(-30rpx) rotate(-15deg);
|
||||
opacity: 0;
|
||||
}
|
||||
30% {
|
||||
transform: scale(1.2) translateY(0) rotate(15deg);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.9) translateY(-15rpx) rotate(-15deg);
|
||||
}
|
||||
70% {
|
||||
transform: scale(1.1) translateY(0) rotate(15deg);
|
||||
}
|
||||
85% {
|
||||
transform: scale(0.95) translateY(-5rpx) rotate(-5deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translateY(0) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes emojiPeck {
|
||||
0% {
|
||||
transform: scale(0) translateY(-40rpx) rotate(0deg);
|
||||
opacity: 0;
|
||||
}
|
||||
25% {
|
||||
transform: scale(1.15) translateY(10rpx) rotate(10deg);
|
||||
opacity: 1;
|
||||
}
|
||||
40% {
|
||||
transform: scale(0.85) translateY(-5rpx) rotate(-10deg);
|
||||
}
|
||||
55% {
|
||||
transform: scale(1.1) translateY(8rpx) rotate(8deg);
|
||||
}
|
||||
70% {
|
||||
transform: scale(0.9) translateY(-3rpx) rotate(-8deg);
|
||||
}
|
||||
85% {
|
||||
transform: scale(1.05) translateY(2rpx) rotate(3deg);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1) translateY(0) rotate(0deg);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.blessing-subtitle {
|
||||
font-size: 28rpx;
|
||||
color: #FF9500;
|
||||
|
||||
@ -39,22 +39,14 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 支付弹窗 -->
|
||||
<PaymentPopup
|
||||
v-model:visible="paymentVisible"
|
||||
:amount="totalAmount"
|
||||
:coupons="coupons"
|
||||
:showCards="false"
|
||||
@confirm="onPaymentConfirm"
|
||||
/>
|
||||
|
||||
<!-- 支付弹窗已移至父组件,避免在 scroll-view 内导致定位问题 -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { getIssueChoices, getUserCoupons, joinLottery, createWechatOrder, getLotteryResult } from '@/api/appUser'
|
||||
import PaymentPopup from '@/components/PaymentPopup.vue'
|
||||
import { requestLotterySubscription } from '@/utils/subscribe'
|
||||
|
||||
const props = defineProps({
|
||||
@ -66,20 +58,35 @@ const props = defineProps({
|
||||
hideActionBar: { type: Boolean, default: false } // 支持隐藏内置操作栏
|
||||
})
|
||||
|
||||
const emit = defineEmits(['payment-success', 'selection-change'])
|
||||
const emit = defineEmits(['payment-success', 'selection-change', 'payment-visible-change', 'payment-amount-change', 'payment-coupons-change'])
|
||||
|
||||
const choices = ref([])
|
||||
const loading = ref(false)
|
||||
const selectedItems = ref([])
|
||||
const paymentVisible = ref(false)
|
||||
|
||||
// 监听支付弹窗状态变化,通知父组件
|
||||
watch(paymentVisible, (newVal) => {
|
||||
emit('payment-visible-change', newVal)
|
||||
})
|
||||
|
||||
// 模拟优惠券和道具卡数据,实际项目中可能需要从接口获取
|
||||
const coupons = ref([])
|
||||
const coupons = ref([])
|
||||
|
||||
const totalAmount = computed(() => {
|
||||
return (selectedItems.value.length * props.pricePerDraw).toFixed(2)
|
||||
})
|
||||
|
||||
// 监听支付金额变化,通知父组件
|
||||
watch(totalAmount, (newVal) => {
|
||||
emit('payment-amount-change', newVal)
|
||||
})
|
||||
|
||||
// 监听优惠券变化,通知父组件
|
||||
watch(coupons, (newVal) => {
|
||||
emit('payment-coupons-change', newVal)
|
||||
})
|
||||
|
||||
const disabled = computed(() => !!props.disabled)
|
||||
const disabledMessage = computed(() => props.disabledText || '暂不可下单')
|
||||
|
||||
@ -168,39 +175,43 @@ function handleSelect(item) {
|
||||
emit('selection-change', [...selectedItems.value])
|
||||
}
|
||||
|
||||
function handleBuy() {
|
||||
async function handleBuy() {
|
||||
if (disabled.value) {
|
||||
uni.showToast({ title: disabledMessage.value, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (selectedItems.value.length === 0) return
|
||||
|
||||
// 主动发送金额和优惠券数据
|
||||
emit('payment-amount-change', totalAmount.value)
|
||||
await fetchCoupons()
|
||||
paymentVisible.value = true
|
||||
fetchCoupons()
|
||||
}
|
||||
|
||||
function handleRandomOne() {
|
||||
async function handleRandomOne() {
|
||||
if (disabled.value) {
|
||||
uni.showToast({ title: disabledMessage.value, icon: 'none' })
|
||||
return
|
||||
}
|
||||
const available = choices.value.filter(item =>
|
||||
const available = choices.value.filter(item =>
|
||||
!item.is_sold && item.status !== 'sold' && !isSelected(item)
|
||||
)
|
||||
|
||||
|
||||
if (available.length === 0) {
|
||||
uni.showToast({ title: '没有可选位置了', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const randomIndex = Math.floor(Math.random() * available.length)
|
||||
const randomItem = available[randomIndex]
|
||||
|
||||
|
||||
// 选中该位置
|
||||
selectedItems.value.push(randomItem)
|
||||
|
||||
// 立即弹出支付
|
||||
|
||||
// 主动发送金额和优惠券数据
|
||||
emit('payment-amount-change', totalAmount.value)
|
||||
await fetchCoupons()
|
||||
paymentVisible.value = true
|
||||
fetchCoupons()
|
||||
}
|
||||
|
||||
|
||||
@ -222,9 +233,12 @@ async function fetchCoupons() {
|
||||
amount: Number(yuan).toFixed(2)
|
||||
}
|
||||
})
|
||||
// 主动发送优惠券数据给父组件
|
||||
emit('payment-coupons-change', coupons.value)
|
||||
} catch (e) {
|
||||
console.error('fetchCoupons error', e)
|
||||
coupons.value = []
|
||||
emit('payment-coupons-change', [])
|
||||
}
|
||||
}
|
||||
|
||||
@ -329,6 +343,10 @@ async function onPaymentConfirm(paymentData) {
|
||||
defineExpose({
|
||||
handleRandomOne,
|
||||
handleBuy,
|
||||
onPaymentConfirm,
|
||||
setPaymentVisible: (visible) => {
|
||||
paymentVisible.value = visible
|
||||
},
|
||||
selectedItems: () => selectedItems.value
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -111,10 +111,14 @@ defineProps({
|
||||
}
|
||||
|
||||
.bg-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 115%;
|
||||
height: 115%;
|
||||
max-width: 115%;
|
||||
max-height: 115%;
|
||||
position: absolute;
|
||||
top: -7.5%;
|
||||
left: -7.5%;
|
||||
filter: blur(40rpx) brightness(0.85) saturate(1.1);
|
||||
transform: scale(1.15);
|
||||
}
|
||||
|
||||
.bg-mask {
|
||||
|
||||
@ -43,10 +43,10 @@
|
||||
:tabs="[{key: 'pool', label: '本机奖池'}, {key: 'records', label: '购买记录'}]"
|
||||
>
|
||||
<!-- 奖池预览 -->
|
||||
<RewardsPreview
|
||||
<RewardsPreview
|
||||
v-if="tabActive === 'pool'"
|
||||
:rewards="currentIssueRewards"
|
||||
:grouped="true"
|
||||
:rewards="previewRewards"
|
||||
:grouped="detail?.play_type !== 'match'"
|
||||
@view-all="openRewardsPopup"
|
||||
/>
|
||||
|
||||
@ -196,6 +196,7 @@ import CabinetPreviewPopup from '@/components/activity/CabinetPreviewPopup.vue'
|
||||
import LotteryResultPopup from '@/components/activity/LotteryResultPopup.vue'
|
||||
import { getActivityDetail, getActivityIssues, getActivityIssueRewards, getUserCoupons, getItemCards, createWechatOrder, getMatchingCardTypes, createMatchingPreorder, checkMatchingGame, getIssueDrawLogs, getMatchingGameCards } from '../../../api/appUser'
|
||||
import { levelToAlpha } from '@/utils/activity'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const detail = ref({})
|
||||
const statusText = ref('')
|
||||
@ -314,27 +315,64 @@ const currentIssueRewards = computed(() => {
|
||||
return (iid && Array.isArray(m[iid])) ? m[iid] : []
|
||||
})
|
||||
|
||||
// 用于奖池预览的 rewards(已排序)
|
||||
const previewRewards = computed(() => {
|
||||
const isMatchType = detail.value?.play_type === 'match'
|
||||
|
||||
if (isMatchType) {
|
||||
// 对对碰模式:按 min_score 升序
|
||||
return [...currentIssueRewards.value].sort((a, b) => (a.min_score - b.min_score))
|
||||
} else {
|
||||
// 普通模式:返回原数组
|
||||
return currentIssueRewards.value
|
||||
}
|
||||
})
|
||||
|
||||
const rewardGroups = computed(() => {
|
||||
const isMatchType = detail.value?.play_type === 'match'
|
||||
|
||||
// 对对碰模式:不分组,直接按 min_score 平铺所有奖品
|
||||
if (isMatchType) {
|
||||
// 先按 min_score 升序排序
|
||||
const sortedRewards = [...currentIssueRewards.value].sort((a, b) => (a.min_score - b.min_score))
|
||||
|
||||
// 将每个奖品作为一个单独的分组
|
||||
return sortedRewards.map(item => ({
|
||||
level: `${item.min_score}对子`,
|
||||
rewards: [item],
|
||||
totalPercent: item.percent.toFixed(1)
|
||||
}))
|
||||
}
|
||||
|
||||
// 普通模式:按原来的分组逻辑
|
||||
const groups = {}
|
||||
currentIssueRewards.value.forEach(item => {
|
||||
let level = item.level || '赏'
|
||||
|
||||
// 普通模式:只显示 min_score > 0 的奖品
|
||||
if (item.min_score > 0 && level !== 'BOSS') {
|
||||
level = `${item.min_score}对子`
|
||||
}
|
||||
|
||||
if (!groups[level]) groups[level] = []
|
||||
groups[level].push(item)
|
||||
})
|
||||
|
||||
return Object.keys(groups).sort((a, b) => {
|
||||
// 普通模式:Last 和 BOSS 优先
|
||||
if (a === 'Last' || a === 'BOSS') return -1
|
||||
if (b === 'Last' || b === 'BOSS') return 1
|
||||
// 分组之间按该组最小 weight 排序(升序)
|
||||
|
||||
// 普通模式:分组之间按该组最小 weight 排序(升序)
|
||||
const minWeightA = Math.min(...groups[a].map(item => item.weight || 0))
|
||||
const minWeightB = Math.min(...groups[b].map(item => item.weight || 0))
|
||||
return minWeightA - minWeightB
|
||||
}).map(key => {
|
||||
const rewards = groups[key]
|
||||
// 分组内按 weight 升序排列
|
||||
|
||||
// 普通模式:分组内按 weight 升序排列
|
||||
rewards.sort((a, b) => (a.weight - b.weight))
|
||||
|
||||
const total = rewards.reduce((sum, item) => sum + (Number(item.percent) || 0), 0)
|
||||
return {
|
||||
level: key,
|
||||
@ -511,7 +549,7 @@ function normalizeIssues(list) {
|
||||
status_text: i.status_text ?? (i.status === 1 ? '进行中' : i.status === 0 ? '未开始' : i.status === 2 ? '已结束' : '')
|
||||
}))
|
||||
}
|
||||
function normalizeRewards(list) {
|
||||
function normalizeRewards(list, playType = 'normal') {
|
||||
const arr = unwrap(list)
|
||||
const items = arr.map((i, idx) => ({
|
||||
...i, // Spread original properties first
|
||||
@ -528,8 +566,16 @@ function normalizeRewards(list) {
|
||||
...it,
|
||||
percent: total > 0 ? Math.round((it.weight / total) * 1000) / 10 : 0
|
||||
}))
|
||||
// 按 weight 升序排列(从小到大)
|
||||
enriched.sort((a, b) => (a.weight - b.weight))
|
||||
|
||||
// 根据 play_type 决定排序方式
|
||||
if (playType === 'match') {
|
||||
// 对对碰:按 min_score 升序排列,不过滤 min_score=0 的奖品
|
||||
enriched.sort((a, b) => (a.min_score - b.min_score))
|
||||
} else {
|
||||
// 普通活动:按 weight 升序排列(从小到大)
|
||||
enriched.sort((a, b) => (a.weight - b.weight))
|
||||
}
|
||||
|
||||
return enriched
|
||||
}
|
||||
async function fetchRewardsForIssues(activityId) {
|
||||
@ -537,10 +583,13 @@ async function fetchRewardsForIssues(activityId) {
|
||||
const promises = list.map(it => getActivityIssueRewards(activityId, it.id))
|
||||
const results = await Promise.allSettled(promises)
|
||||
|
||||
// 获取 play_type
|
||||
const playType = detail.value?.play_type || 'normal'
|
||||
|
||||
results.forEach((res, i) => {
|
||||
const issueId = list[i] && list[i].id
|
||||
if (!issueId) return
|
||||
const value = res.status === 'fulfilled' ? normalizeRewards(res.value) : []
|
||||
const value = res.status === 'fulfilled' ? normalizeRewards(res.value, playType) : []
|
||||
rewardsMap.value = { ...(rewardsMap.value || {}), [issueId]: value }
|
||||
})
|
||||
}
|
||||
@ -847,7 +896,7 @@ function drawOne() {
|
||||
function manualDraw() {
|
||||
if (gameLoading.value) return
|
||||
if (!canManualDraw.value) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
drawOne()
|
||||
chance.value = Math.max(0, Number(chance.value || 0) - 1)
|
||||
pickedHandIndex.value = -1
|
||||
@ -879,7 +928,7 @@ async function autoDrawIfStuck() {
|
||||
async function onCellTap(cell) {
|
||||
if (gameLoading.value) return
|
||||
if (!cell || cell.empty) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
const hi = Number(cell.handIndex)
|
||||
if (!Number.isFinite(hi) || hi < 0) return
|
||||
|
||||
@ -1019,7 +1068,7 @@ function onResultClose() {
|
||||
|
||||
async function advanceOne() {
|
||||
if (gameLoading.value) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
const entry = gameEntry.value || null
|
||||
const gameId = entry && entry.game_id ? String(entry.game_id) : ''
|
||||
if (!gameId) return
|
||||
@ -1077,7 +1126,7 @@ async function autoRun() {
|
||||
}
|
||||
|
||||
async function onParticipate() {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
const aid = activityId.value || ''
|
||||
const iid = currentIssueId.value || ''
|
||||
if (!aid || !iid) { uni.showToast({ title: '期数未选择', icon: 'none' }); return }
|
||||
@ -1123,7 +1172,7 @@ async function applyResumeEntry(entry) {
|
||||
}
|
||||
|
||||
async function onResumeGame() {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
const aid = activityId.value || ''
|
||||
const latest = syncResumeGame(aid)
|
||||
if (!latest || !latest.entry || !latest.entry.game_id) return
|
||||
|
||||
@ -51,16 +51,19 @@
|
||||
|
||||
<!-- 选号组件 - 隐藏内置操作栏 -->
|
||||
<view class="selector-body" v-if="activityId && currentIssueId">
|
||||
<YifanSelector
|
||||
<YifanSelector
|
||||
ref="yifanSelectorRef"
|
||||
:activity-id="activityId"
|
||||
:issue-id="currentIssueId"
|
||||
:activity-id="activityId"
|
||||
:issue-id="currentIssueId"
|
||||
:price-per-draw="Number(detail.price_draw || 0) / 100"
|
||||
:disabled="!isOrderAllowed"
|
||||
:disabled-text="orderBlockedReason"
|
||||
:hide-action-bar="true"
|
||||
@payment-success="onPaymentSuccess"
|
||||
@selection-change="onSelectionChange"
|
||||
@payment-visible-change="onPaymentVisibleChange"
|
||||
@payment-amount-change="onPaymentAmountChange"
|
||||
@payment-coupons-change="onPaymentCouponsChange"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
@ -68,7 +71,7 @@
|
||||
|
||||
<template #footer>
|
||||
<!-- 固定底部操作栏 -->
|
||||
<view class="float-bar">
|
||||
<view class="float-bar" v-show="!isPaymentVisible">
|
||||
<view class="float-bar-inner">
|
||||
<view class="selection-info" v-if="selectedCount > 0">
|
||||
已选 <text class="highlight">{{ selectedCount }}</text> 个位置
|
||||
@ -112,6 +115,16 @@
|
||||
v-model:visible="cabinetVisible"
|
||||
:activity-id="activityId"
|
||||
/>
|
||||
|
||||
<!-- 支付弹窗(从 YifanSelector 提升到这里,确保祝福动画位置正确) -->
|
||||
<PaymentPopup
|
||||
v-model:visible="paymentVisible"
|
||||
:amount="paymentAmount"
|
||||
:coupons="paymentCoupons"
|
||||
:showCards="false"
|
||||
@confirm="onPaymentConfirm"
|
||||
@cancel="onPaymentCancel"
|
||||
/>
|
||||
</template>
|
||||
</ActivityPageLayout>
|
||||
</template>
|
||||
@ -130,6 +143,7 @@ import RulesPopup from '@/components/activity/RulesPopup.vue'
|
||||
import CabinetPreviewPopup from '@/components/activity/CabinetPreviewPopup.vue'
|
||||
import FlipGrid from '@/components/FlipGrid.vue'
|
||||
import YifanSelector from '@/components/YifanSelector.vue'
|
||||
import PaymentPopup from '@/components/PaymentPopup.vue'
|
||||
// Composables
|
||||
import { useActivity, useIssues, useRewards, useRecords } from '../../composables'
|
||||
// Utils
|
||||
@ -174,12 +188,53 @@ const showFlip = ref(false)
|
||||
const flipRef = ref(null)
|
||||
const yifanSelectorRef = ref(null)
|
||||
const selectedCount = ref(0) // 从外部追踪选中数量
|
||||
const isPaymentVisible = ref(false) // 支付弹窗是否显示
|
||||
const paymentVisible = ref(false) // 控制支付弹窗显示
|
||||
const paymentAmount = ref('0') // 支付金额
|
||||
const paymentCoupons = ref([]) // 可用优惠券
|
||||
|
||||
// 接收选中变化事件
|
||||
function onSelectionChange(items) {
|
||||
selectedCount.value = Array.isArray(items) ? items.length : 0
|
||||
}
|
||||
|
||||
// 接收支付弹窗显示状态变化(从 YifanSelector)
|
||||
function onPaymentVisibleChange(visible) {
|
||||
isPaymentVisible.value = visible
|
||||
paymentVisible.value = visible
|
||||
}
|
||||
|
||||
// 接收支付金额变化
|
||||
function onPaymentAmountChange(amount) {
|
||||
paymentAmount.value = amount
|
||||
}
|
||||
|
||||
// 接收优惠券变化
|
||||
function onPaymentCouponsChange(coupons) {
|
||||
paymentCoupons.value = coupons
|
||||
}
|
||||
|
||||
// 支付确认处理(委托给 YifanSelector)
|
||||
async function onPaymentConfirm(paymentData) {
|
||||
if (yifanSelectorRef.value && yifanSelectorRef.value.onPaymentConfirm) {
|
||||
await yifanSelectorRef.value.onPaymentConfirm(paymentData)
|
||||
}
|
||||
}
|
||||
|
||||
// 支付取消处理
|
||||
function onPaymentCancel() {
|
||||
// PaymentPopup 会通过 v-model 自动更新 paymentVisible
|
||||
// watch 会监听到变化并同步给 YifanSelector
|
||||
}
|
||||
|
||||
// 监听支付弹窗状态变化,同步给 YifanSelector
|
||||
watch(paymentVisible, (newVal) => {
|
||||
// 当支付弹窗关闭时,通知 YifanSelector 更新内部状态
|
||||
if (!newVal && yifanSelectorRef.value && yifanSelectorRef.value.setPaymentVisible) {
|
||||
yifanSelectorRef.value.setPaymentVisible(false)
|
||||
}
|
||||
})
|
||||
|
||||
// 触发随机选号
|
||||
function handleRandomDraw() {
|
||||
if (yifanSelectorRef.value && yifanSelectorRef.value.handleRandomOne) {
|
||||
|
||||
@ -124,6 +124,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getUserCoupons } from '../../api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
@ -214,7 +215,7 @@ function getCouponClass() {
|
||||
// 切换Tab
|
||||
function switchTab(tab) {
|
||||
if (currentTab.value === tab) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
currentTab.value = tab
|
||||
list.value = []
|
||||
page.value = 1
|
||||
@ -271,7 +272,7 @@ async function fetchData(append = false) {
|
||||
|
||||
// 去使用优惠券
|
||||
function onUseCoupon(item) {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
// 通常跳转到首页或抽盒页
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
|
||||
@ -125,6 +125,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getItemCards } from '../../api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const list = ref([])
|
||||
const loading = ref(false)
|
||||
@ -193,7 +194,7 @@ function getCardIcon(type) {
|
||||
// 切换Tab
|
||||
function switchTab(tab) {
|
||||
if (currentTab.value === tab) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
currentTab.value = tab
|
||||
list.value = []
|
||||
page.value = 1
|
||||
@ -251,7 +252,7 @@ async function fetchData(append = false) {
|
||||
|
||||
// 去使用道具卡
|
||||
function onUseCard(item) {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
// 道具卡通常去首页或指定的活动页
|
||||
uni.switchTab({
|
||||
url: '/pages/index/index'
|
||||
|
||||
@ -129,6 +129,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { getOrders, cancelOrder as cancelOrderApi, createWechatOrder } from '../../api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const currentTab = ref('pending')
|
||||
const orders = ref([])
|
||||
@ -289,7 +290,7 @@ function getStatusClass(item) {
|
||||
|
||||
function switchTab(tab) {
|
||||
if (currentTab.value === tab) return
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
currentTab.value = tab
|
||||
fetchOrders(false)
|
||||
}
|
||||
|
||||
@ -128,6 +128,7 @@
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { getTasks, getTaskProgress, claimTaskReward } from '../../api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const tasks = ref([])
|
||||
const loading = ref(false)
|
||||
@ -315,8 +316,8 @@ function getTierProgressText(task, tier) {
|
||||
async function claimReward(task, tier) {
|
||||
const key = `${task.id}_${tier.id}`
|
||||
if (claiming[key]) return
|
||||
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
|
||||
vibrateShort()
|
||||
claiming[key] = true
|
||||
try {
|
||||
const userId = getUserId()
|
||||
|
||||
@ -164,6 +164,7 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow, onReachBottom, onShareAppMessage, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getInventory, getProductDetail, redeemInventory, requestShipping, cancelShipping, listAddresses, getShipments, createAddressShare } from '@/api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const currentTab = ref(0)
|
||||
const aggregatedList = ref([])
|
||||
@ -514,7 +515,7 @@ async function loadInventory(uid) {
|
||||
}
|
||||
|
||||
function toggleSelect(item) {
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
item.selected = !item.selected
|
||||
if (item.selected) {
|
||||
// 选中时默认数量为最大值
|
||||
@ -529,7 +530,7 @@ function toggleSelect(item) {
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
uni.vibrateShort({ type: 'light' })
|
||||
vibrateShort()
|
||||
const newState = !isAllSelected.value
|
||||
aggregatedList.value.forEach(item => {
|
||||
item.selected = newState
|
||||
@ -554,7 +555,7 @@ function changeCount(item, delta) {
|
||||
}
|
||||
|
||||
async function onRedeem() {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
if (!user_id) return
|
||||
|
||||
@ -602,7 +603,7 @@ async function onRedeem() {
|
||||
}
|
||||
|
||||
async function onShip() {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
if (!user_id) return
|
||||
|
||||
@ -685,7 +686,7 @@ onShareAppMessage((res) => {
|
||||
})
|
||||
|
||||
async function onInvite(item) {
|
||||
uni.vibrateShort({ type: 'medium' })
|
||||
vibrateShort()
|
||||
const user_id = uni.getStorageSync('user_id')
|
||||
if (!user_id) {
|
||||
uni.navigateTo({ url: '/pages/login/index' })
|
||||
|
||||
@ -149,6 +149,7 @@ import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { request } from '../../utils/request'
|
||||
import { wechatLogin, bindPhone, getUserStats, getPointsBalance, sendSmsCode, smsLogin } from '../../api/appUser'
|
||||
import { vibrateShort } from '@/utils/vibrate.js'
|
||||
|
||||
const loading = ref(false)
|
||||
const agreementChecked = ref(false)
|
||||
@ -235,7 +236,7 @@ function toPurchaseAgreement() {
|
||||
async function handleSendCode() {
|
||||
if (!agreementChecked.value) {
|
||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||
uni.vibrateShort()
|
||||
vibrateShort()
|
||||
return
|
||||
}
|
||||
|
||||
@ -276,7 +277,7 @@ async function handleSendCode() {
|
||||
async function handleSmsLogin() {
|
||||
if (!agreementChecked.value) {
|
||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||
uni.vibrateShort()
|
||||
vibrateShort()
|
||||
return
|
||||
}
|
||||
|
||||
@ -315,7 +316,7 @@ async function handleSmsLogin() {
|
||||
function onGetPhoneNumber(e) {
|
||||
if (!agreementChecked.value) {
|
||||
uni.showToast({ title: '请先同意用户协议', icon: 'none' })
|
||||
uni.vibrateShort()
|
||||
vibrateShort()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 祝福动画工具
|
||||
* 用于在支付弹窗等场景显示祝福动画
|
||||
*/
|
||||
|
||||
// 使用简单的全局变量来存储回调函数
|
||||
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] 祝福动画监听器已移除')
|
||||
}
|
||||
52
utils/vibrate.js
Normal file
52
utils/vibrate.js
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 震动工具函数
|
||||
* 统一处理不同平台的震动API兼容性
|
||||
*/
|
||||
|
||||
/**
|
||||
* 短震动
|
||||
* 微信小程序不支持 type 参数,会忽略该参数
|
||||
* @param {Object} options - 配置项
|
||||
* @param {string} options.type - 震动类型 'light' | 'medium' | 'heavy'(仅在部分平台有效)
|
||||
*/
|
||||
export function vibrateShort(options = {}) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序不支持 type 参数,直接调用
|
||||
uni.vibrateShort({
|
||||
fail: (err) => {
|
||||
console.warn('[vibrateShort] 震动失败:', err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5 || APP-PLUS
|
||||
// H5和App可能支持 type 参数
|
||||
uni.vibrateShort({
|
||||
...options,
|
||||
fail: (err) => {
|
||||
console.warn('[vibrateShort] 震动失败:', err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY || MP-BAIDU || MP-TOUTIAO
|
||||
// 其他小程序平台,尝试传递参数
|
||||
uni.vibrateShort({
|
||||
...options,
|
||||
fail: (err) => {
|
||||
console.warn('[vibrateShort] 震动失败:', err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 长震动
|
||||
*/
|
||||
export function vibrateLong() {
|
||||
uni.vibrateLong({
|
||||
fail: (err) => {
|
||||
console.warn('[vibrateLong] 震动失败:', err)
|
||||
}
|
||||
})
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user