Files
zhgh_hmc/src/main/resources/views/mobile/staffmanage/birthday/index.html
T

1223 lines
40 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!--#
layout("/mobile/platform.html"){
#-->
<style scoped>
/* ====================基础样式 ==================== */
.app-container {
width: 100%;
height: 100vh;
overflow: hidden;
position: relative;
font-family: 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
background: #000;
}
/* 自定义背景层 */
.custom-bg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
transition: opacity 0.5s;
}
/* 深色遮罩 - 更透明,背景更清晰 */
.bg-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
pointer-events: none;
background: linear-gradient(to bottom, rgba(0,0,0,0.03) 0%, rgba(0,0,0,0.18) 100%);
}
/* 工会Logo */
.union-logo {
position: fixed;
top: 5px;
left: 5px;
width: 125px;
height: 27px;
background: rgba(255,255,255,0.01);
border: 1px solid rgba(255,255,255,0.25);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 12px;
font-weight: bold;
z-index: 10001;
backdrop-filter: blur(3px);
opacity: 1;
transition: opacity 0.5s;
}
/* 教师标识 */
.teacher-badge {
position: fixed;
bottom: 10px;
right: 10px;
background: rgba(255,215,0,0.15);
color: #ffd700;
padding: 8px 12px;
border-radius: 15px;
font-size: 0.8em;
border: 1px solid rgba(255,215,0,0.35);
z-index: 10001;
opacity: 1;
transition: opacity 0.5s;
backdrop-filter: blur(3px);
letter-spacing: 1px;
}
/* ==================== 主界面 ==================== */
.main-screen {
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* 上方70%:动画区域 */
.animation-area {
flex: 7;
position: relative;
display: flex;
align-items: center;
justify-content: center;
overflow: visible;
z-index: 2;
}
/* 字体动画容器 - 顶部 */
.font-animation {
position: absolute;
top: 3%;
width: 100%;
height: 60%;
opacity: 1;
transform: translateY(0);
z-index: 4;
}
/* Lottie容器 - 生日蛋糕(居中) */
.lottie-main {
position: absolute;
bottom: 10%;
width: 100%;
height: 280px;
opacity: 1;
transform: scale(1) translateY(0);
filter: drop-shadow(0 12px 25px rgba(0,0,0,0.28));
z-index: 3;
}
/* 礼花粒子 */
.confetti-area {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 1;
}
.confetti {
position: absolute;
width: 6px;
height: 6px;
border-radius: 50%;
opacity: 0;
}
/* 下方30%:功能区域 */
.function-area {
flex: 3;
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
padding: 15px;
gap: 12px;
}
/* 信封区域 - 简洁优雅 */
.envelope-section {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.envelope-wrapper {
width: 100%;
max-width: 420px;
opacity: 0; /* 初始隐藏 */
transform: translateY(25px); /* 初始位置 */
filter: drop-shadow(0 8px 20px rgba(0,0,0,0.35));
transition: none; /* 由gsap控制 */
}
/* 简洁优雅的信封 */
.envelope {
position: relative;
background: linear-gradient(135deg, #fffdf8 0%, #fff8ec 100%);
border: 2px solid #e8d5b9;
border-radius: 14px;
padding: 18px 16px;
box-shadow: 0 6px 18px rgba(0,0,0,0.22);
overflow: hidden;
transition: all 0.3s ease;
}
.envelope:hover {
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(0,0,0,0.3);
}
/* 信封顶部装饰条(细条) */
.envelope::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 10px;
background: linear-gradient(90deg, #c9a885 0%, #d4b896 50%, #c9a885 100%);
box-shadow: 0 1px 3px rgba(0,0,0,0.15);
}
/* 信封底部装饰条(细条) */
.envelope::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 6px;
background: linear-gradient(90deg, #fde8c7 0%, #f5cb6e 50%, #fde8c7 100%);
opacity: 0.7;
}
/* 信封内容 */
.envelope-content {
text-align: center;
position: relative;
z-index: 2;
background: rgba(255,255,255,0.35);
border-radius: 8px;
padding: 12px;
border: 1px dashed rgba(136,104,89,0.25);
backdrop-filter: blur(1.5px);
}
.envelope-title {
font-size: 1.25em;
font-weight: 700;
color: #886859;
margin-bottom: 8px;
letter-spacing: 1.5px;
text-shadow: 0 1px 2px rgba(255,255,255,0.8), 0 2px 3px rgba(0,0,0,0.08);
opacity: 0; /* 初始隐藏 */
transform: translateY(10px); /* 初始位置 */
}
.envelope-text {
font-size: 0.92em;
color: #4a3c31;
line-height: 1.6;
/*margin-bottom: 8px;*/
text-shadow: 0 1px 1px rgba(255,255,255,0.5);
opacity: 0; /* 初始隐藏 */
transform: translateY(10px); /* 初始位置 */
}
.envelope-text strong {
color: #886859;
font-weight: 700;
text-decoration: none;
}
/* 按钮区域 */
.button-section {
display: flex;
align-items: center;
justify-content: center;
padding-bottom: 8px;
}
.gift-button-main {
opacity: 0; /* 初始隐藏 */
transform: translateY(20px); /* 初始位置 */
transition: all 0.5s;
}
.gift-button-main button {
padding: 14px 14px;
background: linear-gradient(135deg, #ffd700, #ffed4e, #ffd700);
border: none;
border-radius: 28px;
color: #8b4513;
font-size: 1.1em;
font-weight: 700;
cursor: pointer;
transition: all 0.3s;
box-shadow: 0 6px 20px rgba(255,215,0,0.4);
letter-spacing: 1px;
text-transform: uppercase;
position: relative;
overflow: hidden;
width: 205px;
text-align: center;
font-family: 'PingFang SC', sans-serif;
animation: bounce 2.5s infinite;
}
.gift-button-main button::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
transition: left 0.6s;
}
.gift-button-main button:hover {
transform: translateY(-3px) scale(1.05);
box-shadow: 0 10px 28px rgba(255,215,0,0.6);
}
.gift-button-main button:hover::before {
left: 100%;
}
.gift-button-main button:active {
transform: translateY(0) scale(0.95);
}
/* ==================== 优惠券模态框(优化版) ==================== */
.coupon-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.9);
backdrop-filter: blur(15px);
display: flex;
justify-content: center;
align-items: center;
z-index: 9000;
opacity: 0;
visibility: hidden;
transition: all 0.5s;
padding: 20px;
box-sizing: border-box;
}
.coupon-modal.active {
opacity: 1;
visibility: visible;
}
.coupon-wrapper {
position: relative;
width: 100%;
max-width: 420px;
max-height: 85vh;
transform: scale(0.85) translateY(25px);
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
filter: drop-shadow(0 25px 60px rgba(0,0,0,0.7));
display: flex;
justify-content: center;
align-items: center;
padding: 0;
border-radius: 22px;
overflow: hidden;
}
.coupon-modal.active .coupon-wrapper {
transform: scale(1) translateY(0);
transition-delay: 0.1s;
}
/* 整个优惠券容器 - 带底部文字 */
.coupon-content-box {
width: 100%;
background: #fff;
border-radius: 22px;
overflow: hidden;
position: relative;
border: 3px solid #ffd700;
box-shadow: 0 15px 40px rgba(255,215,0,0.35);
max-height: 85vh;
display: flex;
flex-direction: column;
}
/* 图片区域 - 可滚动 */
.coupon-image-area {
flex: 1;
min-height: 0; /* 关键:允许flex子元素滚动 */
overflow-y: auto;
-webkit-overflow-scrolling: touch;
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 15px;
color: #fff;
text-align: center;
}
.coupon-image-area::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background:
radial-gradient(circle at 20% 20%, rgba(255,215,0,0.18) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(255,255,255,0.12) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
.coupon-image-area img {
max-width: 100%;
width: 100%;
height: auto;
display: block;
border-radius: 16px;
margin: 0 auto;
object-fit: contain;
background: #fff;
z-index: 1;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
/* 底部提示区域 - 固定在底部 */
.coupon-footer {
background: linear-gradient(135deg, #2c3e50 0%, #1a252f 100%);
color: #ffd700;
padding: 12px 18px;
text-align: center;
font-size: 0.85em;
line-height: 1.6;
border-top: 2px solid #ffd700;
font-weight: 600;
letter-spacing: 0.5px;
z-index: 2;
position: relative;
}
.coupon-footer strong {
color: #fff;
font-weight: 700;
text-shadow: 0 1px 2px rgba(0,0,0,0.3);
}
.coupon-close {
position: absolute;
top: 0;
right: 2px;
width: 40px;
height: 40px;
background: rgba(255,255,255,0.95);
border: 2px solid #ffd700;
border-radius: 50%;
font-size: 22px;
color: #c0392b;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
box-shadow: 0 4px 12px rgba(0,0,0,0.35);
z-index: 10;
font-family: Arial, sans-serif;
}
.coupon-close:hover {
background: #ffd700;
color: #fff;
transform: rotate(90deg) scale(1.1);
box-shadow: 0 6px 16px rgba(255,215,0,0.5);
}
/* ==================== Toast ==================== */
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0.8);
background: rgba(0,0,0,0.85);
color: #fff;
padding: 18px 32px;
border-radius: 28px;
font-size: 0.98em;
z-index: 10000;
opacity: 0;
visibility: hidden;
transition: all 0.35s;
backdrop-filter: blur(12px);
text-align: center;
max-width: 85%;
border: 1px solid rgba(255,215,0,0.4);
letter-spacing: 0.8px;
font-weight: 500;
}
.toast.show {
opacity: 1;
visibility: visible;
transform: translate(-50%, -50%) scale(1);
}
/* ==================== 加载提示 ==================== */
.loading-hint {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0,0,0,0.9);
color: #fff;
padding: 25px 35px;
border-radius: 18px;
font-size: 1.05em;
z-index: 10002;
opacity: 0;
visibility: hidden;
transition: all 0.35s;
backdrop-filter: blur(12px);
text-align: center;
border: 1px solid rgba(255,215,0,0.5);
letter-spacing: 0.8px;
font-weight: 500;
max-width: 85%;
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
}
.loading-hint.show {
opacity: 1;
visibility: visible;
}
/* ==================== 音乐按钮 ==================== */
.music-btn {
position: fixed;
bottom: 10px;
left: 10px;
width: 48px;
height: 48px;
background: rgba(255,255,255,0.12);
backdrop-filter: blur(8px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 10001;
transition: all 0.3s;
border: 1px solid rgba(255,215,0,0.25);
opacity: 1;
pointer-events: auto;
font-size: 20px;
color: #ffd700;
box-shadow: 0 4px 12px rgba(0,0,0,0.25);
}
.music-btn:hover {
background: rgba(255,215,0,0.18);
transform: scale(1.15);
border-color: rgba(255,215,0,0.5);
}
.music-btn.playing {
background: rgba(255,215,0,0.25);
border-color: #ffd700;
animation: pulse 2.5s infinite;
}
/* ==================== 动画 ==================== */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 0.4; }
50% { transform: scale(1.18); opacity: 0.6; }
}
/* ==================== 适配 ==================== */
@media (max-width: 375px) {
.font-animation { top: 3%; height: 50%; width: 100% }
.lottie-main { height: 220px; bottom: 8%; }
.envelope { padding: 16px 12px; border-radius: 12px; }
.envelope-title { font-size: 1.15em; }
.envelope-text { font-size: 0.88em; }
.envelope-content { padding: 2px; }
.gift-button-main button { width: 170px; padding: 12px 12px; font-size: 1em; }
.coupon-wrapper { padding: 10px; }
.coupon-footer { font-size: 0.8em; padding: 10px 12px; }
}
/* 隐藏滚动条 */
::-webkit-scrollbar { display: none; }
</style>
<div id="app">
<div id="birthday-app" class="app-container">
<!-- 自定义背景层(可替换) -->
<div class="custom-bg"
:style="{ backgroundImage: config.customBg ? 'url(' + config.customBg + ')' : 'none' }">
</div>
<div class="bg-overlay"></div>
<!-- 工会Logo -->
<div class="union-logo">杭州医学院校工会</div>
<!-- 教师标识 -->
<div class="teacher-badge">会员专享</div>
<!-- 主界面(直接展示) -->
<div class="main-screen">
<!-- 上方70%:动画区域 -->
<div class="animation-area">
<!-- 字体动画容器 - 顶部 -->
<div class="font-animation" ref="fontAnimation"></div>
<!-- Lottie容器 - 生日蛋糕(居中) -->
<div class="lottie-main" ref="lottieMain"></div>
<!-- 礼花粒子 -->
<div class="confetti-area" id="confettiArea"></div>
</div>
<!-- 下方30%:功能区域 -->
<div class="function-area">
<!-- 信封(初始隐藏,通过动画显示) -->
<div class="envelope-section">
<div class="envelope-wrapper" ref="envelopeWrapper">
<div class="envelope">
<div class="envelope-content">
<div class="envelope-title" ref="envelopeTitle">亲爱的 {{ userName }} 老师:</div>
<div class="envelope-text" ref="envelopeText">
步履所至,皆有回响,<br>
心之所向,岁月成诗。<br>
在这个特别的日子里,<br>
校工会祝您:<br>
生日快乐!幸福安康!
</div>
</div>
</div>
</div>
</div>
<!-- 按钮(初始隐藏,通过动画显示) -->
<div class="button-section">
<div class="gift-button-main" ref="giftButtonMain">
<button @click="handleReceiveGift">领取生日福利</button>
</div>
</div>
</div>
<!-- 优惠券模态框(优化版) -->
<div class="coupon-modal" :class="{ active: showCoupon }" @click="handleModalClick">
<div class="coupon-wrapper" @click.stop>
<button class="coupon-close" @click="closeCoupon">×</button>
<!-- 整个内容盒子:图片区域 + 底部文字 -->
<div class="coupon-content-box">
<!-- 图片区域(可滚动) -->
<div class="coupon-image-area" id="couponImageContainer">
<div v-if="config.couponImageSrc" style="width: 100%; max-width: 100%;">
<img :src="config.couponImageSrc" alt="生日福利券" />
</div>
<div v-else style="padding: 25px; text-align: center; color: #fff; width: 100%;">
<div style="font-size: 2.4em; margin-bottom: 18px;">🎂</div>
<div style="font-size: 1.4em; font-weight: bold; margin-bottom: 12px;">{{ userName }}</div>
<div style="font-size: 1.1em; opacity: 0.95; line-height: 1.7; margin-bottom: 12px;">
生日快乐!<br>感谢您的辛勤付出
</div>
<div style="font-size: 0.9em; opacity: 0.75;">{{ unionName }} 敬上</div>
<div style="font-size: 0.85em; opacity: 0.65; margin-top: 18px;">
请配置您的福利券图片
</div>
</div>
</div>
<!-- 底部提示区域 - 固定在最底部 -->
<div class="coupon-footer">
<strong>💡 使用提示:</strong>长按保存此贺卡图片,领取生日蛋糕券,享用生日长寿面
</div>
</div>
</div>
</div>
<!-- Toast 提示 -->
<div class="toast" :class="{ show: showToast }">{{ toastMessage }}</div>
<!-- 加载提示 -->
<div class="loading-hint" :class="{ show: showLoadingHint }">资源加载中,请稍候...</div>
<!-- 音乐按钮 -->
<div class="music-btn" :class="{ playing: musicPlaying }" @click="toggleMusic">
🎵
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="${base!}/assets/platform/plugins/swiper/swiper-bundle.min.css">
<script src="${base!}/assets/platform/plugins/swiper/swiper-bundle.min.js"></script>
<script src="${base!}/assets/platform/plugins/gsap/gsap.min.js"></script>
<script src="${base!}/assets/platform/plugins/lottie/lottie.min.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
// 用户信息
userName: '',
unionName: '杭州医学院校工会',
// 配置(可自定义)
config: {
customBg: '${base!}/assets/mobile/img/birthday/1111.png',
couponImageSrc: '${base!}/assets/mobile/img/birthday/hmc_birthday.png',
bgMusicSrc: '${base!}/assets/mobile/music/birthday/birthdaySong.mp3',
lottieData: {
font: '${base!}/assets/mobile/lottie/birthday/happyBirthday.json',
cake: '${base!}/assets/mobile/lottie/birthday/lihua.json'
}
},
// UI状态
showCoupon: false,
musicPlaying: false,
showToast: false,
toastMessage: '',
showLoadingHint: false,
// Lottie实例
lottieAnimationFont: null,
lottieAnimationCake: null,
// 音频实例
audio: null,
// 资源加载状态
resourcesLoaded: {
font: false,
cake: false,
image: false,
music: false
},
// 标记
isProcessing: false,
musicAutoPlayed: false,
couponOpened: false
};
},
mounted() {
this.init();
},
methods: {
init() {
this.fetchUserName();
this.preloadResources();
this.createConfetti();
this.animateEntrance(); // 页面加载时的入场动画
this.getBirthdayConfig()
},
async getBirthdayConfig() {
const resp = await $.post('/platform/staffManage/birthday/manage/getConfig')
if (resp.code === 0) {
if (resp.data.picUrl) {
this.config.customBg = '/file_server/fileStreamPreview?id=' + resp.data.picUrl
}
if (resp.data.birthdayUrl) {
this.config.couponImageSrc = '/file_server/fileStreamPreview?id=' + resp.data.birthdayUrl
}
}
},
fetchUserName() {
const urlParams = new URLSearchParams(window.location.search);
this.userName = urlParams.get('name') || "${@shiro.getPrincipalProperty('username')}" || '老师';
},
// ==================== 页面入场动画 ====================
animateEntrance() {
// 1. 礼花粒子持续循环(已在createConfetti中实现)
// 2. 延迟后显示信封容器
setTimeout(() => {
const wrapper = this.$refs.envelopeWrapper;
if (wrapper) {
gsap.to(wrapper, {
duration: 0.8,
opacity: 1,
y: 0,
ease: 'power2.out'
});
}
}, 300);
// 3. 延迟后显示信封标题
setTimeout(() => {
const title = this.$refs.envelopeTitle;
if (title) {
gsap.to(title, {
duration: 0.6,
opacity: 1,
y: 0,
ease: 'power2.out'
});
}
}, 600);
// 4. 延迟后显示信封内容
setTimeout(() => {
const text = this.$refs.envelopeText;
if (text) {
gsap.to(text, {
duration: 0.6,
opacity: 1,
y: 0,
ease: 'power2.out'
});
}
}, 800);
// 5. 延迟后显示按钮
setTimeout(() => {
const btn = this.$refs.giftButtonMain;
if (btn) {
gsap.to(btn, {
duration: 0.6,
opacity: 1,
y: 0,
ease: 'power2.out'
});
}
}, 1000);
},
preloadResources() {
// 预加载字体动画
if (this.config.lottieData.font) {
const fontAnim = lottie.loadAnimation({
container: document.createElement('div'),
renderer: 'svg',
loop: true,
autoplay: false,
path: this.config.lottieData.font
});
fontAnim.addEventListener('DOMLoaded', () => {
this.resourcesLoaded.font = true;
fontAnim.destroy();
this.loadFontAnimation();
});
fontAnim.addEventListener('data_failed', () => {
this.resourcesLoaded.font = true;
this.showFallbackFont();
});
} else {
this.resourcesLoaded.font = true;
this.showFallbackFont();
}
// 预加载蛋糕动画
if (this.config.lottieData.cake) {
const cakeAnim = lottie.loadAnimation({
container: document.createElement('div'),
renderer: 'svg',
loop: true,
autoplay: false,
path: this.config.lottieData.cake
});
cakeAnim.addEventListener('DOMLoaded', () => {
this.resourcesLoaded.cake = true;
cakeAnim.destroy();
this.loadCakeAnimation();
});
cakeAnim.addEventListener('data_failed', () => {
this.resourcesLoaded.cake = true;
this.showFallbackCake();
});
} else {
this.resourcesLoaded.cake = true;
this.showFallbackCake();
}
// 预加载优惠券图片
if (this.config.couponImageSrc) {
const img = new Image();
img.onload = () => {
this.resourcesLoaded.image = true;
};
img.onerror = () => {
this.resourcesLoaded.image = true;
};
img.src = this.config.couponImageSrc;
} else {
this.resourcesLoaded.image = true;
}
// 预加载音乐(仅加载,不播放)
if (this.config.bgMusicSrc) {
this.resourcesLoaded.music = true;
} else {
this.resourcesLoaded.music = true;
}
},
// ==================== 字体动画 ====================
loadFontAnimation() {
if (!this.config.lottieData.font) return;
this.$nextTick(() => {
const container = this.$refs.fontAnimation;
if (!container) return;
this.lottieAnimationFont = lottie.loadAnimation({
container: container,
renderer: 'svg',
loop: true,
autoplay: true,
path: this.config.lottieData.font,
rendererSettings: {
preserveAspectRatio: 'xMidYMid meet'
}
});
});
},
showFallbackFont() {
this.$nextTick(() => {
const container = this.$refs.fontAnimation;
if (container) {
container.innerHTML = '<div style="color:#ffd700;font-size:1.8em;font-weight:bold;text-align:center;text-shadow:0 2px 10px rgba(0,0,0,0.6);padding-top:20px;">🎉 生日快乐 🎉</div>';
}
});
},
// ==================== 蛋糕动画 ====================
loadCakeAnimation() {
if (!this.config.lottieData.cake) return;
this.$nextTick(() => {
const container = this.$refs.lottieMain;
if (!container) return;
this.lottieAnimationCake = lottie.loadAnimation({
container: container,
renderer: 'svg',
loop: true,
autoplay: true,
path: this.config.lottieData.cake,
rendererSettings: {
preserveAspectRatio: 'xMidYMid slice'
}
});
});
},
showFallbackCake() {
this.$nextTick(() => {
const container = this.$refs.lottieMain;
if (container) {
container.innerHTML = '<div style="color:#ffd700;font-size:1.2em;text-align:center;padding-top:80px;text-shadow:0 2px 10px rgba(0,0,0,0.6);">🎂<br>生日蛋糕</div>';
gsap.to(container, {
duration: 1,
scale: 1.1,
yoyo: true,
repeat: -1,
ease: 'power1.inOut'
});
}
});
},
// ==================== 礼花粒子系统 ====================
createConfetti() {
this.$nextTick(() => {
const container = document.getElementById('confettiArea');
if (!container) return;
const colors = ['#ffd700', '#fff', '#ff6b6b', '#ffd700', '#fff', '#ff6b6b'];
for (let i = 0; i < 40; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + '%';
confetti.style.top = Math.random() * 30 + '%';
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
confetti.style.borderRadius = Math.random() > 0.5 ? '50%' : '10%';
confetti.style.width = Math.random() * 5 + 3 + 'px';
confetti.style.height = confetti.style.width;
container.appendChild(confetti);
gsap.to(confetti, {
duration: 2 + Math.random() * 2,
y: 300 + Math.random() * 200,
x: (Math.random() - 0.5) * 100,
rotation: Math.random() * 360,
opacity: [0, 1, 0],
ease: 'power1.in',
repeat: -1,
delay: Math.random() * 0.5
});
}
});
},
// ==================== 领取福利(核心逻辑) ====================
handleReceiveGift() {
if (this.isProcessing) return;
this.isProcessing = true;
// 检查资源加载状态
const allLoaded = this.resourcesLoaded.font && this.resourcesLoaded.cake && this.resourcesLoaded.image;
if (!allLoaded) {
// 资源未加载完成,显示加载提示
this.showLoadingHint = true;
// 延迟2秒后再次检查
setTimeout(() => {
this.showLoadingHint = false;
this.openCoupon();
this.isProcessing = false;
}, 2000);
} else {
// 资源已加载,延迟500ms再打开(给用户点击反馈)
setTimeout(() => {
this.openCoupon();
this.isProcessing = false;
}, 500);
}
},
openCoupon() {
if (this.couponOpened) return;
this.couponOpened = true;
// 播放音乐(在点击时)
this.playMusic();
// 播放打开音效
this.playOpenSound();
// 显示优惠券
this.showCoupon = true;
// 按钮动画
gsap.to('.gift-button-main button', {
duration: 0.2,
scale: 0.95,
yoyo: true,
repeat: 1
});
this.$nextTick(() => {
if (this.config.couponImageSrc) {
const container = document.getElementById('couponImageContainer');
if (!container) return;
container.innerHTML = '';
const img = new Image();
img.onload = () => {
const wrapper = container.parentElement;
const footer = wrapper.querySelector('.coupon-footer');
const footerHeight = footer ? footer.offsetHeight : 0;
const maxHeight = 85 * window.innerHeight / 100 - 40; // 85vh - padding
// 设置图片容器最大高度,为底部文字留空间
container.style.maxHeight = (maxHeight - footerHeight - 10) + 'px';
img.style.maxWidth = '100%';
img.style.width = '100%';
img.style.height = 'auto';
img.style.display = 'block';
img.style.objectFit = 'contain';
img.style.margin = '0 auto';
img.style.borderRadius = '16px';
img.style.background = '#fff';
img.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
container.appendChild(img);
};
img.onerror = () => {
container.innerHTML = '<div style="padding:25px;color:#fff;text-align:center;font-size:1.1em;">福利券图片加载失败<br>请检查路径</div>';
};
img.src = this.config.couponImageSrc;
}
// 如果没有图片,使用默认内容,已经在HTML中定义
});
},
closeCoupon() {
this.showCoupon = false;
this.couponOpened = false;
this.playCloseSound();
},
handleModalClick(e) {
if (e.target.classList.contains('coupon-modal')) {
this.closeCoupon();
}
},
// ==================== 音乐控制 ====================
playMusic() {
if (!this.config.bgMusicSrc) {
this.showToastMsg('请在代码中配置音乐路径', 2000);
return;
}
if (this.musicAutoPlayed) return;
if (!this.audio) {
this.audio = new Audio(this.config.bgMusicSrc);
this.audio.loop = true;
this.audio.volume = 0.3;
}
this.audio.play().then(() => {
this.musicPlaying = true;
this.musicAutoPlayed = true;
const btn = document.querySelector('.music-btn');
if (btn) {
btn.classList.add('playing');
}
this.showToastMsg('背景音乐已开启', 1500);
}).catch((err) => {
console.log('播放失败:', err);
this.showToastMsg('音乐播放失败,请手动开启', 2000);
});
},
toggleMusic() {
if (!this.config.bgMusicSrc) {
this.showToastMsg('请在代码中配置音乐路径', 2000);
return;
}
if (!this.audio) {
this.audio = new Audio(this.config.bgMusicSrc);
this.audio.loop = true;
this.audio.volume = 0.3;
}
if (this.musicPlaying) {
this.audio.pause();
this.musicPlaying = false;
this.showToastMsg('音乐已暂停', 1500);
} else {
this.audio.play().then(() => {
this.musicPlaying = true;
this.musicAutoPlayed = true;
this.showToastMsg('音乐播放中', 1500);
}).catch(() => {
this.showToastMsg('请手动允许音频播放', 1500);
});
}
},
playOpenSound() {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.setValueAtTime(320, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(640, audioContext.currentTime + 0.15);
gainNode.gain.setValueAtTime(0.12, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.15);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.15);
} catch (e) {}
},
playCloseSound() {
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.setValueAtTime(480, audioContext.currentTime);
oscillator.frequency.exponentialRampToValueAtTime(240, audioContext.currentTime + 0.12);
gainNode.gain.setValueAtTime(0.1, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.12);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.12);
} catch (e) {}
},
showToastMsg(message, duration = 2000) {
this.toastMessage = message;
this.showToast = true;
setTimeout(() => {
this.showToast = false;
}, duration);
}
},
beforeDestroy() {
if (this.lottieAnimationFont) {
this.lottieAnimationFont.destroy();
}
if (this.lottieAnimationCake) {
this.lottieAnimationCake.destroy();
}
if (this.audio) {
this.audio.pause();
this.audio = null;
}
}
})
</script>
<!--#
}
#-->