arcteryx-game/src/pages/Game/LastPage.vue
2025-08-19 23:38:39 +08:00

257 lines
9.6 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 lang="ts">
import { ref, onMounted, onUnmounted, watch, useTemplateRef } from 'vue';
import AniEle from '../../components/AniEle.vue';
import assets from '../../assets';
import AudioEffects from '../../assets/sounds'
defineProps<{
userdata: {
region: string;
store: string;
username: string;
},
timeSpent: string;
}>();
import QRCode from 'qrcode';
const qrcodeUrl = ref('');
QRCode.toDataURL(`https://arcteryx-game-demo.xn--876a.net`, {
width: 256,
}).then(url => {
qrcodeUrl.value = url;
}).catch(err => {
console.error('生成二维码失败:', err);
});
const posterP1Ele = useTemplateRef('gui-ani-end-post-p1');
const posterP2Ele = useTemplateRef('gui-ani-end-post-p2');
const emit = defineEmits(['resetGame', 'showShareMask']);
const hasP2AnimationPlayed = ref(false);
const currentPosterIndex = ref(0);
const autoScrollTimer = ref<NodeJS.Timeout | null>(null);
const isUserInteracting = ref(false);
// 开始自动轮播计时器
function startAutoScroll() {
if (autoScrollTimer.value) {
clearTimeout(autoScrollTimer.value);
}
autoScrollTimer.value = setTimeout(() => {
if (!isUserInteracting.value) {
// 自动切换到下一个海报
const nextIndex = currentPosterIndex.value === 0 ? 1 : 0;
scrollToPoster(nextIndex);
// 继续下一轮自动切换
startAutoScroll();
}
}, 5000); // 5秒
}
// 重置自动轮播计时器
function resetAutoScroll() {
isUserInteracting.value = true;
if (autoScrollTimer.value) {
clearTimeout(autoScrollTimer.value);
}
// 1秒后重新开始自动轮播
setTimeout(() => {
isUserInteracting.value = false;
startAutoScroll();
}, 1000);
}
onMounted(() => {
// 在最后一页显示后设置 Intersection Observer 监听 p2 元素
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
currentPosterIndex.value = 1; // p2 进入视口
if (hasP2AnimationPlayed.value) return
hasP2AnimationPlayed.value = true;
posterP2Ele.value?.jumpTo('p2');
console.log('Poster P2 animation triggered');
}
});
}, {
threshold: 0.5, // 当 50% 的元素进入视口时触发
rootMargin: '0px'
});
const p1Element = posterP1Ele.value?.$el;
const p2Element = posterP2Ele.value?.$el;
if (p2Element) {
observer.observe(p2Element);
console.log('Started observing P2 element');
} else {
console.warn('P2 element not found');
}
const observerP1 = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
currentPosterIndex.value = 0; // p1 进入视口
}
});
}, {
threshold: 0.5, // 当 50% 的元素进入视口时触发
rootMargin: '0px'
});
if (p1Element) {
observerP1.observe(p1Element);
console.log('Started observing P1 element');
} else {
console.warn('P1 element not found');
}
// 开始自动轮播
startAutoScroll();
// 添加海报容器的触摸和滚动事件监听
const posterContainer = document.querySelector('.poster-container');
if (posterContainer) {
posterContainer.addEventListener('touchstart', resetAutoScroll);
posterContainer.addEventListener('scroll', resetAutoScroll);
posterContainer.addEventListener('mousedown', resetAutoScroll);
}
})
onUnmounted(() => {
// 清理定时器
if (autoScrollTimer.value) {
clearTimeout(autoScrollTimer.value);
}
})
function scrollToPoster(index: number) {
currentPosterIndex.value = index;
const posterContainer = document.querySelector('.poster-container');
if (posterContainer) {
// 滚动到指定位置
posterContainer.scrollTo({
left: posterContainer.scrollWidth * 0.5 * index,
behavior: 'smooth'
});
// 如果滚动到p2且还没播放动画则播放动画
if (index === 1 && !hasP2AnimationPlayed.value) {
hasP2AnimationPlayed.value = true;
posterP2Ele.value?.jumpTo('p2');
console.log('Poster P2 animation triggered by scroll');
}
}
}
</script>
<template>
<div class="last-page"
style="z-index: 0;position: absolute; inset: 0;height: 100%; width: 100%; pointer-events: none;">
<AniEle :url="assets.ani.结尾主标" :width="925" :height="340"
:rules="[{ name: 'main', frame: 41, loop: 1, pauseAfter: true, duration: 33, reverse: false }]"
style="position: absolute;top: 13%;width: 80%;left: 11%; pointer-events:none;" ref="main-logo" />
<div class="poster-container"
style="position: absolute;top: 29.3%;left: 37.2%;width: 57.3%; height: 38%; overflow-x: auto; overflow-y: hidden; pointer-events: all; scroll-snap-type: x mandatory;">
<div class="poster-wrapper" style="display: flex; width: 200%; height: 100%; flex-direction: row;"
dir="ltr">
<AniEle :url="assets.ani.海报p1" ref="gui-ani-end-post-p1"
style="width: 50%; flex-shrink: 0; scroll-snap-align: start;" class="gui-ani-end-post" :height="940"
:width="671" :rules="[
{ name: 'p1', frame: 22, loop: 1, pauseAfter: true, duration: 33 },
]" />
<AniEle :url="assets.ani.海报p2" ref="gui-ani-end-post-p2"
style="width: 50%; flex-shrink: 0; scroll-snap-align: start;" class="gui-ani-end-post" :height="940"
:width="671" :rules="[
{ name: 'wait', frame: 1, loop: 1, pauseAfter: true, duration: 33 },
{ name: 'p2', frame: 20, loop: 1, pauseAfter: true, duration: 33 },
]" />
</div>
</div>
<div class="dot" style="position: absolute;top: 29.3%;left: 37.2%;width: 57.3%; height: 38%; ">
<div class="dot-item" @click="scrollToPoster(0); resetAutoScroll();" :class="{ active: currentPosterIndex === 0 }"></div>
<div class="dot-item" @click="scrollToPoster(1); resetAutoScroll();" :class="{ active: currentPosterIndex === 1 }"></div>
</div>
<AniEle :url="assets.ani.线" ref="gui-ani-end" class="gui-ani-end" :height="2462" :width="1179" :rules="[
{ name: 'all', frame: 54, loop: 1, pauseAfter: false, duration: 16 },
]" style="width: 100%; height: auto;position: absolute;inset: 0;pointer-events: none;" />
<img src="../../assets/game/床.webp" alt="" class="abs last-bed" style="width: 66vw;bottom: -19%;left: 17%;">
<img src="../../assets/game/分享海报.webp" alt="" class="abs"
@click="AudioEffects.play('按钮音效'); emit('showShareMask')"
style="width: 34vw;bottom: -19%;left: 15%;animation: last-btn-in 0.3s ease-out forwards; cursor: pointer; pointer-events: all;">
<img src="../../assets/game/再玩一次.webp" alt="" class="abs" @click="AudioEffects.play('按钮音效'); emit('resetGame')"
style="width: 34vw;bottom: -19%;left: 51%;animation: last-btn-in 0.3s ease-out forwards; cursor: pointer; animation-delay: 200ms; pointer-events: all;">
<div class="time-spent abs" style="left: 10%; top:44%;width: 24%;height: 23%; display: flex;
align-items: center; flex-direction: column;">
<div class="line-1"
style="display: flex; align-items: center; gap: 1vw; animation: line-in 0.5s ease-out 0.6s forwards;transform: translateX(-230%);">
<div class="username" style="font-size: 2.9vw;">{{
userdata.username }}</div>
<div class="cost-info"
style="font-size: 2.9vw; color: white; display: flex;align-items: center;justify-content: center;background-color: black;padding: 0 1.2vw; height: 4.5vw; border-radius: 4vw;">
用时</div>
</div>
<div class="time"
style="font-size: 10vw;animation: line-in 0.5s ease-out 0.8s forwards;transform: translateX(-230%);">
{{ timeSpent }}</div>
<img :src="qrcodeUrl" alt=""
style="width: 100%;bottom:7%;position: absolute;animation: line-in 0.5s ease-out 1s forwards;transform: translateX(-230%);">
</div>
<div class="region-area abs" style="left: 10%; top:30%;width: 24%;height: 10%;gap:2vw; display: flex;animation: line-in 0.5s ease-out 0.4s forwards;transform: translateX(-230%);
align-items: center; flex-direction: column;justify-content: center;">
<div class="region"
style="width: 20vw; background-color: white;font-size: 3vw;text-align: center;height: 5.2vw;line-height: 5.2vw; border-radius: 4.5vw;">
{{ userdata.region }}</div>
<div class="store" style="font-size: 2.9vw; color: gray;">{{
userdata.store }}</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.dot {
transform: none;
align-items: flex-end;
justify-content: center;
padding-bottom: 1vw;
gap: 1vw;
}
.dot .dot-item {
background-color: #b2d8ff;
height: 1.5vw;
width: 1.5vw;
pointer-events: all;
cursor: pointer;
&.active {
background-color: #78b0e9;
}
}
.poster-container {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
.poster-wrapper {
scroll-behavior: smooth;
}
.gui-ani-end-post {
object-fit: contain;
}
</style>