Files
rating/components/SuccessPopup/SuccessPopup.vue

183 lines
4.1 KiB
Vue
Raw Normal View History

2026-05-25 00:01:32 +08:00
<template>
<view class="success-popup" v-if="visible" @touchmove.stop.prevent>
<view class="mask" :class="{ 'mask-show': showAnim }" @tap="close"></view>
<view class="popup-content" :class="{ 'content-show': showAnim }">
<view class="icon-wrap">
<text class="emoji">{{ actionData?.emoji || '✨' }}</text>
<!-- 环形扩散特效 -->
<view class="ring ring-1"></view>
<view class="ring ring-2"></view>
<!-- 星星点缀 -->
<view class="sparkle s-1"></view>
<view class="sparkle s-2"></view>
<view class="sparkle s-3"></view>
</view>
<text class="title">评分成功</text>
<text class="subtitle">你已将TA评价为{{ actionData?.label || '...' }}</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const visible = ref(false);
const showAnim = ref(false);
const actionData = ref(null);
let timer = null;
const show = (action) => {
actionData.value = action;
visible.value = true;
// 留一点时间让DOM渲染然后添加动画类
setTimeout(() => {
showAnim.value = true;
}, 50);
if (timer) clearTimeout(timer);
// 2秒后自动关闭
timer = setTimeout(() => {
close();
}, 2000);
};
const close = () => {
showAnim.value = false;
setTimeout(() => {
visible.value = false;
}, 300); // 等待淡出动画结束
};
defineExpose({
show,
close
});
</script>
<style lang="scss" scoped>
.success-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
}
.mask {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.4);
backdrop-filter: blur(4px);
opacity: 0;
transition: opacity 0.3s ease;
}
.mask.mask-show {
opacity: 1;
}
.popup-content {
position: relative;
width: 460rpx;
background-color: #ffffff;
border-radius: 40rpx;
padding: 64rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
opacity: 0;
transform: scale(0.8) translateY(20rpx);
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
box-shadow: 0 16rpx 48rpx rgba(0, 0, 0, 0.15);
}
.popup-content.content-show {
opacity: 1;
transform: scale(1) translateY(0);
}
.icon-wrap {
position: relative;
width: 160rpx;
height: 160rpx;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 40rpx;
}
.emoji {
font-size: 100rpx;
position: relative;
z-index: 10;
animation: emojiBounce 1.2s infinite alternate ease-in-out;
}
@keyframes emojiBounce {
0% { transform: translateY(0) scale(1); }
100% { transform: translateY(-12rpx) scale(1.05); }
}
.ring {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
border: 4rpx solid #4d44f1;
opacity: 0;
}
.ring-1 {
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite;
}
.ring-2 {
animation: ripple 1.6s cubic-bezier(0.21, 0.53, 0.56, 0.8) infinite 0.8s;
}
@keyframes ripple {
0% { width: 100rpx; height: 100rpx; opacity: 0.8; }
100% { width: 260rpx; height: 260rpx; opacity: 0; }
}
.sparkle {
position: absolute;
font-size: 32rpx;
opacity: 0;
z-index: 11;
}
.s-1 { top: -10rpx; right: -20rpx; animation: popStar 1.4s ease-out infinite 0.1s; }
.s-2 { bottom: 10rpx; left: -30rpx; animation: popStar 1.4s ease-out infinite 0.6s; font-size: 24rpx; }
.s-3 { top: 60rpx; left: -40rpx; animation: popStar 1.4s ease-out infinite 1.1s; font-size: 28rpx; }
@keyframes popStar {
0% { transform: scale(0) rotate(0deg); opacity: 0; }
30% { transform: scale(1.2) rotate(45deg); opacity: 1; }
80% { transform: scale(0) rotate(90deg); opacity: 0; }
100% { transform: scale(0) rotate(90deg); opacity: 0; }
}
.title {
font-size: 40rpx;
font-weight: 800;
color: #111;
margin-bottom: 16rpx;
font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', sans-serif;
}
.subtitle {
font-size: 28rpx;
color: #666;
text-align: center;
}
</style>