143 lines
3.6 KiB
Vue
143 lines
3.6 KiB
Vue
<template>
|
|
<view v-if="show" class="celebrate-root" @touchmove.stop.prevent>
|
|
<view
|
|
v-for="p in particles"
|
|
:key="p.id"
|
|
class="celebrate-particle"
|
|
:style="p.style"
|
|
/>
|
|
<view v-if="title" class="celebrate-card" :class="{ pop: cardPop }">
|
|
<view class="celebrate-card-glow" />
|
|
<TongjiIcon name="sparkles" size="lg" color="#204E2B" />
|
|
<text class="celebrate-card-title">{{ title }}</text>
|
|
<text v-if="subtitle" class="celebrate-card-sub">{{ subtitle }}</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, watch } from 'vue'
|
|
import TongjiIcon from './TongjiIcon.vue'
|
|
|
|
const props = defineProps({
|
|
show: { type: Boolean, default: false },
|
|
title: { type: String, default: '' },
|
|
subtitle: { type: String, default: '' }
|
|
})
|
|
|
|
const particles = ref([])
|
|
const cardPop = ref(false)
|
|
|
|
const COLORS = ['#204E2B', '#386641', '#AFE2B3', '#FBBF24', '#727970', '#DC2626']
|
|
|
|
function buildParticles() {
|
|
const list = []
|
|
for (let i = 0; i < 18; i++) {
|
|
const left = 8 + Math.random() * 84
|
|
const delay = Math.random() * 0.35
|
|
const hue = COLORS[i % COLORS.length]
|
|
const size = 10 + Math.floor(Math.random() * 14)
|
|
list.push({
|
|
id: `${Date.now()}_${i}`,
|
|
style: {
|
|
left: `${left}%`,
|
|
width: `${size}rpx`,
|
|
height: `${size}rpx`,
|
|
background: hue,
|
|
animationDelay: `${delay}s`
|
|
}
|
|
})
|
|
}
|
|
return list
|
|
}
|
|
|
|
watch(
|
|
() => props.show,
|
|
(v) => {
|
|
if (v) {
|
|
particles.value = buildParticles()
|
|
cardPop.value = false
|
|
setTimeout(() => {
|
|
cardPop.value = true
|
|
}, 30)
|
|
} else {
|
|
particles.value = []
|
|
cardPop.value = false
|
|
}
|
|
}
|
|
)
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.celebrate-root {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 9999;
|
|
pointer-events: none;
|
|
overflow: hidden;
|
|
}
|
|
.celebrate-particle {
|
|
position: absolute;
|
|
top: -20rpx;
|
|
border-radius: 4rpx;
|
|
opacity: 0.9;
|
|
animation: celebrate-fall 1.6s ease-in forwards;
|
|
}
|
|
@keyframes celebrate-fall {
|
|
0% {
|
|
transform: translateY(0) rotate(0deg) scale(1);
|
|
opacity: 1;
|
|
}
|
|
100% {
|
|
transform: translateY(110vh) rotate(540deg) scale(0.4);
|
|
opacity: 0;
|
|
}
|
|
}
|
|
.celebrate-card {
|
|
position: absolute;
|
|
left: 50%;
|
|
top: 38%;
|
|
transform: translate(-50%, -50%) scale(0.82);
|
|
width: 78%;
|
|
max-width: 560rpx;
|
|
padding: 36rpx 32rpx 32rpx;
|
|
background: rgba(255, 255, 255, 0.96);
|
|
border-radius: 28rpx;
|
|
box-shadow: 0 20rpx 60rpx rgba(8, 145, 178, 0.22);
|
|
border: 2rpx solid rgba(8, 145, 178, 0.12);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
text-align: center;
|
|
opacity: 0;
|
|
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.25s ease;
|
|
&.pop {
|
|
opacity: 1;
|
|
transform: translate(-50%, -50%) scale(1);
|
|
}
|
|
}
|
|
.celebrate-card-glow {
|
|
position: absolute;
|
|
inset: -20rpx;
|
|
border-radius: 36rpx;
|
|
background: radial-gradient(circle, rgba(34, 211, 238, 0.2), transparent 70%);
|
|
pointer-events: none;
|
|
}
|
|
.celebrate-card-title {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin-top: 16rpx;
|
|
font-size: 36rpx;
|
|
font-weight: 800;
|
|
color: #1b1c1a;
|
|
}
|
|
.celebrate-card-sub {
|
|
position: relative;
|
|
z-index: 1;
|
|
margin-top: 10rpx;
|
|
font-size: 26rpx;
|
|
color: #475569;
|
|
line-height: 1.45;
|
|
}
|
|
</style>
|