1223 lines
40 KiB
HTML
1223 lines
40 KiB
HTML
<!--#
|
||
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>
|
||
<!--#
|
||
}
|
||
#-->
|