320 lines
7.0 KiB
Vue
320 lines
7.0 KiB
Vue
<template>
|
||
<view v-if="visible" class="draw-loading-overlay" @touchmove.stop.prevent>
|
||
<!-- 背景渐变 -->
|
||
<view class="bg-gradient"></view>
|
||
|
||
<!-- 光圈效果 -->
|
||
<view class="light-ring"></view>
|
||
<view class="light-ring ring-2"></view>
|
||
|
||
<!-- 主内容 -->
|
||
<view class="loading-content">
|
||
<!-- 3D礼盒动画 -->
|
||
<view class="gift-container">
|
||
<view class="gift-box">
|
||
<view class="gift-lid">
|
||
<view class="lid-top"></view>
|
||
<view class="lid-ribbon"></view>
|
||
</view>
|
||
<view class="gift-body">
|
||
<view class="body-ribbon"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 闪光粒子 -->
|
||
<view class="sparkle sparkle-1">✨</view>
|
||
<view class="sparkle sparkle-2">⭐</view>
|
||
<view class="sparkle sparkle-3">✨</view>
|
||
<view class="sparkle sparkle-4">💫</view>
|
||
</view>
|
||
|
||
<!-- 文字区域 -->
|
||
<view class="text-area">
|
||
<text class="loading-title">{{ title }}</text>
|
||
<view class="loading-dots">
|
||
<view class="dot"></view>
|
||
<view class="dot"></view>
|
||
<view class="dot"></view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 进度条(当有多次抽奖时显示) -->
|
||
<view v-if="total > 1" class="progress-area">
|
||
<view class="progress-bar">
|
||
<view class="progress-fill" :style="{ width: progressPercent + '%' }"></view>
|
||
</view>
|
||
<text class="progress-text">{{ progress }} / {{ total }}</text>
|
||
</view>
|
||
|
||
<!-- 提示文字 -->
|
||
<text class="tip-text">请稍候,好运即将到来...</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed } from 'vue'
|
||
|
||
const props = defineProps({
|
||
visible: { type: Boolean, default: false },
|
||
title: { type: String, default: '努力拆盒中' },
|
||
progress: { type: Number, default: 0 },
|
||
total: { type: Number, default: 1 }
|
||
})
|
||
|
||
const progressPercent = computed(() => {
|
||
if (props.total <= 0) return 0
|
||
return Math.min(100, Math.round((props.progress / props.total) * 100))
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.draw-loading-overlay {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
z-index: 2000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
animation: fadeIn 0.3s ease-out;
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
|
||
/* 背景 */
|
||
.bg-gradient {
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(ellipse at center,
|
||
rgba(255, 140, 0, 0.15) 0%,
|
||
rgba(30, 20, 50, 0.98) 50%,
|
||
rgba(10, 5, 20, 0.99) 100%
|
||
);
|
||
}
|
||
|
||
/* 光圈 */
|
||
.light-ring {
|
||
position: absolute;
|
||
width: 500rpx; height: 500rpx;
|
||
border: 4rpx solid rgba(255, 200, 100, 0.3);
|
||
border-radius: 50%;
|
||
animation: ringExpand 2s ease-out infinite;
|
||
}
|
||
|
||
.ring-2 {
|
||
animation-delay: 1s;
|
||
}
|
||
|
||
@keyframes ringExpand {
|
||
0% {
|
||
transform: scale(0.5);
|
||
opacity: 0.8;
|
||
border-width: 8rpx;
|
||
}
|
||
100% {
|
||
transform: scale(2);
|
||
opacity: 0;
|
||
border-width: 2rpx;
|
||
}
|
||
}
|
||
|
||
/* 主内容 */
|
||
.loading-content {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
z-index: 10;
|
||
animation: contentPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
@keyframes contentPop {
|
||
from { opacity: 0; transform: scale(0.8); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
/* 礼盒容器 */
|
||
.gift-container {
|
||
position: relative;
|
||
width: 240rpx;
|
||
height: 240rpx;
|
||
margin-bottom: 60rpx;
|
||
}
|
||
|
||
/* 礼盒动画 */
|
||
.gift-box {
|
||
position: absolute;
|
||
left: 50%; top: 50%;
|
||
transform: translate(-50%, -50%);
|
||
animation: boxBounce 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes boxBounce {
|
||
0%, 100% { transform: translate(-50%, -50%); }
|
||
50% { transform: translate(-50%, -60%); }
|
||
}
|
||
|
||
.gift-lid {
|
||
position: relative;
|
||
animation: lidShake 1.5s ease-in-out infinite;
|
||
transform-origin: center bottom;
|
||
}
|
||
|
||
@keyframes lidShake {
|
||
0%, 100% { transform: rotate(0deg) translateY(0); }
|
||
25% { transform: rotate(-5deg) translateY(-10rpx); }
|
||
75% { transform: rotate(5deg) translateY(-10rpx); }
|
||
}
|
||
|
||
.lid-top {
|
||
width: 140rpx; height: 30rpx;
|
||
background: linear-gradient(135deg, #FF6B35, #FF8C00);
|
||
border-radius: 8rpx 8rpx 0 0;
|
||
box-shadow: 0 -4rpx 16rpx rgba(255, 107, 53, 0.5);
|
||
}
|
||
|
||
.lid-ribbon {
|
||
position: absolute;
|
||
left: 50%; top: -20rpx;
|
||
transform: translateX(-50%);
|
||
width: 40rpx; height: 50rpx;
|
||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||
border-radius: 8rpx;
|
||
|
||
&::before, &::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 36rpx;
|
||
width: 30rpx; height: 30rpx;
|
||
background: linear-gradient(135deg, #FFD700, #FFA500);
|
||
border-radius: 50%;
|
||
}
|
||
&::before { left: -20rpx; }
|
||
&::after { right: -20rpx; }
|
||
}
|
||
|
||
.gift-body {
|
||
width: 120rpx; height: 100rpx;
|
||
background: linear-gradient(135deg, #FF8C00, #FF6B35);
|
||
border-radius: 0 0 12rpx 12rpx;
|
||
margin: 0 auto;
|
||
margin-top: -2rpx;
|
||
box-shadow:
|
||
0 12rpx 32rpx rgba(255, 107, 53, 0.4),
|
||
inset 0 -10rpx 20rpx rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.body-ribbon {
|
||
width: 30rpx; height: 100%;
|
||
background: linear-gradient(180deg, #FFD700, #FFA500);
|
||
margin: 0 auto;
|
||
}
|
||
|
||
/* 闪光粒子 */
|
||
.sparkle {
|
||
position: absolute;
|
||
font-size: 32rpx;
|
||
animation: sparkleFloat 2s ease-in-out infinite;
|
||
}
|
||
|
||
.sparkle-1 { top: 10rpx; left: 20rpx; animation-delay: 0s; }
|
||
.sparkle-2 { top: 30rpx; right: 20rpx; animation-delay: 0.5s; }
|
||
.sparkle-3 { bottom: 40rpx; left: 0; animation-delay: 1s; }
|
||
.sparkle-4 { bottom: 20rpx; right: 10rpx; animation-delay: 1.5s; }
|
||
|
||
@keyframes sparkleFloat {
|
||
0%, 100% {
|
||
opacity: 0.4;
|
||
transform: translateY(0) scale(0.8);
|
||
}
|
||
50% {
|
||
opacity: 1;
|
||
transform: translateY(-20rpx) scale(1.2);
|
||
}
|
||
}
|
||
|
||
/* 文字区域 */
|
||
.text-area {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16rpx;
|
||
margin-bottom: 40rpx;
|
||
}
|
||
|
||
.loading-title {
|
||
font-size: 40rpx;
|
||
font-weight: 800;
|
||
color: #FFF;
|
||
text-shadow: 0 0 30rpx rgba(255, 180, 100, 0.8);
|
||
letter-spacing: 4rpx;
|
||
}
|
||
|
||
.loading-dots {
|
||
display: flex;
|
||
gap: 8rpx;
|
||
}
|
||
|
||
.dot {
|
||
width: 12rpx; height: 12rpx;
|
||
background: #FFD700;
|
||
border-radius: 50%;
|
||
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 dotBounce {
|
||
0%, 80%, 100% {
|
||
transform: scale(0.6);
|
||
opacity: 0.5;
|
||
}
|
||
40% {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 进度条 */
|
||
.progress-area {
|
||
width: 400rpx;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.progress-bar {
|
||
height: 16rpx;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
box-shadow: inset 0 2rpx 4rpx rgba(0,0,0,0.2);
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: linear-gradient(90deg, #FFD700, #FF8C00, #FF6B35);
|
||
border-radius: 8rpx;
|
||
transition: width 0.3s ease-out;
|
||
box-shadow: 0 0 16rpx rgba(255, 200, 0, 0.6);
|
||
}
|
||
|
||
.progress-text {
|
||
display: block;
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
margin-top: 12rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* 提示文字 */
|
||
.tip-text {
|
||
font-size: 26rpx;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
letter-spacing: 2rpx;
|
||
}
|
||
</style>
|