qs_xinchun2026_h5/components/LongfusiScene.vue

364 lines
8.2 KiB
Vue

<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import ImageGalleryModal from './ImageGalleryModal.vue'
import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue'
import ImagePreloader from '@/utils/preload';
import { recordInteraction } from '../api/api.js'
// 组件属性
const props = defineProps({
// 是否为活动状态
active: {
type: Boolean,
default: false
},
// 滚动位置,用于实现视差效果
scrollPosition: {
type: Number,
default: 0
},
// 视频地址
videoUrl: {
type: String,
default: ''
},
// 页面访问UUID
pageVisitUuid: {
type: String,
default: ''
}
})
// 组件事件
const emit = defineEmits(['collect-seal', 'video-open', 'video-close'])
// sq3图片显示状态
const sq3ImageVisible = ref(false)
// 是否已经收集福印
const sealCollected = ref(false)
// 视频播放状态
const showVideoPlayer = ref(false)
// 视频播放完成状态
const hasVideoPlayed = ref(false)
// 互动点击完成状态
const hasInteractiveClicked = ref(false)
// 图片加载状态
const imagesLoaded = ref(false)
const isPreloading = ref(false)
// 计算视差效果的偏移量
const parallaxOffset = computed(() => {
// 滚动位置的1/10作为视差偏移
return props.scrollPosition * 0.1
})
// 预加载图片函数
const preloadImages = async () => {
if (imagesLoaded.value || isPreloading.value) return
isPreloading.value = true
try {
// 提取所有图片地址
const imageUrls = lfsImages.map(img => img.src)
// 使用ImagePreloader批量预加载图片
await ImagePreloader.preloadAll(imageUrls)
imagesLoaded.value = true
} catch (error) {
console.error('图片预加载失败:', error)
} finally {
isPreloading.value = false
}
}
// 监听激活状态变化,当组件激活时预加载图片
watch(() => props.active, async (newActive) => {
if (newActive) {
await preloadImages()
}
})
// 动态加载图片
const getImageUrl = (name) => {
return new URL(`/static/lfs/${name}.jpg`, import.meta.url).href
}
// 图片浏览数据
const lfsImages = [
{ src: getImageUrl('img1'), title: '传艺承福阁' },
{ src: getImageUrl('img2'), title: '京味福食巷' },
{ src: getImageUrl('img3'), title: '雅趣福玩斋' }
]
// 图片浏览器弹窗状态
const galleryVisible = ref(false)
const currentGalleryIndex = ref(0)
// 点击热点打开图片浏览器
const openGallery = (index) => {
currentGalleryIndex.value = index
galleryVisible.value = true
// 标记互动点击已完成
hasInteractiveClicked.value = true
// 记录点击交互
if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'longfusi',
interaction_type: 'click'
}).catch(err => {
console.log('记录点击交互失败:', err)
})
}
// 检查是否满足收集福印的条件
checkSealCollection()
}
// 检查是否满足收集福印的条件
const checkSealCollection = () => {
if (!sealCollected.value && hasVideoPlayed.value && hasInteractiveClicked.value) {
sq3ImageVisible.value = true
sealCollected.value = true
emit('collect-seal')
}
}
// 关闭图片浏览器
const closeGallery = () => {
galleryVisible.value = false
}
// 打开视频播放器
const openVideoPlayer = () => {
showVideoPlayer.value = true
emit('video-open')
}
// 关闭视频播放器
const closeVideoPlayer = () => {
showVideoPlayer.value = false
emit('video-close')
}
// 处理视频播放事件
const handleVideoPlayed = () => {
hasVideoPlayed.value = true
// 记录视频播放交互
if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'longfusi',
interaction_type: 'video_play'
}).catch(err => {
console.log('记录视频播放交互失败:', err)
})
}
// 检查是否满足收集福印的条件
checkSealCollection()
}
// 页面挂载时的初始化
onMounted(() => {
// 添加动画类,触发入场动画
const container = document.querySelector('.longfusi-scene-container')
if (container) {
container.classList.add('animate-in')
}
// 如果组件已经激活,立即预加载图片
if (props.active) {
preloadImages()
}
})
</script>
<template>
<section class="longfusi-scene-container" :class="{ 'active': active }">
<!-- 背景图片层 -->
<div class="background-layer" :style="{ transform: `translateY(${parallaxOffset}px)` }">
<!-- 使用隆福寺商圈背景图片 -->
<img src="/static/bg/bg4.jpg" alt="隆福寺商圈" class="background-image" />
</div>
<!-- sq3图片 -->
<img
v-if="sq3ImageVisible"
src="/static/images/sq4.png"
alt="新春祝福"
class="sq-image"
/>
<!-- 热点点击区域1 - 左下方 -->
<!-- <div class="hotspot-area" style="left: 163rpx; top: 950rpx;" @click="openGallery(0)">
<div class="pulse-indicator">
<div class="pulse-circle"></div>
</div>
</div> -->
<!-- 热点点击区域2 - 右下方 -->
<div class="hotspot-area" style="left: 450rpx; top: 900rpx;" @click="openGallery(0)">
<div class="pulse-indicator">
<div class="pulse-circle"></div>
</div>
<div class="hotspot-text">点击查看隆福寺新年照片</div>
</div>
<!-- 热点点击区域3 - 上方 -->
<!-- <div class="hotspot-area" style="left: 320rpx; top: 738rpx;" @click="openGallery(2)">
<div class="pulse-indicator">
<div class="pulse-circle"></div>
</div>
</div> -->
<!-- 图片浏览器弹窗 -->
<ImageGalleryModal
:visible="galleryVisible"
:images="lfsImages"
:current-index="currentGalleryIndex"
mode="with-title"
@close="closeGallery"
/>
<!-- 视频播放按钮 -->
<VideoPlayButton @play="openVideoPlayer" />
<!-- 视频播放器弹窗 -->
<VideoPlayerModal
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
@video-played="handleVideoPlayed"
/>
</section>
</template>
<style scoped>
.longfusi-scene-container {
position: relative;
width: 100%;
height: auto; /* 高度由内容决定 */
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
background-color: #ff6b35;
}
/* 背景图片层 */
.background-layer {
position: relative;
width: 100%;
transition: transform 0.1s ease;
/* 背景图片决定容器高度 */
height: auto;
}
.background-image {
width: 100%;
height: auto;
display: block;
/* 确保图片完整显示,决定容器高度 */
object-fit: contain;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-20px); }
to { opacity: 1; transform: translateY(0); }
}
/* 入场动画 */
.longfusi-scene-container.animate-in {
animation: sceneFadeIn 1s ease-out;
}
@keyframes sceneFadeIn {
from { opacity: 0; transform: translateY(50px); }
to { opacity: 1; transform: translateY(0); }
}
/* sq图片 */
.sq-image {
position: absolute;
top: 390rpx;
right: -8rpx;
width: auto;
height: auto;
max-width: 300rpx;
z-index: 20;
animation: fadeIn 0.5s ease;
}
/* 热点点击区域 */
.hotspot-area {
position: absolute;
width: 150rpx;
height: 150rpx;
cursor: pointer;
z-index: 25;
display: flex;
align-items: center;
justify-content: center;
}
/* 脉冲动效 */
.pulse-indicator {
position: relative;
width: 100rpx;
height: 100rpx;
}
.pulse-circle {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
border-radius: 50%;
background-color: rgba(255, 215, 0, 0.4);
border: 3rpx solid rgba(255, 215, 0, 0.8);
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: translate(-50%, -50%) scale(0.8);
opacity: 0.8;
}
100% {
transform: translate(-50%, -50%) scale(2);
opacity: 0;
}
}
/* 热点文字提示 */
.hotspot-text {
position: absolute;
bottom: -40rpx;
left: 50%;
transform: translateX(-50%);
font-size: 24rpx;
color: #fff;
background-color: rgba(0, 0, 0, 0.6);
padding: 8rpx 16rpx;
border-radius: 20rpx;
white-space: nowrap;
z-index: 30;
text-align: center;
}
</style>