Commit 9214ed3a authored by 张九刚's avatar 张九刚

feat: 在首页和Home组件中添加滚动事件处理和曝光检测功能,优化用户体验

parent 25005662
<template>
<view class="container">
<Home v-if="globalStore.curTabIndex == 0" />
<Home ref="homeRef" v-if="globalStore.curTabIndex == 0" :scroll-top="scrollTop" />
<Brand v-if="globalStore.curTabIndex == 1" />
<Integral v-if="globalStore.curTabIndex == 2" />
<My v-if="globalStore.curTabIndex == 3" />
......@@ -10,7 +10,7 @@
<script setup>
import { ref, getCurrentInstance } from "vue";
import { onLoad, onShareAppMessage, onShareTimeline } from "@dcloudio/uni-app";
import { onLoad, onShareAppMessage, onShareTimeline, onPageScroll } from "@dcloudio/uni-app";
import TabBar from "@/components/TabBar.vue";
import Home from "@/views/Home.vue";
import Brand from "@/views/Brand.vue";
......@@ -21,7 +21,8 @@ import { useGlobalStore } from "@/stores/global.js";
const globalStore = useGlobalStore();
const { proxy } = getCurrentInstance();
const $baseUrl = proxy.$baseUrl;
const homeRef = ref(null);
const scrollTop = ref(0);
const shareOptions = {
0: {
title: "8000万中国妈妈信赖的育儿品牌",
......@@ -72,6 +73,12 @@ onShareTimeline(() => {
const pageType = globalStore.curTabIndex;
return shareOptions[pageType] || shareOptions[0];
});
onPageScroll((e) => {
if (globalStore.curTabIndex === 0) {
scrollTop.value = e.scrollTop;
}
});
</script>
<style lang="scss" scoped>
......
import md from '../md';
class ExposureTracker {
constructor(component) {
this.component = component;
this.exposureMap = new Map(); // 记录每个元素的曝光状态
this.windowHeight = 0;
this.init();
}
init() {
// 获取窗口高度
const systemInfo = uni.getSystemInfoSync();
this.windowHeight = systemInfo.windowHeight;
}
// 添加需要追踪的元素配置
addExposureElement(config) {
const { id, logParams } = config;
if (!this.exposureMap.has(id)) {
this.exposureMap.set(id, {
isExposed: false,
logParams
});
}
}
// 批量添加需要追踪的元素配置
addExposureElements(configs) {
configs.forEach(config => this.addExposureElement(config));
}
// 检查元素是否在可视区域内
isElementInViewport(rect, scrollTop) {
const elementTop = rect.top;
const elementBottom = rect.top + rect.height;
const viewportTop = scrollTop;
const viewportBottom = scrollTop + this.windowHeight;
return (
(elementBottom >= viewportTop && elementBottom <= viewportBottom) ||
(elementTop >= viewportTop && elementTop <= viewportBottom) ||
(elementTop <= viewportTop && elementBottom >= viewportBottom)
);
}
// 检查所有元素的曝光状态
checkExposure(scrollTop) {
const query = uni.createSelectorQuery().in(this.component);
this.exposureMap.forEach((value, id) => {
if (value.isExposed) return; // 如果已经曝光过,则跳过
query.select(`#${id}`)
.boundingClientRect(res => {
if (!res) return;
const isVisible = this.isElementInViewport(res, scrollTop);
if (isVisible && !value.isExposed) {
value.isExposed = true;
// 触发埋点
md.sensorLogTake(value.logParams);
}
});
});
query.exec();
}
// 重置某个元素的曝光状态
resetExposure(id) {
const element = this.exposureMap.get(id);
if (element) {
element.isExposed = false;
}
}
// 重置所有元素的曝光状态
resetAllExposure() {
this.exposureMap.forEach(value => {
value.isExposed = false;
});
}
}
export default ExposureTracker;
\ No newline at end of file
<template>
<view class="home-container">
<view class="home-container" @scroll="onPageScroll">
<view class="content">
<swiper class="swiper banner" circular :indicator-dots="indicatorDots" :autoplay="autoplay"
<swiper id="firstScreen" class="swiper banner" circular :indicator-dots="indicatorDots" :autoplay="autoplay"
:interval="interval" :duration="duration" :indicator-color="indicatorColor"
:indicator-active-color="indicatoractiveColor">
<swiper-item v-for="(item, index) in swiperList" :key="item.url">
......@@ -67,7 +67,7 @@
</view>
</view>
</view>
<view class="contentbox">
<view class="contentbox" id="secondScreen">
<image class="contentbg" :src="$baseUrl + 'homepage/contentbg.png'" alt="" />
<image class="contentitem" :key="index" :style="contentItem._style"
v-for="(contentItem, index) in contentImgList" :src="$baseUrl + contentItem.bgUrl" :data-log="{
......@@ -76,7 +76,7 @@
buttonName: `品牌介绍${index + 1}`
}" @tap="jumpLink(contentItem.link, contentItem.videoUrl, index, $event)"></image>
</view>
<view class="channelbox">
<view class="channelbox" id="thirdScreen">
<text class="maintitle">有声频道</text>
<text class="subtitle">用声音传递爱与智慧,守护宝贝成长的每一步</text>
<view class="listbox">
......@@ -138,7 +138,7 @@
更多星妈会权威专家服务团 <view class="desc1"> 点击查看 > </view>
</view>
</view>
<view class="bottomlink">
<view id="fourthScreen" class="bottomlink">
<image class="bottombg" :src="$baseUrl + 'homepage/bottombg.png'"></image>
<view class="box">
<image class="icon" v-for="(icon, index) in bottomLinkList" :key="index" :data-log="{
......@@ -179,7 +179,6 @@
<script>
import { jump, JumpType } from '../utils';
import { homeObj } from '../mock/home';
import { useHomeStore } from '../stores/home';
......@@ -187,9 +186,55 @@ import { fetchHomeJSON } from '../api/home';
import { useUserStore } from '../stores/user';
import RegisterLayer from "../components/RegisterLayer.vue";
import md from '../md';
import ExposureTracker from '../utils/exposure';
// const homeStore = useHomeStore();
const userStore = useUserStore();
// 定义需要曝光检测的元素配置
const EXPOSURE_CONFIGS = [
{
id: 'firstScreen',
logParams: {
xcxPage: '首页-首屏页面浏览',
pageName: '首页-首屏'
}
},
// 可以添加更多需要曝光检测的元素配置
{
id: 'secondScreen',
logParams: {
xcxPage: '首页-二屏页面浏览',
pageName: '首页-二屏'
}
},
{
id: 'thirdScreen',
logParams: {
xcxPage: '首页-三屏页面浏览',
pageName: '首页-三屏'
}
},
{
id: 'fourthScreen',
logParams: {
xcxPage: '首页-四屏页面浏览',
pageName: '首页-四屏'
}
}
];
export default {
beforeDestroy() {
if (this.exposureTracker) {
this.exposureTracker.resetAllExposure();
}
},
deactivated() {
if (this.exposureTracker) {
this.exposureTracker.resetAllExposure();
}
},
data() {
return {
popType: 'bottom',
......@@ -219,7 +264,11 @@ export default {
voiceStory: {},
suggest: {},
videoHeight: '56vw',
qrNameList:['公众号','企业微信','视频号','小红书']
qrNameList:['公众号','企业微信','视频号','小红书'],
windowHeight: 0,
isFirstScreenExposed: false,
scrollTimer: null,
exposureTracker: null,
}
},
components: {
......@@ -230,30 +279,49 @@ export default {
return useHomeStore();
}
},
mounted() {
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
this.statusBarHeight = menuButtonInfo.top;
this.isClickPhoneAuth = false;
props: {
scrollTop: {
type: Number,
default: 0
}
},
watch: {
homeStore: {
handler(newVal) {
this.showRegisterLayer = this.isClickPhoneAuth && newVal.isLogin && !newVal.babyExistence;
console.log('newVal.homeInfo', newVal.homeInfo);
if (newVal.homeInfo !== null) {
this.initHomeInfo();
}
},
deep: true,
immediate: true
},
scrollTop: {
handler(newVal) {
this.checkExposure(newVal);
}
}
},
mounted() {
const menuButtonInfo = wx.getMenuButtonBoundingClientRect();
this.statusBarHeight = menuButtonInfo.top;
this.isClickPhoneAuth = false;
// 获取窗口高度
const systemInfo = uni.getSystemInfoSync();
this.windowHeight = systemInfo.windowHeight;
// 初始化曝光检测工具
this.exposureTracker = new ExposureTracker(this);
this.exposureTracker.addExposureElements(EXPOSURE_CONFIGS);
// 初始检查曝光
this.$nextTick(() => {
this.checkExposure(this.scrollTop);
});
},
methods: {
async initHomeInfo() {
const { data } = await fetchHomeJSON();
if (data) {
this.swiperList = data.swiperList;
......@@ -269,16 +337,12 @@ export default {
this.voiceStory = data.voiceStory;
this.suggest = data.suggest;
if (this.homeStore.isLogin) {
this.vipCardList[0] = data.vipConfigList.find(item => item.grade === this.homeStore.homeInfo?.grade);
this.vipCardList[0].level = this.homeStore.homeInfo?.gradeName;
} else {
this.vipCardList[0] = data.vipConfigList[0];
}
}
},
changeIndicatorDots(e) {
this.indicatorDots = !this.indicatorDots
......@@ -377,7 +441,6 @@ export default {
if (_index === 3) {
this.qrObj = null;
} else {
_index = Math.max(0, Math.min(_index, this.qrInfoList.length - 1));
this.qrObj = this.qrInfoList[_index];
......@@ -386,7 +449,6 @@ export default {
this.$refs.popup.open(this.popType);
},
closePop() {
this.$refs.popup.close();
},
goSearchHandler(e) {
......@@ -457,6 +519,16 @@ export default {
const { memberId, mobile, openId, unionId } = this.homeStore.homeInfo;
const customerUrl = `https://intelcc-user.icsoc.net/?channelKey=45839e0505554f8c8aea3c7b6259b049&init=1&crmld=${memberId}&mobile=${mobile}&openId=${openId}&unionId=${unionId}`;
jump({ type: JumpType.H5, url: customerUrl });
},
checkExposure(scrollTop) {
if (this.exposureTracker) {
this.exposureTracker.checkExposure(scrollTop);
}
},
resetExposure() {
if (this.exposureTracker) {
this.exposureTracker.resetAllExposure();
}
}
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment