Files
zhaoma/src/components/HomePage.vue
2025-12-24 17:14:48 +08:00

880 lines
22 KiB
Vue
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.

<script setup>
import { ref, computed, watch, onMounted } from "vue"
import bgm from "../assets/audio/zhaoma_bgm.mp3"
import globalToastEvent, { ToastType } from '../globalToastEvent';
import { globalStore } from "../globalstore.js";
import Login from '../components/Login.vue'
import { isWeixin, isLogin, getParam, Storage, Request } from "../libs/utils"
import Lottery from '../components/Lottery.vue';
import PrizeList from "./PrizeList.vue"
import Rule from "./Rule.vue";
import GameSwiper from "./GameSwiper.vue";
import Todolist from "./TodoList.vue";
import SharePage from "./SharePage.vue";
import GamePage from "./GamePage.vue";
import GameDemo from "./GameDemo.vue";
import Address from "./Address.vue";
import PopupMore from "./PopupMore.vue";
const props = defineProps({
show: true
})
const isMusicOn = ref(false);
const audioElement = ref(null);
const videoElement = ref(null);
const videoLoaded = ref(false);
const videoError = ref(false);
const lotteryShow = ref(false)
const lotteryType = ref("draw")
const lotteryNoticeData = ref(null)
// 初始化全局音频实例
const initGlobalAudio = () => {
if (!globalStore.globalAudio) {
globalStore.globalAudio = new Audio(bgm);
globalStore.globalAudio.loop = true; // 设置循环播放
globalStore.globalAudio.preload = 'auto';
// 监听音频事件
globalStore.globalAudio.addEventListener('play', () => {
isMusicOn.value = true;
});
globalStore.globalAudio.addEventListener('pause', () => {
isMusicOn.value = false;
});
globalStore.globalAudio.addEventListener('ended', () => {
isMusicOn.value = false;
});
}
audioElement.value = globalStore.globalAudio;
// 同步当前音乐状态
isMusicOn.value = !globalStore.globalAudio.paused;
};
onMounted(() => {
initGlobalAudio();
initVideo();
checkAndPlayAudio();
})
// 检查并播放音频
const checkAndPlayAudio = () => {
if (!audioElement.value) return;
// 如果用户曾经手动静音,则不自动播放
if (globalStore.userMutedMusic) {
console.log('用户曾经手动静音,不自动播放音乐');
isMusicOn.value = false;
return;
}
// 检查音频是否已在播放
if (!audioElement.value.paused) {
console.log('音频已在播放,跳过重复播放');
isMusicOn.value = true;
return;
}
// 只在首次访问或音乐没有被用户手动静音时才尝试自动播放
if (globalStore.isFirstVisitHomePage || !globalStore.userMutedMusic) {
const playPromise = audioElement.value.play();
if (playPromise !== undefined) {
playPromise.then(() => {
// 自动播放成功
isMusicOn.value = true;
globalStore.isFirstVisitHomePage = false;
console.log('自动播放成功');
})
.catch(error => {
// 自动播放被阻止
console.log('自动播放被阻止,需要用户交互:', error);
isMusicOn.value = false;
audioElement.value.pause();
});
}
} else {
// 非首次访问且用户没有手动静音,但音乐当前暂停,则保持暂停状态
isMusicOn.value = false;
}
};
// 初始化视频
const initVideo = () => {
document.addEventListener('WeixinJSBridgeReady',()=>{
videoElement.value && videoElement.value.play()
})
setTimeout(() => {
if (videoElement.value) {
const video = videoElement.value;
// 移动设备视频优化设置
video.muted = true;
video.loop = true;
video.autoplay = true;
video.playsInline = true;
video.setAttribute('webkit-playsinline', 'true');
video.setAttribute('playsinline', 'true');
video.setAttribute('x5-video-player-type', 'h5');
video.setAttribute('x5-video-player-fullscreen', 'false');
// 移动设备特殊处理
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
if (isMobile) {
video.preload = 'metadata'; // 移动设备使用较小的预加载
// 尝试在用户交互后播放
const playVideo = () => {
video.play().then(() => {
console.log('移动设备视频播放成功');
videoLoaded.value = true;
}).catch(error => {
console.log('移动设备视频播放失败:', error);
videoError.value = true;
});
};
// 在用户点击时尝试播放视频
document.addEventListener('touchstart', playVideo, { once: true });
document.addEventListener('click', playVideo, { once: true });
}
// 按钮交互触发视频播放(用于 iOS 等严格的自动播放限制)
const buttons = document.querySelectorAll('.scene-item');
buttons.forEach(button => {
button.addEventListener('click', () => {
if (video.paused && !videoError.value) {
video.play().catch(error => {
console.log('点击后视频播放失败:', error);
});
}
}, { once: true });
});
// 监听视频事件
video.addEventListener('loadedmetadata', () => {
console.log('视频元数据加载完成');
if (!isMobile) {
video.play().catch(error => {
console.log('PC设备视频自动播放失败:', error);
videoError.value = true;
});
}
});
video.addEventListener('canplay', () => {
videoLoaded.value = true;
videoError.value = false;
});
}
}, 200);
};
// 播放/暂停切换
const toggleMusicState = () => {
if (!audioElement.value) {
initGlobalAudio();
}
if (isMusicOn.value) {
// 当前在播放,用户点击静音
audioElement.value.pause();
globalStore.userMutedMusic = true; // 记录用户手动静音
isMusicOn.value = false;
console.log('用户手动静音音乐');
} else {
// 当前暂停,用户点击播放
const playPromise = audioElement.value.play();
if (playPromise !== undefined) {
playPromise.then(() => {
globalStore.userMutedMusic = false; // 用户重新开启音乐,清除静音标记
isMusicOn.value = true;
console.log('用户手动开启音乐');
})
.catch(error => {
console.log('播放失败:', error);
isMusicOn.value = false;
});
}
}
};
// 视频事件处理
const onVideoLoadStart = () => {
console.log('视频开始加载');
};
const onVideoCanPlay = () => {
console.log('视频可以播放');
videoLoaded.value = true;
videoError.value = false;
};
const onVideoLoaded = () => {
console.log('视频数据加载完成');
videoLoaded.value = true;
};
const onVideoError = (event) => {
console.error('视频加载错误:', event);
videoError.value = true;
videoLoaded.value = false;
};
const navigateTodoList = () => {
globalToastEvent.emit(ToastType.SHOW_TODO)
}
const popupMore = async () => {
globalToastEvent.emit(ToastType.SHOW_POPUPMORE)
}
const handleRule = () => {
globalToastEvent.emit(ToastType.SHOW_RULE)
}
const handleLottery = () => {
// if (globalStore.draw_chances <= 0) {
// return weui.alert("还没有抽奖机会,快去参加活动吧")
// };
lotteryType.value = 'draw'
lotteryNoticeData.value = null
globalToastEvent.emit(ToastType.SHOW_LOTTERY);
}
const loginShow = ref(false)
// 登录状态
const userStatus = async (callback) => {
const pageCode = getParam("code")
const userinfos = Storage.get("userinfos")
if (isWeixin()) {
if (!pageCode) {
weui.alert("未获取到code")
return
}
const weixinResult = await Request("wechat/login", {
code: pageCode
})
Storage.set("userinfos", weixinResult.json)
if (weixinResult.json && weixinResult.json.phone) {
loginShow.value = false
callback && callback()
} else {
loginShow.value = true
}
} else {
loginShow.value = true
}
}
//初始化持久数据
const initUserGameInfos = async (refresh_official, refresh_cap_scan) => {
const result = await Request('game/info', { refresh_official: refresh_official, refresh_cap_scan: refresh_cap_scan }, "GET")
if (result?.res?.status === 200) {
globalStore.draw_chances = result.json.draw_chances
globalStore.game_chances = result.json.game_chances
globalStore.first_share_today = result.json.first_share_today
globalStore.followed_official = result.json.followed_official
globalStore.cap_scan = result.json.cap_scan
globalStore.game_chances_view_recipes = result.json.game_chances_view_recipes
globalStore.MAX_VIEW_RECIPES_DAILY = result.json.constants.MAX_VIEW_RECIPES_DAILY
globalStore.CONSUME_POINT_1_PER_DRAW = result.json.constants.CONSUME_POINT_1_PER_DRAW
globalStore.MAX_CAP_SCAN = result.json.constants.MAX_CAP_SCAN
globalStore.MAX_INVITE_DAILY = result.json.constants.MAX_INVITE_DAILY
globalToastEvent.emit(ToastType.MOUNTED)
}
}
const getNotice = async () => {
const result = await Request('notice/latest', {}, "GET")
if (result?.json?.notification) {
lotteryType.value = 'notice'
lotteryNoticeData.value = result.json.notification
lotteryShow.value = true
}
}
const handleLoginSuccess = async () => {
console.log("已登录")
loginShow.value = false
// await initUserGameInfos(true, true)
await getNotice()
}
if (isLogin()) {
handleLoginSuccess()
} else {
userStatus(handleLoginSuccess)
}
let mergeId = '';
const urlParams = new URLSearchParams(window.location.search);
mergeId = urlParams.get('merge_id');
watch(() => mergeId, async (newVal) => {
if (!newVal) {
return
}
if (newVal) {
isPhotoSquareVisible.value = true;
}
}, { immediate: true })
globalToastEvent.on(ToastType.SHOW_LOTTERY, async () => {
// await initUserGameInfos(false, false);
lotteryShow.value = true
})
globalToastEvent.on(ToastType.INFO_UPDATE, async () => {
initUserGameInfos(false, false)
})
watch(() => lotteryShow.value, async (newVal) => {
initUserGameInfos(true, true);
}, { immediate: true })
const gameSwiperShow = ref(false);
const navigateGamePage = () => {
gameSwiperShow.value = true;
}
globalToastEvent.on(ToastType.SHOW_SWIPER, () => {
gameSwiperShow.value = true;
})
const ruleShow = ref(false)
globalToastEvent.on(ToastType.SHOW_RULE, () => {
ruleShow.value = true
})
const prizeListShow = ref(false)
const handlePrizeList = async () => {
await getUserLottery()
prizeListShow.value = true
}
const prizelist = ref([])
const getUserLottery = async () => {
const result = await Request("lottery", { pool: "all" }, "GET")
if (result.res.status === 200) {
prizelist.value = result.json.lottery_logs.length > 0 ? result.json.lottery_logs : []
}
//TODO 上线换成上面的
// prizelist.value = [
// { id: 1, prize_code: "FIRST", prize_name: "一等奖", coupon_type: "scene", pushed: 0 },
// { id: 2, prize_code: "FIRST1", prize_name: "二等奖", coupon_type: "scene", pushed: 1 }
// ]
}
globalToastEvent.on(ToastType.SHOW_PRIZELIST, async () => {
await getUserLottery()
prizeListShow.value = true
})
const todolistShow = ref(false)
globalToastEvent.on(ToastType.SHOW_TODO, () => {
todolistShow.value = true
})
const sharePageShow = ref(false)
const isFromTodoList = ref(false)
globalToastEvent.on(ToastType.SHOW_SHAREPAGE, (env) => {
sharePageShow.value = true;
isFromTodoList.value = env;
})
const popupMoreShow = ref(false)
globalToastEvent.on(ToastType.SHOW_POPUPMORE, () => {
popupMoreShow.value = true
})
const gamePageShow = ref(false)
const gameSlideId = ref('')
globalToastEvent.on(ToastType.SHOW_GAMEPAGE, (slide) => {
gameSlideId.value = slide.id;
gamePageShow.value = true
})
const gameDemoShow = ref(false)
globalToastEvent.on(ToastType.SHOW_GAMEDEMO, () => {
gameDemoShow.value = true
})
globalToastEvent.on(ToastType.CLOSE_ALL, () => {
gameSwiperShow.value = false;
gamePageShow.value = false;
gameDemoShow.value = false;
lotteryShow.value = false;
})
const activePrizeId = ref(0)
const addressShow = ref(false)
const handleAddressSubmitAfter = (data) => {
const targetItem = prizelist.value.find(item => item.id === data.id)
targetItem.pushed = 1
addressShow.value = false
}
const handleAddress = (id) => {
activePrizeId.value = id
addressShow.value = true
}
</script>
<template>
<div :show="show" class="main">
<div class="home-wrapper">
<div class="fallback-background"></div>
<div class="scene-item item-1" @click="handleLottery" :class="{ 'disabled': globalStore.draw_chances <= 0 }">
<img src="../assets/images/new/lottery.png" alt="抽奖">
</div>
<div class="scene-item item-2" @click="navigateGamePage">
<img src="../assets/images/new/start-btn.png" alt="开始探索">
</div>
<div class="scene-item item-3" @click="navigateTodoList">
<img src="../assets/images/new/task.png" alt="任务">
</div>
<div @click="toggleMusicState">
<div v-if="isMusicOn" key="on" class="scene-item item-4">
<img src="../assets/images/new/music.png" alt="音乐开">
</div>
<div v-else key="off" class="scene-item item-5">
<img src="../assets/images/new/music-off.png" alt="音乐关">
</div>
</div>
<div class="scene-item item-6" @click="handleRule">
<img src="../assets/images/new/rule.png" alt="规则">
</div>
<div class="scene-item item-7" @click="handlePrizeList">
<img src="../assets/images/new/award.png" alt="奖励">
</div>
<div class="scene-item item-ma1">
<img src="../assets/images/new/ma1.png" alt="马1">
</div>
<div class="scene-item item-ma2">
<img src="../assets/images/new/ma2.png" alt="马2">
</div>
<div class="scene-item item-ma3">
<img src="../assets/images/new/ma3.png" alt="马3">
</div>
<div class="scene-item item-ma4">
<img src="../assets/images/new/ma4.png" alt="马4">
</div>
<div class="scene-item item-jiu1">
<img src="../assets/images/new/jiuping1.png" alt="酒瓶1">
</div>
<div class="scene-item item-jiu2">
<img src="../assets/images/new/jiuping2.png" alt="酒瓶2">
</div>
<div class="scene-item item-xique1">
<img src="../assets/images/new/xique1.png" alt="喜鹊1">
</div>
<div class="scene-item item-xique2">
<img src="../assets/images/new/xique2.png" alt="喜鹊2">
</div>
<div class="scene-item item-sidai">
<img src="../assets/images/new/sidai.png" alt="丝带">
</div>
<div class="scene-item item-lihe">
<img src="../assets/images/new/lihe.png" alt="礼盒">
</div>
<div class="scene-item item-hulu">
<img src="../assets/images/new/hulu.png" alt="葫芦">
</div>
<div class="scene-item item-taozi">
<img src="../assets/images/new/taozi.png" alt="桃子">
</div>
<div class="scene-item item-8" @click="popupMore">
<img src="../assets/images/new/learn-more.png" alt="更多金喜">
</div>
<Rule :show="ruleShow" @close="ruleShow = false"></Rule>
<PrizeList :show="prizeListShow" @close="prizeListShow = false" :prizelist="prizelist" @address="handleAddress">
</PrizeList>
<Address :show="addressShow" :prizeId="activePrizeId" @address-submit="handleAddressSubmitAfter"
@address-close="addressShow = false"></Address>
</div>
</div>
<Login :show="loginShow" @login-success="handleLoginSuccess" />
<GameSwiper v-model:show="gameSwiperShow" @close="gameSwiperShow = false" />
<Todolist :show="todolistShow" @close="todolistShow = false"></Todolist>
<GamePage :show="gamePageShow" @close="gamePageShow = false" :gameSlideId="gameSlideId" />
<GameDemo :show="gameDemoShow" @close="gameDemoShow = false" />
<Lottery :show="lotteryShow" @close="lotteryShow = false" :type="lotteryType" :data="lotteryNoticeData"></Lottery>
<SharePage :show="sharePageShow" @close="sharePageShow = false" :isFromTodoList="isFromTodoList"></SharePage>
<PopupMore :show="popupMoreShow" @close="popupMoreShow = false"></PopupMore>
</template>
<style scoped>
.logo {
top: 4vw;
width: 24vw;
left: 4vw;
}
.slogan {
top: 20vw;
width: 76vw;
}
.home-title {
width: 72vw;
top: 144vw;
}
.main {
height: 100%;
overflow-y: auto;
}
.home-wrapper {
width: 100vw;
height: 200vw;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
min-height: -webkit-fill-available;
overflow: hidden;
/* 防止视频溢出 */
}
/* 视频背景样式 */
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
/* 保持视频比例并填满容器 */
z-index: 0;
/* 置于最底层 */
pointer-events: none;
/* 禁止视频交互,避免影响按钮点击 */
}
/* fallback 背景图 */
.fallback-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('../assets/images/new/home-bg.png');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
z-index: -1;
/* 置于视频下方 */
}
.scene-item {
position: fixed;
cursor: pointer;
transition: all 0.4s ease;
overflow: hidden;
animation: float 4s ease-in-out infinite;
}
.scene-item:hover {
transform: scale(1.05);
}
.scene-item img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
/* 马的位置 */
.item-ma1 {
width: 14vw;
top: 47vw;
left: 4.4vw;
}
.item-ma2 {
width: 14vw;
top: 47vw;
right: 4.4vw;
}
.item-ma3 {
width: 21vw;
top: 162vw;
left: 3vw;
}
.item-ma4 {
width: 25vw;
top: 128vw;
right: 6.6vw;
}
.item-jiu1 {
width: 49vw;
top: 63vw;
left: 0;
}
.item-jiu2 {
width: 49vw;
top: 63vw;
right: 0;
}
.item-xique1 {
width: 31vw;
top: 78vw;
left: 39vw;
}
.item-xique2 {
width: 37vw;
top: 77vw;
right: 28vw;
}
.item-sidai {
width: 31vw;
top: 79vw;
left: 39vw;
}
.item-lihe {
width: 42vw;
top: 85vw;
left: 7vw;
}
.item-hulu {
width: 30vw;
top: 99vw;
left: 21vw;
}
.item-taozi {
width: 25vw;
top: 110vw;
left: 10vw;
}
/* 通用“奔跑”动画:轻微上下+前后+缩放 */
@keyframes horse-gallop {
0% {
transform: translateY(0) translateX(0) scale(1) rotate(0deg);
}
20% {
transform: translateY(-2px) translateX(2px) scale(1.01) rotate(-1deg);
}
40% {
transform: translateY(0) translateX(3px) scale(1.01) rotate(1deg);
}
60% {
transform: translateY(-3px) translateX(1px) scale(1.02) rotate(-0.5deg);
}
80% {
transform: translateY(0) translateX(0) scale(1.01) rotate(0.5deg);
}
100% {
transform: translateY(0) translateX(0) scale(1) rotate(0deg);
}
}
/* 每匹马用同一个 keyframes但频率和起始时间不同让节奏更自然 */
.scene-item.item-ma1 img {
animation: horse-gallop 1.0s infinite ease-in-out;
animation-delay: 0s;
transform-origin: center bottom;
}
.scene-item.item-ma2 img {
animation: horse-gallop 1.25s infinite ease-in-out;
/* 负 delay进场时就已经错位不会同时起跳 */
animation-delay: -0.3s;
transform-origin: center bottom;
}
.scene-item.item-ma3 img {
animation: horse-gallop 1.4s infinite ease-in-out;
animation-delay: -0.6s;
transform-origin: center bottom;
}
.scene-item.item-ma4 img {
animation: horse-gallop 1.65s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-xique1 img {
animation: horse-gallop 1.7s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-xique2 img {
animation: horse-gallop 1.7s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-sidai img {
animation: horse-gallop 2.6s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-jiu1 img {
animation: horse-gallop 2.4s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-jiu2 img {
animation: horse-gallop 2.2s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-lihe img {
animation: horse-gallop 2s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-taozi img {
animation: horse-gallop 1.5s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.scene-item.item-hulu img {
animation: horse-gallop 1.8s infinite ease-in-out;
animation-delay: -0.9s;
transform-origin: center bottom;
}
.item-1 {
width: 21vw;
bottom: 7vw;
left: 0;
animation-delay: 0s;
}
.lottery-main {
display: flex;
justify-content: center;
align-items: center;
width: 4vw;
height: 3.5vw;
position: absolute;
top: 1.7vw;
right: 7.3vw;
color: #fff;
}
.lottery-main .lottery-value {
font-size: 4vw;
}
.item-2 {
width: 48vw;
bottom: 5vw;
animation-delay: 0s;
}
.join-main {
position: absolute;
top: 3.4vw;
right: -3.5vw;
width: 13vw;
}
.join-main .join-value {
margin: 0;
color: white;
text-shadow: -1px -1px 0 #ff0000, 1px -1px 0 #ff0000, -1px 1px 0 #ff0000, 1px 1px 0 #ff0000, 0 0 2px #ff0000;
font-size: 5.4vw;
font-weight: 900;
}
.item-3 {
width: 21vw;
bottom: 7vw;
right: 0;
animation-delay: 0s;
}
.item-4 {
width: 11vw;
top: 8vw;
right: 4vw;
animation-delay: 0s;
z-index: 999;
}
.item-5 {
width: 11vw;
top: 8vw;
right: 4vw;
animation-delay: 0s;
}
.item-6 {
width: 14.5vw;
top: 8%;
right: 0;
animation-delay: 0s;
}
.item-7 {
width: 14.5vw;
top: 12%;
right: 0;
animation-delay: 0s;
}
.item-8 {
width: 27vw;
bottom: 27vw;
right: 1vw;
animation-delay: 0s;
}
.item-ma1 {
width: 14vw;
top: 47vw;
left: 4.4vw;
}
@keyframes loginloading {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>