From 2c686db5a58f8d65bb88db539d55657cd9a43a36 Mon Sep 17 00:00:00 2001 From: Wenzhe Date: Fri, 6 Feb 2026 13:27:41 +0800 Subject: [PATCH] v1.3.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、增加场景交互点击日志记录 2、每个场景中,必须有一次点击和视频播放,才能收集福印 3、抽奖留资时,必须检测是否完成了5个福印的收集 --- api/api.js | 15 +++++++- components/ChongwenScene.vue | 60 ++++++++++++++++++++++++++--- components/DongzhimenScene.vue | 59 ++++++++++++++++++++++++++--- components/EndPage.vue | 12 ++++++ components/LongfusiScene.vue | 54 +++++++++++++++++++++++++- components/QianmenScene.vue | 61 +++++++++++++++++++++++++++--- components/SinglePageContainer.vue | 5 +++ components/VideoPlayerModal.vue | 8 +++- components/WangfujingScene.vue | 54 +++++++++++++++++++++++++- 9 files changed, 306 insertions(+), 22 deletions(-) diff --git a/api/api.js b/api/api.js index 864a556..c72aa64 100644 --- a/api/api.js +++ b/api/api.js @@ -64,10 +64,23 @@ export const getSceneById = (id) => { return get(`/api/scenes/${id}`) } +/** + * 记录用户交互 + * @param {Object} data - 交互数据 + * @param {string} data.page_visit_uuid - 页面访问UUID + * @param {string} data.scene_id - 场景ID + * @param {string} data.interaction_type - 交互类型 (click 或 video_play) + * @returns {Promise} + */ +export const recordInteraction = (data) => { + return post('/api/interactions', data) +} + export default { saveUserInfo, generateCoupletPoster, recordPageVisit, getScenes, - getSceneById + getSceneById, + recordInteraction } diff --git a/components/ChongwenScene.vue b/components/ChongwenScene.vue index 9d5d197..28b70d4 100644 --- a/components/ChongwenScene.vue +++ b/components/ChongwenScene.vue @@ -2,6 +2,7 @@ import { ref, onMounted, computed } from 'vue' import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayerModal from './VideoPlayerModal.vue' +import { recordInteraction } from '../api/api.js' // 组件属性 const props = defineProps({ @@ -24,6 +25,11 @@ const props = defineProps({ isMusicPlaying: { type: Boolean, default: false + }, + // 页面访问UUID + pageVisitUuid: { + type: String, + default: '' } }) @@ -38,6 +44,11 @@ const sq2ImageVisible = ref(false) // 视频播放状态 const showVideoPlayer = ref(false) +// 视频播放完成状态 +const hasVideoPlayed = ref(false) +// 互动点击完成状态 +const hasInteractiveClicked = ref(false) + // 计算视差效果的偏移量 const parallaxOffset = computed(() => { // 滚动位置的1/10作为视差偏移 @@ -49,13 +60,23 @@ const wasBgPlayingBeforeWebview = ref(false) // 打开webview页面 const openWebview = () => { - // 第一次点击查看按钮时收集福印 - if (!sealCollected.value) { - sealCollected.value = true - sq2ImageVisible.value = true - emit('collect-seal') + // 标记互动点击已完成 + hasInteractiveClicked.value = true + + // 记录点击交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'chongwenmen', + interaction_type: 'click' + }).catch(err => { + console.log('记录点击交互失败:', err) + }) } + // 检查是否满足收集福印的条件 + checkSealCollection() + // 保存BGM状态 wasBgPlayingBeforeWebview.value = props.isMusicPlaying @@ -93,6 +114,15 @@ const openWebview = () => { }) } +// 检查是否满足收集福印的条件 +const checkSealCollection = () => { + if (!sealCollected.value && hasVideoPlayed.value && hasInteractiveClicked.value) { + sealCollected.value = true + sq2ImageVisible.value = true + emit('collect-seal') + } +} + // 打开视频播放器 const openVideoPlayer = () => { showVideoPlayer.value = true @@ -105,6 +135,25 @@ const closeVideoPlayer = () => { emit('video-close') } +// 处理视频播放事件 +const handleVideoPlayed = () => { + hasVideoPlayed.value = true + + // 记录视频播放交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'chongwenmen', + interaction_type: 'video_play' + }).catch(err => { + console.log('记录视频播放交互失败:', err) + }) + } + + // 检查是否满足收集福印的条件 + checkSealCollection() +} + // 页面挂载时的初始化 onMounted(() => { // 添加动画类,触发入场动画 @@ -156,6 +205,7 @@ onMounted(() => { :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" + @video-played="handleVideoPlayed" /> diff --git a/components/DongzhimenScene.vue b/components/DongzhimenScene.vue index 511c697..fe9a364 100644 --- a/components/DongzhimenScene.vue +++ b/components/DongzhimenScene.vue @@ -3,6 +3,7 @@ import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurr import ImagePreloader from '@/utils/preload' import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayerModal from './VideoPlayerModal.vue' +import { recordInteraction } from '../api/api.js' // 获取 Vue 实例 const instance = vueGetCurrentInstance() @@ -20,6 +21,10 @@ const props = defineProps({ videoUrl: { type: String, default: '' + }, + pageVisitUuid: { + type: String, + default: '' } }) @@ -32,6 +37,11 @@ const sealCollected = ref(false) // 视频播放状态 const showVideoPlayer = ref(false) +// 视频播放完成状态 +const hasVideoPlayed = ref(false) +// 互动点击完成状态 +const hasInteractiveClicked = ref(false) + // 计算视差效果的偏移量 const parallaxOffset = computed(() => { return props.scrollPosition * 0.1 @@ -451,12 +461,22 @@ const handleTouchEnd = () => { canvasDisabled.value = true console.log('Canvas 触摸事件已禁用') - // 第一次成功放入目标区域时显示sq3图片并收集福印 - if (!sealCollected.value) { - sq3ImageVisible.value = true - sealCollected.value = true - emit('collect-seal') + // 标记互动点击已完成 + hasInteractiveClicked.value = true + + // 记录点击交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'dongzhimen', + interaction_type: 'click' + }).catch(err => { + console.log('记录点击交互失败:', err) + }) } + + // 检查是否满足收集福印的条件 + checkSealCollection() } else { // 未放入目标区域,回归到原始位置 console.log('未放入目标区域,回归原始位置') @@ -465,6 +485,15 @@ const handleTouchEnd = () => { } } +// 检查是否满足收集福印的条件 +const checkSealCollection = () => { + if (!sealCollected.value && hasVideoPlayed.value && hasInteractiveClicked.value) { + sq3ImageVisible.value = true + sealCollected.value = true + emit('collect-seal') + } +} + // 重置鸭子位置到初始值 const resetDuckPosition = () => { // 恢复原始 rpx 值 @@ -487,6 +516,25 @@ const closeVideoPlayer = () => { emit('video-close') } +// 处理视频播放事件 +const handleVideoPlayed = () => { + hasVideoPlayed.value = true + + // 记录视频播放交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'dongzhimen', + interaction_type: 'video_play' + }).catch(err => { + console.log('记录视频播放交互失败:', err) + }) + } + + // 检查是否满足收集福印的条件 + checkSealCollection() +} + // 预加载图片 const preloadImages = async () => { if (imagesLoaded.value || isPreloading.value) return @@ -626,6 +674,7 @@ onUnmounted(() => { :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" + @video-played="handleVideoPlayed" /> diff --git a/components/EndPage.vue b/components/EndPage.vue index 41063c2..bdbbb4d 100644 --- a/components/EndPage.vue +++ b/components/EndPage.vue @@ -139,6 +139,18 @@ const props = defineProps({ const emit = defineEmits(['lottery', 'couplet', 'showCouplet']) const handleLottery = () => { + // 检查是否已收集所有5个福印 + const allSealsCollected = Object.values(props.collectedSeals).every(seal => seal) + + if (!allSealsCollected) { + uni.showToast({ + title: '请先集齐5个福印', + icon: 'error', + duration: 2000 + }) + return + } + // 如果已经提交过,直接提示 if (props.hasSubmitted) { uni.showToast({ diff --git a/components/LongfusiScene.vue b/components/LongfusiScene.vue index 2c9d26a..b09542d 100644 --- a/components/LongfusiScene.vue +++ b/components/LongfusiScene.vue @@ -4,6 +4,7 @@ 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({ @@ -21,6 +22,11 @@ const props = defineProps({ videoUrl: { type: String, default: '' + }, + // 页面访问UUID + pageVisitUuid: { + type: String, + default: '' } }) @@ -34,6 +40,11 @@ const sealCollected = ref(false) // 视频播放状态 const showVideoPlayer = ref(false) +// 视频播放完成状态 +const hasVideoPlayed = ref(false) +// 互动点击完成状态 +const hasInteractiveClicked = ref(false) + // 图片加载状态 const imagesLoaded = ref(false) const isPreloading = ref(false) @@ -92,8 +103,27 @@ const openGallery = (index) => { currentGalleryIndex.value = index galleryVisible.value = true - // 第一次点击热点时显示sq3图片并收集福印 - if (!sealCollected.value) { + // 标记互动点击已完成 + 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') @@ -117,6 +147,25 @@ const closeVideoPlayer = () => { 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(() => { // 添加动画类,触发入场动画 @@ -188,6 +237,7 @@ onMounted(() => { :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" + @video-played="handleVideoPlayed" /> diff --git a/components/QianmenScene.vue b/components/QianmenScene.vue index 0740128..87e90f2 100644 --- a/components/QianmenScene.vue +++ b/components/QianmenScene.vue @@ -2,6 +2,7 @@ import { ref, onMounted, onUnmounted, computed, watch } from 'vue' import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayerModal from './VideoPlayerModal.vue' +import { recordInteraction } from '../api/api.js' // 组件属性 const props = defineProps({ @@ -34,6 +35,11 @@ const props = defineProps({ isWebviewOpening: { type: Boolean, default: false + }, + // 页面访问UUID + pageVisitUuid: { + type: String, + default: '' } }) @@ -59,6 +65,11 @@ const sq1ImageVisible = ref(false) // 视频播放状态 const showVideoPlayer = ref(false) +// 视频播放完成状态 +const hasVideoPlayed = ref(false) +// 互动点击完成状态 +const hasInteractiveClicked = ref(false) + // 场景高度 const sceneHeight = ref(0) @@ -132,13 +143,23 @@ const parallaxOffset = computed(() => { // 控制鼓声播放/暂停 const toggleMusic = () => { - // 第一次点击音乐按钮时收集福印 - if (!sealCollected.value) { - sealCollected.value = true - sq1ImageVisible.value = true - emit('collect-seal') + // 标记互动点击已完成 + hasInteractiveClicked.value = true + + // 记录点击交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'qianmen', + interaction_type: 'click' + }).catch(err => { + console.log('记录点击交互失败:', err) + }) } + // 检查是否满足收集福印的条件 + checkSealCollection() + if (!drumPlayer.value) { // 创建鼓声音频对象 drumPlayer.value = uni.createInnerAudioContext() @@ -184,6 +205,15 @@ const toggleMusic = () => { } } +// 检查是否满足收集福印的条件 +const checkSealCollection = () => { + if (!sealCollected.value && hasVideoPlayed.value && hasInteractiveClicked.value) { + sealCollected.value = true + sq1ImageVisible.value = true + emit('collect-seal') + } +} + // 页面挂载时的初始化 onMounted(() => { // 预加载舞狮动画图片 @@ -245,6 +275,25 @@ const closeVideoPlayer = () => { emit('video-close') } +// 处理视频播放事件 +const handleVideoPlayed = () => { + hasVideoPlayed.value = true + + // 记录视频播放交互 + if (props.pageVisitUuid) { + recordInteraction({ + page_visit_uuid: props.pageVisitUuid, + scene_id: 'qianmen', + interaction_type: 'video_play' + }).catch(err => { + console.log('记录视频播放交互失败:', err) + }) + } + + // 检查是否满足收集福印的条件 + checkSealCollection() +} + // 预加载舞狮动画图片 const preloadLionDanceImages = () => { const lionImages = [ @@ -318,7 +367,7 @@ onUnmounted(() => { - + diff --git a/components/SinglePageContainer.vue b/components/SinglePageContainer.vue index 1dc9d77..88b53cc 100644 --- a/components/SinglePageContainer.vue +++ b/components/SinglePageContainer.vue @@ -966,6 +966,7 @@ onUnmounted(() => { :active="activeSceneIndex === 1" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :video-url="scenes[1].videoUrl" + :page-visit-uuid="pageVisitUuid" @collect-seal="collectSeal(1)" @video-open="handleVideoOpen" @video-close="handleVideoClose" @@ -976,6 +977,7 @@ onUnmounted(() => { :active="activeSceneIndex === 2" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :video-url="scenes[2].videoUrl" + :page-visit-uuid="pageVisitUuid" @collect-seal="collectSeal(2)" @video-open="handleVideoOpen" @video-close="handleVideoClose" @@ -986,6 +988,7 @@ onUnmounted(() => { :active="activeSceneIndex === 3" :scroll-position="scrollContainer?.value?.scrollTop || 0" :video-url="scenes[3].videoUrl" + :page-visit-uuid="pageVisitUuid" @collect-seal="collectSeal(3)" @video-open="handleVideoOpen" @video-close="handleVideoClose" @@ -997,6 +1000,7 @@ onUnmounted(() => { :scroll-position="scrollContainer?.value?.scrollTop || 0" :video-url="scenes[4].videoUrl" :is-music-playing="isMusicPlaying" + :page-visit-uuid="pageVisitUuid" @collect-seal="collectSeal(4)" @video-open="handleVideoOpen" @video-close="handleVideoClose" @@ -1014,6 +1018,7 @@ onUnmounted(() => { :is-music-playing="isMusicPlaying" :is-video-playing="isVideoPlaying" :is-webview-opening="isWebviewOpening" + :page-visit-uuid="pageVisitUuid" @collect-seal="collectSeal(5)" @height-changed="handleQianmenHeightChanged" @video-open="handleVideoOpen" diff --git a/components/VideoPlayerModal.vue b/components/VideoPlayerModal.vue index 8c82db2..006c633 100644 --- a/components/VideoPlayerModal.vue +++ b/components/VideoPlayerModal.vue @@ -16,7 +16,7 @@ const props = defineProps({ }) // 组件事件 -const emit = defineEmits(['close']) +const emit = defineEmits(['close', 'video-played']) // 处理关闭按钮点击 const handleClose = () => { @@ -37,6 +37,11 @@ const handleContentClick = (e) => { const handleTouchMove = (e) => { e.preventDefault() } + +// 处理视频播放 +const handleVideoPlay = () => { + emit('video-played') +}