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')
+}
@@ -48,6 +53,7 @@ const handleTouchMove = (e) => {
controls
autoplay
loop
+ @play="handleVideoPlay"
>
diff --git a/components/WangfujingScene.vue b/components/WangfujingScene.vue
index 7ab7c3b..d083d52 100644
--- a/components/WangfujingScene.vue
+++ b/components/WangfujingScene.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: ''
}
})
@@ -32,6 +38,11 @@ const sq3ImageVisible = ref(false)
// 视频播放状态
const showVideoPlayer = ref(false)
+// 视频播放完成状态
+const hasVideoPlayed = ref(false)
+// 互动点击完成状态
+const hasInteractiveClicked = ref(false)
+
// 图片加载状态
const imagesLoaded = ref(false)
const isPreloading = ref(false)
@@ -91,8 +102,27 @@ const openGallery = (index) => {
currentGalleryIndex.value = index
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
emit('collect-seal')
}
@@ -115,6 +145,25 @@ const closeVideoPlayer = () => {
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(() => {
// 添加动画类,触发入场动画
@@ -171,6 +220,7 @@ onMounted(() => {
:video-url="props.videoUrl"
:visible="showVideoPlayer"
@close="closeVideoPlayer"
+ @video-played="handleVideoPlayed"
/>