257 lines
9.6 KiB
Vue
257 lines
9.6 KiB
Vue
<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> |