569 lines
12 KiB
Vue
569 lines
12 KiB
Vue
<template>
|
||
<ModalTransition class="game-swiper" :show="show">
|
||
<div class="scene-item item-1" @click="handleGoHome">
|
||
<img src="../assets/images/new/go-home.webp" alt="回到首页">
|
||
</div>
|
||
|
||
<div class="confirm-layout" v-if="isConfirmBtnDisplay">
|
||
<div class="scene-item item-3" @click="handleConfirmClick">
|
||
<img src="../assets/images/new/confirm-btn.webp" alt="确定">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="swiper-page">
|
||
<div class="carousel-container">
|
||
<div
|
||
class="carousel-wrapper"
|
||
@touchstart="handleTouchStart"
|
||
@touchmove="handleTouchMove"
|
||
@touchend="handleTouchEnd"
|
||
>
|
||
<div
|
||
class="carousel-track"
|
||
:style="{
|
||
transform: `translateX(calc(-${currentIndex * 72 }vw))`,
|
||
transition: isTransitioning ? 'transform 0.3s ease' : 'none'
|
||
}"
|
||
>
|
||
|
||
<div
|
||
v-for="(slide, index) in slides"
|
||
:key="index"
|
||
:class="['carousel-slide', { active: currentIndex === index }]"
|
||
:style="{ backgroundImage: `url(${slide.border})` }"
|
||
>
|
||
<img v-show="slide.show" class="complete-icon" src="../assets/images/new/complete-icon.webp" alt="已完成">
|
||
<img class="slider-img" :src="slide.image" :alt="slide.title" />
|
||
|
||
<div class="shou-dev">
|
||
<div class="show-position">
|
||
<div class="action-btn">
|
||
<img class="shou-click" src="../assets/images/new/pre-icon.webp" alt="左" />
|
||
<img class="shou-click" src="../assets/images/new/next-icon.webp" alt="右" />
|
||
</div>
|
||
<img class="shou-btn" src="../assets/images/new/shouzhi.webp" alt="手" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 指示器 -->
|
||
<div class="carousel-indicators">
|
||
<span
|
||
v-for="(slide, index) in slides"
|
||
:key="index"
|
||
:class="['indicator', { active: currentIndex === index }]"
|
||
@click="goToSlide(index)"
|
||
></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</ModalTransition>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted, watch, computed } from 'vue'
|
||
import ModalTransition from "./ModalTransition.vue"
|
||
import globalToastEvent, { ToastType } from '../globalToastEvent';
|
||
import { Request } from "../libs/utils"
|
||
import slider1 from '../assets/images/new/slider1.webp';
|
||
import slider2 from '../assets/images/new/slider2.webp';
|
||
import slider3 from '../assets/images/new/slider3.webp';
|
||
import slider4 from '../assets/images/new/slider4.webp';
|
||
import slider5 from '../assets/images/new/slider5.webp';
|
||
import { globalStore } from "../globalstore.js";
|
||
|
||
import gameBorder1 from '../assets/images/new/game1-border.webp'
|
||
import gameBorder2 from '../assets/images/new/game2-border.webp'
|
||
import gameBorder3 from '../assets/images/new/game3-border.webp'
|
||
import gameBorder4 from '../assets/images/new/game4-border.webp'
|
||
import gameBorder5 from '../assets/images/new/game5-border.webp'
|
||
|
||
const props = defineProps({
|
||
show: false,
|
||
})
|
||
|
||
const emit = defineEmits(['close'])
|
||
|
||
// 轮播图数据
|
||
const slides = ref([
|
||
{
|
||
image: slider1,
|
||
title: '轮播图1',
|
||
id: 1,
|
||
show: true,
|
||
border: gameBorder1,
|
||
},
|
||
{
|
||
image: slider2,
|
||
title: '轮播图2',
|
||
id: 2,
|
||
show: true,
|
||
border: gameBorder2,
|
||
},
|
||
{
|
||
image: slider3,
|
||
title: '轮播图3',
|
||
id: 3,
|
||
show: true,
|
||
border: gameBorder3,
|
||
},
|
||
{
|
||
image: slider4,
|
||
title: '轮播图4',
|
||
id: 4,
|
||
show: true,
|
||
border: gameBorder4,
|
||
},
|
||
{
|
||
image: slider5,
|
||
title: '轮播图5',
|
||
id: 5,
|
||
show: true,
|
||
border: gameBorder5,
|
||
}
|
||
])
|
||
|
||
const isConfirmBtnDisplay = computed(() => {
|
||
const slide = slides.value?.[currentIndex.value];
|
||
return !globalStore.completed_games_today.includes(`game${slide.id}`);
|
||
})
|
||
|
||
const getGameInfo = async () => {
|
||
slides.value = slides.value.map(slide => ({
|
||
...slide,
|
||
show: globalStore.completed_games_today.includes(`game${slide.id}`)
|
||
}));
|
||
}
|
||
|
||
watch(() => props.show, async (newVal) => {
|
||
if (!newVal) {
|
||
return
|
||
}
|
||
getGameInfo();
|
||
}, { immediate: true })
|
||
|
||
const handleGoHome = () => {
|
||
emit('close')
|
||
}
|
||
|
||
|
||
const currentIndex = ref(0)
|
||
const startX = ref(0)
|
||
const moveX = ref(0)
|
||
const isDragging = ref(false)
|
||
const isTransitioning = ref(true)
|
||
let autoPlayTimer = null
|
||
|
||
// 触摸开始
|
||
const handleTouchStart = (e) => {
|
||
startX.value = e.touches[0].clientX
|
||
isDragging.value = true
|
||
isTransitioning.value = false
|
||
stopAutoPlay()
|
||
}
|
||
|
||
// 触摸移动
|
||
const handleTouchMove = (e) => {
|
||
if (!isDragging.value) return
|
||
moveX.value = e.touches[0].clientX - startX.value
|
||
}
|
||
|
||
// 触摸结束
|
||
const handleTouchEnd = () => {
|
||
if (!isDragging.value) return
|
||
|
||
isTransitioning.value = true
|
||
const threshold = 50 // 滑动阈值
|
||
|
||
if (moveX.value > threshold) {
|
||
// 向右滑动 - 上一张
|
||
prevSlide()
|
||
} else if (moveX.value < -threshold) {
|
||
// 向左滑动 - 下一张
|
||
nextSlide()
|
||
}
|
||
|
||
isDragging.value = false
|
||
moveX.value = 0
|
||
// startAutoPlay()
|
||
}
|
||
|
||
// 上一张
|
||
const prevSlide = () => {
|
||
currentIndex.value = currentIndex.value > 0
|
||
? currentIndex.value - 1
|
||
: slides.value.length - 1
|
||
}
|
||
|
||
// 下一张
|
||
const nextSlide = () => {
|
||
currentIndex.value = currentIndex.value < slides.value.length - 1
|
||
? currentIndex.value + 1
|
||
: 0
|
||
}
|
||
|
||
// 跳转到指定幻灯片
|
||
const goToSlide = (index) => {
|
||
isTransitioning.value = true
|
||
currentIndex.value = index
|
||
stopAutoPlay()
|
||
// startAutoPlay()
|
||
}
|
||
|
||
const hasVisitedBefore = localStorage.getItem('hasVisitedGameSwiper');
|
||
const handleConfirmClick = async () => {
|
||
const loading = weui.loading()
|
||
const slide = slides.value?.[currentIndex.value]
|
||
if (!slide) return
|
||
handleSlideClick(slide)
|
||
|
||
// 点击确定按钮开始游戏,调用一次game log接口
|
||
const gameSource = `game${slide.id}`;
|
||
globalStore.game_id = gameSource;
|
||
let gameLogRes = await Request("game/log", { source: globalStore.game_id }, "POST");
|
||
if (!gameLogRes || !gameLogRes.res || (gameLogRes.res.status !== 200 && gameLogRes.res.status !== 201)) {
|
||
emit('close')
|
||
loading.hide()
|
||
return
|
||
} else {
|
||
globalStore.current_game_log_id = gameLogRes.json.log_id;
|
||
}
|
||
}
|
||
|
||
const handleSlideClick = (slide) => {
|
||
// globalToastEvent.emit(ToastType.SHOW_GAMEDEMO)
|
||
if (!hasVisitedBefore) {
|
||
localStorage.setItem('hasVisitedGameSwiper', true);
|
||
globalToastEvent.emit(ToastType.SHOW_GAMEDEMO)
|
||
globalToastEvent.emit(ToastType.SHOW_GAMEPAGE, slide)
|
||
} else {
|
||
globalToastEvent.emit(ToastType.SHOW_GAMEPAGE, slide)
|
||
}
|
||
}
|
||
|
||
// 自动播放
|
||
const startAutoPlay = () => {
|
||
autoPlayTimer = setInterval(() => {
|
||
nextSlide()
|
||
}, 3000) // 每3秒切换一次
|
||
}
|
||
|
||
// 停止自动播放
|
||
const stopAutoPlay = () => {
|
||
if (autoPlayTimer) {
|
||
clearInterval(autoPlayTimer)
|
||
autoPlayTimer = null
|
||
}
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
// startAutoPlay()
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
stopAutoPlay()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 手指轻微上下浮动 */
|
||
.shou-dev .shou-btn {
|
||
width: 25vw;
|
||
position: absolute;
|
||
top: 7vw;
|
||
left: 20vw;
|
||
animation: hand-bounce 1.2s ease-in-out infinite;
|
||
transform-origin: center bottom;
|
||
}
|
||
|
||
@keyframes hand-bounce {
|
||
0%, 100% {
|
||
transform: translateY(0);
|
||
}
|
||
50% {
|
||
/* 6px -> 6/375*100 ≈ 1.6vw */
|
||
transform: translateY(-1.6vw);
|
||
}
|
||
}
|
||
|
||
/* 确保按钮容器居中,方便做左右位移动画 */
|
||
.action-btn {
|
||
display: flex;
|
||
flex-flow: row;
|
||
justify-content: center;
|
||
align-items: center;
|
||
position: relative;
|
||
}
|
||
|
||
/* 左箭头 向左轻微移动 */
|
||
.action-btn .shou-click:first-child {
|
||
width: 24vw;
|
||
animation: arrow-left-move 1.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes arrow-left-move {
|
||
0%, 100% {
|
||
transform: translateX(0);
|
||
opacity: 0.6;
|
||
}
|
||
50% {
|
||
/* 6px -> 1.6vw */
|
||
transform: translateX(-1.6vw);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 右箭头 向右轻微移动 */
|
||
.action-btn .shou-click:last-child {
|
||
width: 24vw;
|
||
animation: arrow-right-move 1.2s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes arrow-right-move {
|
||
0%, 100% {
|
||
transform: translateX(0);
|
||
opacity: 0.6;
|
||
}
|
||
50% {
|
||
/* 6px -> 1.6vw */
|
||
transform: translateX(1.6vw);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.show-position {
|
||
position: relative;
|
||
}
|
||
|
||
.shou-dev {
|
||
position: absolute;
|
||
top: 50vw;
|
||
left: 11vw;
|
||
}
|
||
|
||
.shou-dev .shou-btn {
|
||
width: 25vw;
|
||
position: absolute;
|
||
top: 7vw;
|
||
left: 20vw;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
flex-flow: row;
|
||
justify-content: center;
|
||
}
|
||
|
||
.action-btn .shou-click {
|
||
width: 24vw;
|
||
}
|
||
|
||
.complete-icon {
|
||
width: 14vw !important;
|
||
position: absolute;
|
||
left: 2vw;
|
||
top: -2vw;
|
||
}
|
||
.slider-img {
|
||
padding-top: 1.4vw;
|
||
padding-left: 0.5vw;
|
||
}
|
||
|
||
.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-1 {
|
||
width: 14vw;
|
||
height: 14vw;
|
||
top: 6.8vw;
|
||
left: 3vw;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.item-2 {
|
||
width: 12vw;
|
||
height: 12vw;
|
||
top: 8vw;
|
||
right: 2vw;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.item-3 {
|
||
width: 46vw;
|
||
top: 163vw;
|
||
animation-delay: 0s;
|
||
}
|
||
|
||
.confirm-layout {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.swiper-page {
|
||
width: 100%;
|
||
min-height: 100vh;
|
||
background: #f5f5f5;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
/* 20px -> 5.33vw */
|
||
padding: 5.33vw;
|
||
box-sizing: border-box;
|
||
background-image: url('../assets/images/new/swiper-page-bg.webp');
|
||
background-size: 100% auto;
|
||
background-repeat: no-repeat;
|
||
background-position: 0 0;
|
||
}
|
||
|
||
.carousel-container {
|
||
width: 100%;
|
||
max-width: 100vw;
|
||
position: relative;
|
||
/* 0 20px -> 0 5.33vw */
|
||
padding: 0 5.33vw;
|
||
box-sizing: border-box;
|
||
top: -18vw;
|
||
}
|
||
|
||
.carousel-wrapper {
|
||
width: 80vw;
|
||
overflow: visible;
|
||
position: relative;
|
||
touch-action: pan-y;
|
||
}
|
||
|
||
.carousel-track {
|
||
display: flex;
|
||
flex-direction: row;
|
||
width: 100%;
|
||
margin-left: 3vw;
|
||
/* 20px 0 -> 5.33vw 0 */
|
||
/* padding: 5.33vw 0; */
|
||
}
|
||
|
||
.carousel-slide {
|
||
width: 100%;
|
||
height: 115.7vw;
|
||
padding: 5vw;
|
||
background-size: cover;
|
||
background-position: center;
|
||
|
||
/* 100% - 160px -> 100% - 42.67vw (160/375*100) */
|
||
min-width: calc(100% - 42.67vw);
|
||
width: calc(100% - 42.67vw);
|
||
|
||
/* 0 20px -> 0 5.33vw */
|
||
margin: 0 2vw;
|
||
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
transition: all 0.3s ease;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
transform: scale(0.85);
|
||
opacity: 0.5;
|
||
filter: grayscale(60%) brightness(0.7);
|
||
}
|
||
|
||
.carousel-slide.active {
|
||
transform: scale(1);
|
||
opacity: 1;
|
||
filter: grayscale(0%) brightness(1);
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||
z-index: 2;
|
||
}
|
||
|
||
.carousel-slide img {
|
||
width: 100%;
|
||
height: auto;
|
||
display: block;
|
||
object-fit: cover;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 指示器 */
|
||
.carousel-indicators {
|
||
position: absolute;
|
||
bottom: -8vw;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
gap: 2vw;
|
||
/* 2vw 4vw 保持不变,这里本来就是 vw */
|
||
padding: 2vw 4vw;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
border-radius: 4vw;
|
||
backdrop-filter: blur(4px);
|
||
}
|
||
|
||
.indicator {
|
||
/* 10px -> 2.67vw */
|
||
width: 2.67vw;
|
||
height: 2.67vw;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.6);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
/* 2px -> 0.53vw */
|
||
border: 0.53vw solid transparent;
|
||
}
|
||
|
||
.indicator:hover {
|
||
background: rgba(255, 255, 255, 0.8);
|
||
transform: scale(1.2);
|
||
}
|
||
|
||
.indicator.active {
|
||
background: #fff;
|
||
/* 28px -> 7.47vw */
|
||
width: 7.47vw;
|
||
border-radius: 5px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
/* 移动端适配 */
|
||
@media (max-width: 768px) {
|
||
.game-page {
|
||
/* 10px -> 2.67vw */
|
||
padding: 2.67vw;
|
||
}
|
||
|
||
.carousel-container {
|
||
/* 0 10px -> 0 2.67vw */
|
||
/* padding: 0 2.67vw; */
|
||
}
|
||
|
||
.carousel-slide {
|
||
min-width: calc(100% - 12vw);
|
||
width: calc(100% - 12vw);
|
||
/* margin: 0 2.67vw; */
|
||
}
|
||
|
||
.carousel-track {
|
||
/* 10px 0 -> 2.67vw 0 */
|
||
padding: 2.67vw 0;
|
||
}
|
||
}
|
||
</style>
|