接龙调整
This commit is contained in:
parent
534831f592
commit
33a4cac829
26
src/api/base/srApi.ts
Normal file
26
src/api/base/srApi.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// 生日贺卡相关API接口
|
||||
import { get, post } from "@/utils/request";
|
||||
|
||||
/**
|
||||
* 获取贺卡详情
|
||||
* @param params { id: 推送记录ID }
|
||||
*/
|
||||
export const srGetCardDetailApi = async (params: { id: string }) => {
|
||||
return await get("/api/srTsRecord/getCardDetail", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 标记贺卡为已查阅
|
||||
* @param params { xxtsId: 消息推送ID }
|
||||
*/
|
||||
export const srMarkAsViewedApi = async (params: { xxtsId: string }) => {
|
||||
return await post("/api/srTsRecord/markAsViewed", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取推送记录列表
|
||||
*/
|
||||
export const srFindPageApi = async (params: any) => {
|
||||
return await get("/api/srTsRecord/findPage", params);
|
||||
};
|
||||
|
||||
@ -381,6 +381,24 @@
|
||||
"navigationBarTitleText": "收款码",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/base/birthday/envelope",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "生日祝福",
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#ff9a9e"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/base/birthday/card",
|
||||
"style": {
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "生日祝福",
|
||||
"enablePullDownRefresh": false,
|
||||
"backgroundColor": "#ff9a9e"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
695
src/pages/base/birthday/card.vue
Normal file
695
src/pages/base/birthday/card.vue
Normal file
@ -0,0 +1,695 @@
|
||||
<!-- src/pages/base/birthday/card.vue -->
|
||||
<!-- 生日贺卡展示页面(家长端) - 信纸样式 -->
|
||||
<template>
|
||||
<view class="birthday-card-page" :class="bgColorClass">
|
||||
<!-- 加载遮罩层 -->
|
||||
<view v-if="isLoading" class="loading-overlay">
|
||||
<view class="loading-content">
|
||||
<view class="loading-spinner"></view>
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 贺卡内容 -->
|
||||
<view v-else-if="cardData" class="card-container">
|
||||
<!-- 信纸主体 -->
|
||||
<view class="letter-paper">
|
||||
<!-- 信纸顶部装饰 -->
|
||||
<view class="paper-header">
|
||||
<view class="header-line"></view>
|
||||
<view class="header-title">
|
||||
<text class="title-icon">🎂</text>
|
||||
<text class="title-text">生日祝福</text>
|
||||
<text class="title-icon">🎂</text>
|
||||
</view>
|
||||
<view class="header-date">{{ formatBirthdayDate }}</view>
|
||||
</view>
|
||||
|
||||
<!-- 称呼 -->
|
||||
<view class="letter-greeting" v-if="greetingFromContent">
|
||||
<view class="greeting-line">
|
||||
<text class="greeting-text">{{ greetingFromContent }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 信件正文(带虚线) -->
|
||||
<view class="letter-content">
|
||||
<view
|
||||
v-for="(line, index) in contentLines"
|
||||
:key="index"
|
||||
class="content-line"
|
||||
>
|
||||
<text class="line-text">{{ line || '\u00A0' }}</text>
|
||||
<view class="line-border"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 签名区域 -->
|
||||
<view class="letter-signature" v-if="cardData.signerName || cardData.signLabel">
|
||||
<view class="signature-wrapper">
|
||||
<!-- 签名文字 -->
|
||||
<view class="signature-info">
|
||||
<text class="signer-name">
|
||||
<text v-if="cardData.signLabel" class="signer-label">{{ cardData.signLabel }}</text>
|
||||
<text v-if="cardData.signerName">{{ cardData.signerName }}</text>
|
||||
</text>
|
||||
</view>
|
||||
<!-- 日期 -->
|
||||
<view class="signature-date">
|
||||
<text>{{ currentDate }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 信纸底部装饰 -->
|
||||
<view class="paper-footer">
|
||||
<view class="footer-decoration">
|
||||
<text class="footer-icon">🎉</text>
|
||||
<text class="footer-icon">🎁</text>
|
||||
<text class="footer-icon">🎈</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右上角音乐图标 -->
|
||||
<view
|
||||
class="music-icon-wrapper"
|
||||
:class="{ 'playing': isMusicPlaying }"
|
||||
@click="toggleMusic"
|
||||
>
|
||||
<text class="music-icon-emoji">🎵</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else class="empty-state">
|
||||
<text class="empty-icon">🎂</text>
|
||||
<text class="empty-text">贺卡不存在或已过期</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onUnmounted } from "vue";
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import { srGetCardDetailApi, srMarkAsViewedApi } from "@/api/base/srApi";
|
||||
import { imagUrl } from "@/utils";
|
||||
|
||||
const xxtsId = ref<string>("");
|
||||
const cardData = ref<any>(null);
|
||||
const isLoading = ref(false);
|
||||
const isMusicPlaying = ref(false);
|
||||
const audioContext = ref<any>(null);
|
||||
|
||||
// 格式化生日日期
|
||||
const formatBirthdayDate = computed(() => {
|
||||
if (!cardData.value?.birthdayDate) return '';
|
||||
const date = cardData.value.birthdayDate;
|
||||
if (date.includes('月')) return date;
|
||||
try {
|
||||
const d = new Date(date);
|
||||
return `${d.getMonth() + 1}月${d.getDate()}日`;
|
||||
} catch {
|
||||
return date;
|
||||
}
|
||||
});
|
||||
|
||||
// 当前日期
|
||||
const currentDate = computed(() => {
|
||||
const now = new Date();
|
||||
return `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
|
||||
});
|
||||
|
||||
// 寿星类型文本
|
||||
const personTypeText = computed(() => {
|
||||
const type = cardData.value?.srPersonType;
|
||||
if (type === 'JS') return '老师';
|
||||
if (type === 'XS') return '同学';
|
||||
return '';
|
||||
});
|
||||
|
||||
// 背景颜色类名(根据后台配置)
|
||||
const bgColorClass = computed(() => {
|
||||
const colorType = cardData.value?.hkMb?.animationType || 'blue';
|
||||
return `bg-${colorType}`;
|
||||
});
|
||||
|
||||
// 去掉HTML标签,保留文本内容
|
||||
const stripHtmlTags = (html: string): string => {
|
||||
if (!html) return '';
|
||||
// 使用正则表达式去掉所有HTML标签
|
||||
return html.replace(/<[^>]+>/g, '').trim();
|
||||
};
|
||||
|
||||
// 从内容中提取第一行作为称呼
|
||||
const greetingFromContent = computed(() => {
|
||||
if (!cardData.value?.zfContent) return '';
|
||||
const content = cardData.value.zfContent;
|
||||
// 按换行符或</p>标签分割
|
||||
const lines = content.split(/<\/p>|<\/div>|\n/).filter((line: string) => line.trim());
|
||||
if (lines.length > 0) {
|
||||
// 去掉HTML标签,获取第一行文本
|
||||
let firstLine = lines[0];
|
||||
// 去掉开头的<p>、<div>等标签
|
||||
firstLine = firstLine.replace(/^<[^>]+>/, '');
|
||||
// 去掉所有HTML标签
|
||||
firstLine = stripHtmlTags(firstLine).trim();
|
||||
return firstLine || '';
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// 将祝福语内容按行分割(从第二行开始),去掉HTML标签,确保每行都有虚线
|
||||
const contentLines = computed(() => {
|
||||
if (!cardData.value?.zfContent) return [''];
|
||||
const content = cardData.value.zfContent;
|
||||
// 按换行符或</p>标签分割
|
||||
let lines = content.split(/<\/p>|<\/div>|\n/).filter((line: string) => line.trim());
|
||||
|
||||
// 去掉第一行(已用作称呼)
|
||||
if (lines.length > 0) {
|
||||
lines = lines.slice(1);
|
||||
}
|
||||
|
||||
// 去掉每行的HTML标签
|
||||
lines = lines.map((line: string) => {
|
||||
// 去掉开头的<p>、<div>等标签
|
||||
line = line.replace(/^<[^>]+>/, '');
|
||||
// 去掉所有HTML标签,保留文本
|
||||
return stripHtmlTags(line).trim();
|
||||
}).filter((line: string) => line); // 过滤空行
|
||||
|
||||
// 确保至少有5行(让信纸看起来更完整)
|
||||
const minLines = 5;
|
||||
while (lines.length < minLines) {
|
||||
lines.push('');
|
||||
}
|
||||
return lines;
|
||||
});
|
||||
|
||||
// 加载贺卡详情
|
||||
const loadCardDetail = async () => {
|
||||
if (!xxtsId.value) return;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const res = await srGetCardDetailApi({ id: xxtsId.value });
|
||||
if (res?.result) {
|
||||
cardData.value = res.result;
|
||||
|
||||
const returnedXxtsId = res.result.xxtsId || xxtsId.value;
|
||||
if (returnedXxtsId) {
|
||||
await markAsViewed(returnedXxtsId);
|
||||
}
|
||||
|
||||
// 初始化并播放背景音乐(优先使用后台配置,否则使用默认音乐)
|
||||
initAudio();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("加载贺卡详情失败:", e);
|
||||
uni.showToast({ title: "加载失败", icon: "none" });
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 标记已查阅
|
||||
const markAsViewed = async (id: string) => {
|
||||
if (!id) return;
|
||||
try {
|
||||
await srMarkAsViewedApi({ xxtsId: id });
|
||||
} catch (e) {
|
||||
console.error("标记已查阅失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 初始化音频
|
||||
const initAudio = () => {
|
||||
// 优先使用后台配置的音乐,如果没有则使用本地默认音乐
|
||||
const musicUrl = cardData.value?.hkMb?.musicUrl
|
||||
? imagUrl(cardData.value.hkMb.musicUrl)
|
||||
: '/static/base/music/srkl.mp3';
|
||||
|
||||
try {
|
||||
// #ifdef H5
|
||||
audioContext.value = new Audio(musicUrl);
|
||||
audioContext.value.loop = true;
|
||||
audioContext.value.play().catch((e: any) => {
|
||||
console.error("自动播放失败,可能需要用户交互:", e);
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
audioContext.value = uni.createInnerAudioContext();
|
||||
audioContext.value.src = musicUrl;
|
||||
audioContext.value.loop = true;
|
||||
audioContext.value.play();
|
||||
// #endif
|
||||
|
||||
// 自动播放
|
||||
isMusicPlaying.value = true;
|
||||
} catch (e) {
|
||||
console.error("初始化音频失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 切换音乐播放
|
||||
const toggleMusic = () => {
|
||||
if (!audioContext.value) return;
|
||||
|
||||
try {
|
||||
if (isMusicPlaying.value) {
|
||||
audioContext.value.pause();
|
||||
} else {
|
||||
audioContext.value.play();
|
||||
}
|
||||
isMusicPlaying.value = !isMusicPlaying.value;
|
||||
} catch (e) {
|
||||
console.error("音乐播放控制失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面卸载时清理
|
||||
onUnmounted(() => {
|
||||
if (audioContext.value) {
|
||||
try {
|
||||
audioContext.value.pause();
|
||||
// #ifdef MP-WEIXIN
|
||||
audioContext.value.destroy?.();
|
||||
// #endif
|
||||
} catch (e) {
|
||||
console.error("清理音频失败:", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onLoad(async (options) => {
|
||||
xxtsId.value = options?.id || '';
|
||||
|
||||
if (xxtsId.value) {
|
||||
await loadCardDetail();
|
||||
uni.setNavigationBarTitle({ title: "生日祝福" });
|
||||
} else {
|
||||
uni.showToast({ title: "缺少贺卡ID", icon: "none" });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.birthday-card-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
// 默认浅蓝色背景
|
||||
&.bg-blue {
|
||||
background: linear-gradient(180deg, #e6f3ff 0%, #cce5ff 50%, #b3d9ff 100%);
|
||||
|
||||
.letter-paper {
|
||||
background: linear-gradient(180deg, #f5faff 0%, #eef6ff 100%);
|
||||
border-color: rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
.header-line {
|
||||
background: linear-gradient(90deg, transparent 0%, #3b82f6 50%, transparent 100%);
|
||||
}
|
||||
|
||||
.title-text {
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
.paper-footer {
|
||||
border-top-color: #93c5fd;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
background-color: rgba(230, 243, 255, 0.95);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border-color: #93c5fd;
|
||||
border-top-color: #3b82f6;
|
||||
}
|
||||
}
|
||||
|
||||
// 浅黄色背景
|
||||
&.bg-yellow {
|
||||
background: linear-gradient(180deg, #fdf6e3 0%, #f5e6c8 50%, #efe0b9 100%);
|
||||
|
||||
.letter-paper {
|
||||
background: linear-gradient(180deg, #fffef5 0%, #fff9e6 100%);
|
||||
border-color: rgba(201, 162, 39, 0.2);
|
||||
}
|
||||
|
||||
.header-line {
|
||||
background: linear-gradient(90deg, transparent 0%, #c9a227 50%, transparent 100%);
|
||||
}
|
||||
|
||||
.title-text {
|
||||
color: #8b4513;
|
||||
}
|
||||
|
||||
.paper-footer {
|
||||
border-top-color: #e8d5a3;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
background-color: rgba(253, 246, 227, 0.95);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border-color: #e8d5a3;
|
||||
border-top-color: #c9a227;
|
||||
}
|
||||
}
|
||||
|
||||
// 浅紫色背景
|
||||
&.bg-purple {
|
||||
background: linear-gradient(180deg, #f3e8ff 0%, #e9d5ff 50%, #ddd6fe 100%);
|
||||
|
||||
.letter-paper {
|
||||
background: linear-gradient(180deg, #faf5ff 0%, #f5f0ff 100%);
|
||||
border-color: rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
.header-line {
|
||||
background: linear-gradient(90deg, transparent 0%, #8b5cf6 50%, transparent 100%);
|
||||
}
|
||||
|
||||
.title-text {
|
||||
color: #6b21a8;
|
||||
}
|
||||
|
||||
.paper-footer {
|
||||
border-top-color: #c4b5fd;
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
background-color: rgba(243, 232, 255, 0.95);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
border-color: #c4b5fd;
|
||||
border-top-color: #8b5cf6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 加载遮罩 */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* 贺卡容器 */
|
||||
.card-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
padding-top: calc(20px + env(safe-area-inset-top));
|
||||
padding-bottom: calc(20px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
/* 信纸主体 */
|
||||
.letter-paper {
|
||||
flex: 1;
|
||||
border-radius: 8px;
|
||||
padding: 30px 25px;
|
||||
box-shadow:
|
||||
0 4px 20px rgba(0, 0, 0, 0.1),
|
||||
0 1px 3px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||||
border: 1px solid;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 信纸左边红色竖线(模拟信纸) */
|
||||
.letter-paper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
top: 80px;
|
||||
bottom: 100px;
|
||||
width: 2px;
|
||||
background: linear-gradient(180deg, #e74c3c 0%, #c0392b 100%);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 信纸顶部装饰 */
|
||||
.paper-header {
|
||||
text-align: center;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-line {
|
||||
height: 2px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.title-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
font-size: 26px;
|
||||
font-weight: bold;
|
||||
font-family: "KaiTi", "STKaiti", "楷体", serif;
|
||||
}
|
||||
|
||||
.header-date {
|
||||
font-size: 14px;
|
||||
color: #a08060;
|
||||
}
|
||||
|
||||
/* 称呼 */
|
||||
.letter-greeting {
|
||||
margin-bottom: 20px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.greeting-line {
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dashed #d4c4a8;
|
||||
}
|
||||
|
||||
.greeting-text {
|
||||
font-size: 18px;
|
||||
color: #5a4a3a;
|
||||
font-family: "KaiTi", "STKaiti", "楷体", serif;
|
||||
}
|
||||
|
||||
/* 信件正文 */
|
||||
.letter-content {
|
||||
flex: 1;
|
||||
padding-left: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.content-line {
|
||||
position: relative;
|
||||
min-height: 40px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
padding-bottom: 8px;
|
||||
}
|
||||
|
||||
.line-text {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: #4a3a2a;
|
||||
font-family: "KaiTi", "STKaiti", "楷体", serif;
|
||||
text-indent: 2em;
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.line-border {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
border-bottom: 1px dashed #d4c4a8;
|
||||
}
|
||||
|
||||
/* 签名区域 */
|
||||
.letter-signature {
|
||||
margin-top: auto;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.signature-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.signature-image {
|
||||
width: 100px;
|
||||
height: 45px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.signature-info {
|
||||
text-align: right;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.signer-label {
|
||||
font-size: 14px;
|
||||
color: #8b7355;
|
||||
display: inline;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.signer-name {
|
||||
font-size: 18px;
|
||||
color: #5a4a3a;
|
||||
font-weight: 500;
|
||||
font-family: "KaiTi", "STKaiti", "楷体", serif;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.signature-date {
|
||||
font-size: 14px;
|
||||
color: #a08060;
|
||||
border-bottom: 1px dashed #d4c4a8;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
/* 信纸底部装饰 */
|
||||
.paper-footer {
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid;
|
||||
}
|
||||
|
||||
.footer-decoration {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.footer-icon {
|
||||
font-size: 24px;
|
||||
animation: float-icon 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.footer-icon:nth-child(2) {
|
||||
animation-delay: 0.5s;
|
||||
}
|
||||
|
||||
.footer-icon:nth-child(3) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
@keyframes float-icon {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-5px); }
|
||||
}
|
||||
|
||||
/* 右上角音乐图标 */
|
||||
.music-icon-wrapper {
|
||||
position: fixed;
|
||||
top: calc(20px + env(safe-area-inset-top));
|
||||
right: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.music-icon-wrapper:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.music-icon-wrapper.playing .music-icon-emoji {
|
||||
animation: music-rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
.music-icon-emoji {
|
||||
font-size: 28px;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes music-rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80px;
|
||||
margin-bottom: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 16px;
|
||||
color: #8b7355;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
</style>
|
||||
653
src/pages/base/birthday/envelope.vue
Normal file
653
src/pages/base/birthday/envelope.vue
Normal file
@ -0,0 +1,653 @@
|
||||
<!-- src/pages/base/birthday/envelope.vue -->
|
||||
<!-- 生日贺卡信封页面(家长端) -->
|
||||
<template>
|
||||
<view class="envelope-page" :style="pageStyle">
|
||||
<!-- 装饰背景 -->
|
||||
<view class="decoration-bg">
|
||||
<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 class="sparkle sparkle-5">⭐</view>
|
||||
<view class="sparkle sparkle-6">✨</view>
|
||||
</view>
|
||||
|
||||
<!-- 提示文字 -->
|
||||
<view class="hint-text" :class="{ 'fade-out': isOpening }">
|
||||
<text>您收到一封生日祝福</text>
|
||||
<text class="hint-sub">点击信封查看</text>
|
||||
</view>
|
||||
|
||||
<!-- 信封容器 -->
|
||||
<view
|
||||
class="envelope-wrapper"
|
||||
:class="{ 'stop-swing': isOpening, 'opened': isOpened }"
|
||||
@click="openEnvelope"
|
||||
>
|
||||
<!-- 信封主体 -->
|
||||
<view class="envelope">
|
||||
<!-- 信封背面(内衬) -->
|
||||
<view class="envelope-back"></view>
|
||||
|
||||
<!-- 信纸 -->
|
||||
<view class="letter" :class="{ 'slide-out': isOpening }">
|
||||
<view class="letter-content">
|
||||
<text class="letter-emoji">🎂</text>
|
||||
<text class="letter-title">生日快乐</text>
|
||||
<text class="letter-hint">点击查看完整贺卡</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 信封盖子(三角形翻盖) -->
|
||||
<view class="envelope-flap" :class="{ 'flap-open': isOpening }"></view>
|
||||
|
||||
<!-- 信封正面 -->
|
||||
<view class="envelope-front">
|
||||
<!-- 封印 -->
|
||||
<view class="seal">
|
||||
<text class="seal-icon">🎂</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 手指提示 -->
|
||||
<view class="finger-hint" :class="{ 'fade-out': isOpening }">
|
||||
<text class="finger-icon">👆</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部装饰 -->
|
||||
<view class="bottom-decoration" :class="{ 'fade-out': isOpening }">
|
||||
<text class="deco-icon">🎈</text>
|
||||
<text class="deco-icon">🎁</text>
|
||||
<text class="deco-icon">🎈</text>
|
||||
</view>
|
||||
|
||||
<!-- 右上角音乐图标 -->
|
||||
<view
|
||||
class="music-icon-wrapper"
|
||||
:class="{ 'playing': isMusicPlaying, 'fade-out': isOpening }"
|
||||
@click="toggleMusic"
|
||||
>
|
||||
<text class="music-icon-emoji">🎵</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onUnmounted } from "vue";
|
||||
import { onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const xxtsId = ref<string>("");
|
||||
const openId = ref<string>("");
|
||||
const receiverName = ref<string>("");
|
||||
const isOpening = ref(false);
|
||||
const isOpened = ref(false);
|
||||
const audioContext = ref<any>(null);
|
||||
const isMusicPlaying = ref(false);
|
||||
|
||||
// 页面背景样式
|
||||
const pageStyle = computed(() => {
|
||||
return {
|
||||
background: 'linear-gradient(135deg, #ff9a9e 0%, #fecfef 50%, #fad0c4 100%)',
|
||||
};
|
||||
});
|
||||
|
||||
// 打开信封
|
||||
const openEnvelope = () => {
|
||||
if (isOpening.value || isOpened.value) return;
|
||||
|
||||
isOpening.value = true;
|
||||
|
||||
// 信封打开动画完成后跳转
|
||||
setTimeout(() => {
|
||||
isOpened.value = true;
|
||||
// 跳转到贺卡页面(已在信封页完成认证,无需再传openId)
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/base/birthday/card?id=${xxtsId.value}`
|
||||
});
|
||||
}, 300);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// 初始化音频(等待用户交互后自动播放)
|
||||
const initAudio = () => {
|
||||
try {
|
||||
// #ifdef H5
|
||||
audioContext.value = new Audio('/static/base/music/srkl.mp3');
|
||||
audioContext.value.loop = true;
|
||||
// 监听播放状态
|
||||
audioContext.value.addEventListener('play', () => {
|
||||
isMusicPlaying.value = true;
|
||||
});
|
||||
audioContext.value.addEventListener('pause', () => {
|
||||
isMusicPlaying.value = false;
|
||||
});
|
||||
audioContext.value.addEventListener('ended', () => {
|
||||
isMusicPlaying.value = false;
|
||||
});
|
||||
|
||||
// 标记是否已尝试播放
|
||||
let hasTriedPlay = false;
|
||||
|
||||
// 播放音乐的辅助函数
|
||||
const attemptPlay = () => {
|
||||
if (!audioContext.value || hasTriedPlay || isMusicPlaying.value) {
|
||||
return;
|
||||
}
|
||||
hasTriedPlay = true;
|
||||
audioContext.value.play().then(() => {
|
||||
isMusicPlaying.value = true;
|
||||
}).catch(() => {
|
||||
// 播放失败,重置标记以便下次尝试
|
||||
hasTriedPlay = false;
|
||||
});
|
||||
};
|
||||
|
||||
// 监听用户交互事件,在第一次交互时自动播放
|
||||
let hasInteracted = false;
|
||||
const playOnInteraction = () => {
|
||||
if (!hasInteracted && !isMusicPlaying.value) {
|
||||
hasInteracted = true;
|
||||
attemptPlay();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听多种用户交互事件(只绑定一次)
|
||||
const events = ['click', 'touchstart'];
|
||||
events.forEach(eventType => {
|
||||
document.addEventListener(eventType, playOnInteraction, { once: true, passive: true });
|
||||
});
|
||||
|
||||
// 尝试自动播放(可能被阻止,但不记录错误)
|
||||
attemptPlay();
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
audioContext.value = uni.createInnerAudioContext();
|
||||
audioContext.value.src = '/static/base/music/srkl.mp3';
|
||||
audioContext.value.loop = true;
|
||||
// 监听播放状态
|
||||
audioContext.value.onPlay(() => {
|
||||
isMusicPlaying.value = true;
|
||||
});
|
||||
audioContext.value.onPause(() => {
|
||||
isMusicPlaying.value = false;
|
||||
});
|
||||
audioContext.value.onStop(() => {
|
||||
isMusicPlaying.value = false;
|
||||
});
|
||||
// 微信小程序可以自动播放
|
||||
audioContext.value.play();
|
||||
isMusicPlaying.value = true;
|
||||
// #endif
|
||||
} catch (e) {
|
||||
console.error("初始化音频失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 播放音乐
|
||||
const playMusic = () => {
|
||||
if (!audioContext.value) return;
|
||||
try {
|
||||
audioContext.value.play();
|
||||
isMusicPlaying.value = true;
|
||||
} catch (e) {
|
||||
console.error("播放音乐失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 切换音乐播放
|
||||
const toggleMusic = () => {
|
||||
if (!audioContext.value) return;
|
||||
|
||||
try {
|
||||
if (isMusicPlaying.value) {
|
||||
audioContext.value.pause();
|
||||
isMusicPlaying.value = false;
|
||||
} else {
|
||||
audioContext.value.play();
|
||||
isMusicPlaying.value = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("音乐播放控制失败:", e);
|
||||
}
|
||||
};
|
||||
|
||||
// 页面卸载时清理音频
|
||||
onUnmounted(() => {
|
||||
if (audioContext.value) {
|
||||
try {
|
||||
audioContext.value.pause();
|
||||
// #ifdef MP-WEIXIN
|
||||
audioContext.value.destroy?.();
|
||||
// #endif
|
||||
} catch (e) {
|
||||
console.error("清理音频失败:", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 重置信封状态(返回时恢复初始状态)
|
||||
const resetEnvelopeState = () => {
|
||||
isOpening.value = false;
|
||||
isOpened.value = false;
|
||||
};
|
||||
|
||||
onLoad(async (options) => {
|
||||
// URL格式: ?id=xxx&openId=yyy
|
||||
xxtsId.value = options?.id || '';
|
||||
openId.value = options?.openId || '';
|
||||
receiverName.value = options?.name || '';
|
||||
|
||||
// 如果有 openId,先进行认证
|
||||
if (openId.value) {
|
||||
try {
|
||||
await userStore.loginByOpenId(openId.value);
|
||||
} catch (e) {
|
||||
console.error("openId认证失败:", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!xxtsId.value) {
|
||||
uni.showToast({ title: "缺少贺卡ID", icon: "none" });
|
||||
}
|
||||
|
||||
// 初始化并播放背景音乐
|
||||
initAudio();
|
||||
});
|
||||
|
||||
// 页面显示时重置状态(从其他页面返回时)
|
||||
onShow(() => {
|
||||
resetEnvelopeState();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.envelope-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 装饰背景 */
|
||||
.decoration-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sparkle {
|
||||
position: absolute;
|
||||
font-size: 28px;
|
||||
animation: sparkle-float 4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.sparkle-1 { top: 8%; left: 8%; animation-delay: 0s; }
|
||||
.sparkle-2 { top: 12%; right: 12%; animation-delay: 0.5s; }
|
||||
.sparkle-3 { top: 28%; left: 5%; animation-delay: 1s; }
|
||||
.sparkle-4 { top: 22%; right: 8%; animation-delay: 1.5s; }
|
||||
.sparkle-5 { bottom: 22%; left: 12%; animation-delay: 2s; }
|
||||
.sparkle-6 { bottom: 18%; right: 15%; animation-delay: 2.5s; }
|
||||
|
||||
@keyframes sparkle-float {
|
||||
0%, 100% {
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-12px) scale(1.15);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 提示文字 */
|
||||
.hint-text {
|
||||
position: absolute;
|
||||
top: 12%;
|
||||
text-align: center;
|
||||
z-index: 10;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.hint-text text {
|
||||
display: block;
|
||||
text-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.hint-text text:first-child {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 12px;
|
||||
color: #973cfe;
|
||||
text-shadow: 0 2px 15px rgba(151, 60, 254, 0.5), 0 0 20px rgba(151, 60, 254, 0.3);
|
||||
}
|
||||
|
||||
.hint-sub {
|
||||
font-size: 14px;
|
||||
color: #973cfe;
|
||||
text-shadow: 0 2px 10px rgba(151, 60, 254, 0.4);
|
||||
animation: hint-pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes hint-pulse {
|
||||
0%, 100% { opacity: 0.6; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
.hint-text.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
/* 信封容器 - 摇摆动画 */
|
||||
.envelope-wrapper {
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
cursor: pointer;
|
||||
animation: envelope-swing 2.5s ease-in-out infinite;
|
||||
transform-origin: top center;
|
||||
}
|
||||
|
||||
@keyframes envelope-swing {
|
||||
0%, 100% { transform: rotate(-3deg); }
|
||||
50% { transform: rotate(3deg); }
|
||||
}
|
||||
|
||||
.envelope-wrapper.stop-swing {
|
||||
animation: none;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.envelope-wrapper:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
/* 信封主体 */
|
||||
.envelope {
|
||||
position: relative;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
/* 信封背面(内衬) - 信封打开时可见的内部 */
|
||||
.envelope-back {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 200px;
|
||||
background: linear-gradient(180deg, #d63031 0%, #c0392b 100%);
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 信纸 */
|
||||
.letter {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 25px;
|
||||
right: 25px;
|
||||
height: 140px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fff8f0 100%);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.15);
|
||||
z-index: 2;
|
||||
transform: translateY(0);
|
||||
transition: transform 1s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.letter.slide-out {
|
||||
transform: translateY(-160px);
|
||||
}
|
||||
|
||||
.letter-content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.letter-emoji {
|
||||
font-size: 36px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.letter-title {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: #e74c3c;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.letter-hint {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 信封盖子 - 三角形翻盖 */
|
||||
.envelope-flap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 150px solid transparent;
|
||||
border-right: 150px solid transparent;
|
||||
border-top: 100px solid #e74c3c;
|
||||
z-index: 4;
|
||||
transform-origin: top center;
|
||||
transform: rotateX(0deg);
|
||||
transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.15));
|
||||
}
|
||||
|
||||
.envelope-flap.flap-open {
|
||||
transform: rotateX(180deg);
|
||||
}
|
||||
|
||||
/* 信封正面 */
|
||||
.envelope-front {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 130px;
|
||||
background: linear-gradient(180deg, #e74c3c 0%, #c0392b 100%);
|
||||
border-radius: 0 0 8px 8px;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* 信封左右折角装饰 */
|
||||
.envelope-front::before,
|
||||
.envelope-front::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.envelope-front::before {
|
||||
left: 0;
|
||||
border-left: 150px solid #c0392b;
|
||||
border-top: 65px solid transparent;
|
||||
border-bottom: 65px solid transparent;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.envelope-front::after {
|
||||
right: 0;
|
||||
border-right: 150px solid #c0392b;
|
||||
border-top: 65px solid transparent;
|
||||
border-bottom: 65px solid transparent;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* 封印 */
|
||||
.seal {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #f39c12 0%, #e67e22 50%, #d35400 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow:
|
||||
0 4px 15px rgba(0, 0, 0, 0.3),
|
||||
inset 0 2px 4px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 4px rgba(0, 0, 0, 0.2);
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
animation: seal-glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes seal-glow {
|
||||
0%, 100% {
|
||||
box-shadow:
|
||||
0 4px 15px rgba(243, 156, 18, 0.4),
|
||||
inset 0 2px 4px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
50% {
|
||||
box-shadow:
|
||||
0 4px 30px rgba(243, 156, 18, 0.8),
|
||||
0 0 20px rgba(243, 156, 18, 0.5),
|
||||
inset 0 2px 4px rgba(255, 255, 255, 0.3),
|
||||
inset 0 -2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.seal-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* 手指提示 */
|
||||
.finger-hint {
|
||||
position: absolute;
|
||||
bottom: -60px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
animation: finger-bounce 1s ease-in-out infinite;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes finger-bounce {
|
||||
0%, 100% { transform: translateX(-50%) translateY(0); }
|
||||
50% { transform: translateX(-50%) translateY(-15px); }
|
||||
}
|
||||
|
||||
.finger-icon {
|
||||
font-size: 40px;
|
||||
filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.2));
|
||||
}
|
||||
|
||||
.finger-hint.fade-out {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* 打开后的状态 */
|
||||
.envelope-wrapper.opened {
|
||||
animation: envelope-fadeUp 0.5s ease forwards;
|
||||
}
|
||||
|
||||
@keyframes envelope-fadeUp {
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: translateY(-60px) scale(0.9);
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部装饰 */
|
||||
.bottom-decoration {
|
||||
position: absolute;
|
||||
bottom: 12%;
|
||||
display: flex;
|
||||
gap: 25px;
|
||||
z-index: 10;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
|
||||
.deco-icon {
|
||||
font-size: 36px;
|
||||
animation: deco-bounce 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.deco-icon:nth-child(2) {
|
||||
animation-delay: 0.3s;
|
||||
}
|
||||
|
||||
.deco-icon:nth-child(3) {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
@keyframes deco-bounce {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(-12px); }
|
||||
}
|
||||
|
||||
.bottom-decoration.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
|
||||
/* 右上角音乐图标 */
|
||||
.music-icon-wrapper {
|
||||
position: fixed;
|
||||
top: calc(20px + env(safe-area-inset-top));
|
||||
right: 20px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.music-icon-wrapper:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.music-icon-wrapper.playing .music-icon-emoji {
|
||||
animation: music-rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
.music-icon-wrapper.fade-out {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
.music-icon-emoji {
|
||||
font-size: 28px;
|
||||
display: block;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes music-rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
BIN
src/static/base/music/srkl.mp3
Normal file
BIN
src/static/base/music/srkl.mp3
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user