1、增加场景交互点击日志记录
2、每个场景中,必须有一次点击和视频播放,才能收集福印
3、抽奖留资时,必须检测是否完成了5个福印的收集
This commit is contained in:
Wenzhe 2026-02-06 13:27:41 +08:00
parent 3cf5a07df5
commit 2c686db5a5
9 changed files with 306 additions and 22 deletions

View File

@ -64,10 +64,23 @@ export const getSceneById = (id) => {
return get(`/api/scenes/${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 { export default {
saveUserInfo, saveUserInfo,
generateCoupletPoster, generateCoupletPoster,
recordPageVisit, recordPageVisit,
getScenes, getScenes,
getSceneById getSceneById,
recordInteraction
} }

View File

@ -2,6 +2,7 @@
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue' import VideoPlayerModal from './VideoPlayerModal.vue'
import { recordInteraction } from '../api/api.js'
// //
const props = defineProps({ const props = defineProps({
@ -24,6 +25,11 @@ const props = defineProps({
isMusicPlaying: { isMusicPlaying: {
type: Boolean, type: Boolean,
default: false default: false
},
// 访UUID
pageVisitUuid: {
type: String,
default: ''
} }
}) })
@ -38,6 +44,11 @@ const sq2ImageVisible = ref(false)
// //
const showVideoPlayer = ref(false) const showVideoPlayer = ref(false)
//
const hasVideoPlayed = ref(false)
//
const hasInteractiveClicked = ref(false)
// //
const parallaxOffset = computed(() => { const parallaxOffset = computed(() => {
// 1/10 // 1/10
@ -49,13 +60,23 @@ const wasBgPlayingBeforeWebview = ref(false)
// webview // webview
const openWebview = () => { const openWebview = () => {
// //
if (!sealCollected.value) { hasInteractiveClicked.value = true
sealCollected.value = true
sq2ImageVisible.value = true //
emit('collect-seal') if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'chongwenmen',
interaction_type: 'click'
}).catch(err => {
console.log('记录点击交互失败:', err)
})
} }
//
checkSealCollection()
// BGM // BGM
wasBgPlayingBeforeWebview.value = props.isMusicPlaying 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 = () => { const openVideoPlayer = () => {
showVideoPlayer.value = true showVideoPlayer.value = true
@ -105,6 +135,25 @@ const closeVideoPlayer = () => {
emit('video-close') 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(() => { onMounted(() => {
// //
@ -156,6 +205,7 @@ onMounted(() => {
:video-url="props.videoUrl" :video-url="props.videoUrl"
:visible="showVideoPlayer" :visible="showVideoPlayer"
@close="closeVideoPlayer" @close="closeVideoPlayer"
@video-played="handleVideoPlayed"
/> />

View File

@ -3,6 +3,7 @@ import { ref, onMounted, onUnmounted, computed, getCurrentInstance as vueGetCurr
import ImagePreloader from '@/utils/preload' import ImagePreloader from '@/utils/preload'
import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue' import VideoPlayerModal from './VideoPlayerModal.vue'
import { recordInteraction } from '../api/api.js'
// Vue // Vue
const instance = vueGetCurrentInstance() const instance = vueGetCurrentInstance()
@ -20,6 +21,10 @@ const props = defineProps({
videoUrl: { videoUrl: {
type: String, type: String,
default: '' default: ''
},
pageVisitUuid: {
type: String,
default: ''
} }
}) })
@ -32,6 +37,11 @@ const sealCollected = ref(false)
// //
const showVideoPlayer = ref(false) const showVideoPlayer = ref(false)
//
const hasVideoPlayed = ref(false)
//
const hasInteractiveClicked = ref(false)
// //
const parallaxOffset = computed(() => { const parallaxOffset = computed(() => {
return props.scrollPosition * 0.1 return props.scrollPosition * 0.1
@ -451,12 +461,22 @@ const handleTouchEnd = () => {
canvasDisabled.value = true canvasDisabled.value = true
console.log('Canvas 触摸事件已禁用') console.log('Canvas 触摸事件已禁用')
// sq3 //
if (!sealCollected.value) { hasInteractiveClicked.value = true
sq3ImageVisible.value = true
sealCollected.value = true //
emit('collect-seal') if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'dongzhimen',
interaction_type: 'click'
}).catch(err => {
console.log('记录点击交互失败:', err)
})
} }
//
checkSealCollection()
} else { } else {
// //
console.log('未放入目标区域,回归原始位置') 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 = () => { const resetDuckPosition = () => {
// rpx // rpx
@ -487,6 +516,25 @@ const closeVideoPlayer = () => {
emit('video-close') 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 () => { const preloadImages = async () => {
if (imagesLoaded.value || isPreloading.value) return if (imagesLoaded.value || isPreloading.value) return
@ -626,6 +674,7 @@ onUnmounted(() => {
:video-url="props.videoUrl" :video-url="props.videoUrl"
:visible="showVideoPlayer" :visible="showVideoPlayer"
@close="closeVideoPlayer" @close="closeVideoPlayer"
@video-played="handleVideoPlayed"
/> />
</view> </view>

View File

@ -139,6 +139,18 @@ const props = defineProps({
const emit = defineEmits(['lottery', 'couplet', 'showCouplet']) const emit = defineEmits(['lottery', 'couplet', 'showCouplet'])
const handleLottery = () => { 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) { if (props.hasSubmitted) {
uni.showToast({ uni.showToast({

View File

@ -4,6 +4,7 @@ import ImageGalleryModal from './ImageGalleryModal.vue'
import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue' import VideoPlayerModal from './VideoPlayerModal.vue'
import ImagePreloader from '@/utils/preload'; import ImagePreloader from '@/utils/preload';
import { recordInteraction } from '../api/api.js'
// //
const props = defineProps({ const props = defineProps({
@ -21,6 +22,11 @@ const props = defineProps({
videoUrl: { videoUrl: {
type: String, type: String,
default: '' default: ''
},
// 访UUID
pageVisitUuid: {
type: String,
default: ''
} }
}) })
@ -34,6 +40,11 @@ const sealCollected = ref(false)
// //
const showVideoPlayer = ref(false) const showVideoPlayer = ref(false)
//
const hasVideoPlayed = ref(false)
//
const hasInteractiveClicked = ref(false)
// //
const imagesLoaded = ref(false) const imagesLoaded = ref(false)
const isPreloading = ref(false) const isPreloading = ref(false)
@ -92,8 +103,27 @@ const openGallery = (index) => {
currentGalleryIndex.value = index currentGalleryIndex.value = index
galleryVisible.value = true 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 sq3ImageVisible.value = true
sealCollected.value = true sealCollected.value = true
emit('collect-seal') emit('collect-seal')
@ -117,6 +147,25 @@ const closeVideoPlayer = () => {
emit('video-close') 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(() => { onMounted(() => {
// //
@ -188,6 +237,7 @@ onMounted(() => {
:video-url="props.videoUrl" :video-url="props.videoUrl"
:visible="showVideoPlayer" :visible="showVideoPlayer"
@close="closeVideoPlayer" @close="closeVideoPlayer"
@video-played="handleVideoPlayed"
/> />

View File

@ -2,6 +2,7 @@
import { ref, onMounted, onUnmounted, computed, watch } from 'vue' import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue' import VideoPlayerModal from './VideoPlayerModal.vue'
import { recordInteraction } from '../api/api.js'
// //
const props = defineProps({ const props = defineProps({
@ -34,6 +35,11 @@ const props = defineProps({
isWebviewOpening: { isWebviewOpening: {
type: Boolean, type: Boolean,
default: false default: false
},
// 访UUID
pageVisitUuid: {
type: String,
default: ''
} }
}) })
@ -59,6 +65,11 @@ const sq1ImageVisible = ref(false)
// //
const showVideoPlayer = ref(false) const showVideoPlayer = ref(false)
//
const hasVideoPlayed = ref(false)
//
const hasInteractiveClicked = ref(false)
// //
const sceneHeight = ref(0) const sceneHeight = ref(0)
@ -132,13 +143,23 @@ const parallaxOffset = computed(() => {
// / // /
const toggleMusic = () => { const toggleMusic = () => {
// //
if (!sealCollected.value) { hasInteractiveClicked.value = true
sealCollected.value = true
sq1ImageVisible.value = true //
emit('collect-seal') if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'qianmen',
interaction_type: 'click'
}).catch(err => {
console.log('记录点击交互失败:', err)
})
} }
//
checkSealCollection()
if (!drumPlayer.value) { if (!drumPlayer.value) {
// //
drumPlayer.value = uni.createInnerAudioContext() 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(() => { onMounted(() => {
// //
@ -245,6 +275,25 @@ const closeVideoPlayer = () => {
emit('video-close') 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 preloadLionDanceImages = () => {
const lionImages = [ const lionImages = [
@ -318,7 +367,7 @@ onUnmounted(() => {
<VideoPlayButton :position="{ bottom: '30rpx', right: '11rpx' }" @play="openVideoPlayer" /> <VideoPlayButton :position="{ bottom: '30rpx', right: '11rpx' }" @play="openVideoPlayer" />
<!-- 视频播放器弹窗 --> <!-- 视频播放器弹窗 -->
<VideoPlayerModal :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" /> <VideoPlayerModal :video-url="props.videoUrl" :visible="showVideoPlayer" @close="closeVideoPlayer" @video-played="handleVideoPlayed" />
</section> </section>
</template> </template>

View File

@ -966,6 +966,7 @@ onUnmounted(() => {
:active="activeSceneIndex === 1" :active="activeSceneIndex === 1"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
:video-url="scenes[1].videoUrl" :video-url="scenes[1].videoUrl"
:page-visit-uuid="pageVisitUuid"
@collect-seal="collectSeal(1)" @collect-seal="collectSeal(1)"
@video-open="handleVideoOpen" @video-open="handleVideoOpen"
@video-close="handleVideoClose" @video-close="handleVideoClose"
@ -976,6 +977,7 @@ onUnmounted(() => {
:active="activeSceneIndex === 2" :active="activeSceneIndex === 2"
:scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0" :scroll-position="scrollContainer && scrollContainer.value ? scrollContainer.value.scrollTop : 0"
:video-url="scenes[2].videoUrl" :video-url="scenes[2].videoUrl"
:page-visit-uuid="pageVisitUuid"
@collect-seal="collectSeal(2)" @collect-seal="collectSeal(2)"
@video-open="handleVideoOpen" @video-open="handleVideoOpen"
@video-close="handleVideoClose" @video-close="handleVideoClose"
@ -986,6 +988,7 @@ onUnmounted(() => {
:active="activeSceneIndex === 3" :active="activeSceneIndex === 3"
:scroll-position="scrollContainer?.value?.scrollTop || 0" :scroll-position="scrollContainer?.value?.scrollTop || 0"
:video-url="scenes[3].videoUrl" :video-url="scenes[3].videoUrl"
:page-visit-uuid="pageVisitUuid"
@collect-seal="collectSeal(3)" @collect-seal="collectSeal(3)"
@video-open="handleVideoOpen" @video-open="handleVideoOpen"
@video-close="handleVideoClose" @video-close="handleVideoClose"
@ -997,6 +1000,7 @@ onUnmounted(() => {
:scroll-position="scrollContainer?.value?.scrollTop || 0" :scroll-position="scrollContainer?.value?.scrollTop || 0"
:video-url="scenes[4].videoUrl" :video-url="scenes[4].videoUrl"
:is-music-playing="isMusicPlaying" :is-music-playing="isMusicPlaying"
:page-visit-uuid="pageVisitUuid"
@collect-seal="collectSeal(4)" @collect-seal="collectSeal(4)"
@video-open="handleVideoOpen" @video-open="handleVideoOpen"
@video-close="handleVideoClose" @video-close="handleVideoClose"
@ -1014,6 +1018,7 @@ onUnmounted(() => {
:is-music-playing="isMusicPlaying" :is-music-playing="isMusicPlaying"
:is-video-playing="isVideoPlaying" :is-video-playing="isVideoPlaying"
:is-webview-opening="isWebviewOpening" :is-webview-opening="isWebviewOpening"
:page-visit-uuid="pageVisitUuid"
@collect-seal="collectSeal(5)" @collect-seal="collectSeal(5)"
@height-changed="handleQianmenHeightChanged" @height-changed="handleQianmenHeightChanged"
@video-open="handleVideoOpen" @video-open="handleVideoOpen"

View File

@ -16,7 +16,7 @@ const props = defineProps({
}) })
// //
const emit = defineEmits(['close']) const emit = defineEmits(['close', 'video-played'])
// //
const handleClose = () => { const handleClose = () => {
@ -37,6 +37,11 @@ const handleContentClick = (e) => {
const handleTouchMove = (e) => { const handleTouchMove = (e) => {
e.preventDefault() e.preventDefault()
} }
//
const handleVideoPlay = () => {
emit('video-played')
}
</script> </script>
<template> <template>
@ -48,6 +53,7 @@ const handleTouchMove = (e) => {
controls controls
autoplay autoplay
loop loop
@play="handleVideoPlay"
></video> ></video>
</div> </div>
<!-- 关闭按钮放在视频下方 --> <!-- 关闭按钮放在视频下方 -->

View File

@ -4,6 +4,7 @@ import ImageGalleryModal from './ImageGalleryModal.vue'
import VideoPlayButton from './VideoPlayButton.vue' import VideoPlayButton from './VideoPlayButton.vue'
import VideoPlayerModal from './VideoPlayerModal.vue' import VideoPlayerModal from './VideoPlayerModal.vue'
import ImagePreloader from '@/utils/preload'; import ImagePreloader from '@/utils/preload';
import { recordInteraction } from '../api/api.js'
// //
const props = defineProps({ const props = defineProps({
@ -21,6 +22,11 @@ const props = defineProps({
videoUrl: { videoUrl: {
type: String, type: String,
default: '' default: ''
},
// 访UUID
pageVisitUuid: {
type: String,
default: ''
} }
}) })
@ -32,6 +38,11 @@ const sq3ImageVisible = ref(false)
// //
const showVideoPlayer = ref(false) const showVideoPlayer = ref(false)
//
const hasVideoPlayed = ref(false)
//
const hasInteractiveClicked = ref(false)
// //
const imagesLoaded = ref(false) const imagesLoaded = ref(false)
const isPreloading = ref(false) const isPreloading = ref(false)
@ -91,8 +102,27 @@ const openGallery = (index) => {
currentGalleryIndex.value = index currentGalleryIndex.value = index
galleryVisible.value = true galleryVisible.value = true
// sq3 //
if (!sq3ImageVisible.value) { hasInteractiveClicked.value = true
//
if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'wangfujing',
interaction_type: 'click'
}).catch(err => {
console.log('记录点击交互失败:', err)
})
}
//
checkSealCollection()
}
//
const checkSealCollection = () => {
if (!sq3ImageVisible.value && hasVideoPlayed.value && hasInteractiveClicked.value) {
sq3ImageVisible.value = true sq3ImageVisible.value = true
emit('collect-seal') emit('collect-seal')
} }
@ -115,6 +145,25 @@ const closeVideoPlayer = () => {
emit('video-close') emit('video-close')
} }
//
const handleVideoPlayed = () => {
hasVideoPlayed.value = true
//
if (props.pageVisitUuid) {
recordInteraction({
page_visit_uuid: props.pageVisitUuid,
scene_id: 'wangfujing',
interaction_type: 'video_play'
}).catch(err => {
console.log('记录视频播放交互失败:', err)
})
}
//
checkSealCollection()
}
// //
onMounted(() => { onMounted(() => {
// //
@ -171,6 +220,7 @@ onMounted(() => {
:video-url="props.videoUrl" :video-url="props.videoUrl"
:visible="showVideoPlayer" :visible="showVideoPlayer"
@close="closeVideoPlayer" @close="closeVideoPlayer"
@video-played="handleVideoPlayed"
/> />