uniapp 埋点打开页面,关闭页面,列表元素曝光,按钮点击上报,批量上传
·
App.vue
onShow: function() {
uni.$emit('pageShow');
}
onHide: function(){
// 触发全局自定义事件
uni.$emit('pageHide');
},
PerformanceMonitor.js 文件
/* import {
batchCreate
} from "@/api/trackingpoint/trackingpoint.js"; */
class PerformanceMonitor {
constructor() {
this.open = true; //是否上报埋点
this.appId = 2; //埋点系统appid
this.userId = null; //本次请求的用户ID
this.performanceQueue = [];
this.maxQueueSize = 10;
this.reportInterval = 30000; // 30秒上报一次
this.initBatchReport();
}
// 批量上报初始化
initBatchReport() {
setInterval(() => {
if (this.performanceQueue.length > 0) {
this.batchReport();
}
}, this.reportInterval);
}
/**
* 带性能监控的请求包装器
* @param {Function} apiFn - 要监控的API函数
* @param {string} apiName - API名称,用于上报标识
* @param {...any} args - 传递给API函数的参数
* @returns {Promise} 返回原始API的Promise
*/
monitorApiPerformance(apiFn, args, biz) {
return new Promise((resolve, reject) => {
const startTime = Date.now();
// 调用原始API
apiFn(...args)
.then(response => {
const endTime = Date.now();
const duration = endTime - startTime;
// 记录耗时并上报
this.record({
...biz,
success: true,
time: duration
});
// 返回原始数据
resolve(response);
})
.catch(error => {
const endTime = Date.now();
const duration = endTime - startTime;
// 记录失败请求的耗时
this.record({
...biz,
success: false,
time: duration,
error: error
});
// 抛出原始错误
reject(error);
});
});
}
// 记录性能数据
record(log) {
var parameter2json = {...log,eventParameterJson:JSON.stringify(log.eventParameterJson)};
this.performanceQueue.push({
...parameter2json,
appId: this.appId,
userId: uni.getStorageSync('USERID')
});
// 队列达到上限时立即上报
if (this.performanceQueue.length >= this.maxQueueSize) {
this.batchReport();
}
}
// 批量上报
batchReport() {
if (this.open == false) {
return;
}
const batchData = [...this.performanceQueue];
this.performanceQueue = [];
/* console.log(33333333333333);
console.log(batchData); */
/* batchCreate(batchData).then(res => {
console.log("上报成功");
}).catch(err => {
console.log("失败", err);
}) */
uni.request({
url: '/trackingpointApi/app-api/trackingpoint/app-collect/batchCreate',
method: 'POST',
data: batchData,
header: {'terminal':30,'tenant-id':1},
}).then(res => {
console.log("上报成功");
}).catch(error => {
console.error('批量性能数据上报失败:', error);
});
/* uni.request({
url: 'https://your-monitor-server.com/api/performance/batch',
method: 'POST',
data: { records: batchData }
}).catch(error => {
console.error('批量性能数据上报失败:', error);
// 上报失败,重新放回队列(可选去重逻辑)
this.performanceQueue = [...this.performanceQueue, ...batchData];
}); */
}
}
// 创建全局监控实例
const performanceMonitor = new PerformanceMonitor();
export default performanceMonitor;
列表元素曝光统计组件 exposureTracker.vue
<template>
<view class="exposure-tracker">
<!-- 插槽用于传入列表内容 -->
<slot></slot>
</view>
</template>
<script>
export default {
name: 'ExposureTracker',
props: {
// 观察的类名选择器
itemSelector: {
type: String,
default: '.exposure-item'
},
// 观察的类名选择器
containerSelector: {
type: String,
default: '.container'
},
// 曝光阈值
threshold: {
type: Number,
default: 0.5
},
// 上报策略配置
reportStrategy: {
type: Object,
default: () => ({
minExposureTime: 1000, // 最小曝光时间(小于此时间不计入)
periodicReport: 5000, // 周期性上报间隔
maxExposureTime: 30000 // 最大单次曝光时间(超过按此值计算)
})
},
//上报配置
report: {
type: Object,
default: () => ({
pagePath: '', //事件发生的页面路径
event: '', //事件
eventParameter: {} //发生事件的参数
})
}
},
data() {
return {
observer: null,
exposureData: new Map(), // 存储 { startTime, lastReportTime, timer, totalDuration }
isPageActive: true
}
},
mounted() {
this.initObserver();
this.bindPageEvents();
},
beforeDestroy() {
this.cleanup();
},
methods: {
initObserver() {
this.$nextTick(() => {
try {
this.observer = uni.createIntersectionObserver(this, {
thresholds: [this.threshold], //设置为0,或者设置多个观察点 0, 0.1, 0.5, 0.9, 1
observeAll: true
});
//this.observer.relativeToViewport().observe(this.itemSelector, (res) => {
this.observer.relativeTo(this.containerSelector).observe(this.itemSelector, (res) => {
const id = res.dataset.id;
if (id == null || id.length == 0){
return;
}
const joinId = id.join("-");
const isIntersecting = res.intersectionRatio > 0;
if (isIntersecting) {
this.handleElementEnter(joinId);
} else {
this.handleElementLeave(joinId);
}
});
} catch (error) {
console.error('曝光观察器初始化失败:', error);
}
});
},
handleElementEnter(id) {
if (!this.exposureData.has(id)) {
const startTime = Date.now();
// 创建周期性上报计时器
const timer = setInterval(() => {
if (this.isPageActive) {
this.reportPeriodicExposure(id);
}
}, this.reportStrategy.periodicReport);
this.exposureData.set(id, {
startTime,
lastReportTime: startTime,
timer,
totalDuration: 0
});
//console.log(`元素 ${id} 开始曝光`);
this.$emit('exposure-start', {
id,
startTime
});
}
},
handleElementLeave(id) {
const data = this.exposureData.get(id);
if (data) {
this.finalizeExposure(id, data);
}
},
reportPeriodicExposure(id) {
const data = this.exposureData.get(id);
if (data && this.isPageActive) {
const currentTime = Date.now();
const periodDuration = currentTime - data.lastReportTime;
data.totalDuration += periodDuration;
data.lastReportTime = currentTime;
//console.log(`元素 ${id} 周期性上报,当前累计: ${data.totalDuration}ms`);
this.$emit('exposure-periodic', {
id,
currentDuration: data.totalDuration,
startTime: data.startTime
});
}
},
finalizeExposure(id, data) {
clearInterval(data.timer);
const currentTime = Date.now();
const currentDuration = currentTime - data.lastReportTime;
const totalDuration = data.totalDuration + currentDuration;
// 应用上报策略
if (totalDuration < this.reportStrategy.minExposureTime) {
//console.log(`元素 ${id} 曝光时间过短,忽略上报`);
this.$emit('exposure-ignored', {
id,
duration: totalDuration,
reason: 'below_min_threshold'
});
} else {
const finalDuration = Math.min(totalDuration, this.reportStrategy.maxExposureTime);
//console.log(`元素 ${id} 最终曝光时长: ${finalDuration}ms`);
this.$emit('exposure-end', {
id,
startTime: data.startTime,
endTime: currentTime,
duration: finalDuration,
originalDuration: totalDuration
});
console.log(`添加曝光记录`);
this.$performanceMonitor.record({pagePath:this.report.pagePath,pageName:this.report.pageName,event:this.report.event,eventParameterJson:{...this.report.eventParameter,bizId:id.split("-"),exposureDuration:totalDuration}});//示例保存曝光记录
}
this.exposureData.delete(id);
//console.log(99999);
//console.log(this.report);
},
// 绑定页面事件
bindPageEvents() {
// 使用全局事件监听页面状态
uni.$on('pageHide', () => {
this.isPageActive = false;
this.pauseAllTimers();
});
uni.$on('pageShow', () => {
this.isPageActive = true;
this.resumeAllTimers();
});
},
// 页面隐藏时暂停所有计时器
pauseAllTimers() {
console.log("页面隐藏,暂停计时");
const currentTime = Date.now();
this.exposureData.forEach((data) => {
clearInterval(data.timer);
// 更新总时长(从最后一次上报到暂停的时间)
data.totalDuration += (currentTime - data.lastReportTime);
});
},
// 页面显示时恢复所有计时器
resumeAllTimers() {
console.log("页面显示,恢复计时");
const currentTime = Date.now();
this.exposureData.forEach((data, id) => {
data.lastReportTime = currentTime;
// 重新创建计时器
data.timer = setInterval(() => {
this.reportPeriodicExposure(id);
}, this.reportStrategy.periodicReport);
});
},
// 页面卸载时的最终上报
finalReportAll() {
this.exposureData.forEach((data, id) => {
this.finalizeExposure(id, data);
});
},
// 清理资源
cleanup() {
this.finalReportAll();
if (this.observer) {
this.observer.disconnect();
}
// 移除事件监听
uni.$off('pageHide');
uni.$off('pageShow');
},
// 外部调用:手动触发曝光结束
manualTriggerExposureEnd(id) {
const data = this.exposureData.get(id);
if (data) {
this.finalizeExposure(id, data);
}
},
// 外部调用:获取当前正在曝光的元素
getCurrentExposingItems() {
return Array.from(this.exposureData.keys());
}
}
}
</script>
<style scoped>
.exposure-tracker {
width: 100%;
}
</style>
测试页面exposureTrackerTest.vue
<template>
<view>
<button @click="testClick">点击埋点</button>
<exposure-tracker container-selector=".container" item-selector=".list-item" :threshold="0" :report-strategy="{
minExposureTime: 1000,
periodicReport: 5000,
maxExposureTime: 30000
}" :report="{pagePath:'pages/daygoodness/index',pageName:'xxx页面',event:'stay',eventParameter:{
'bizName': '停留xxx2',
'bizTable': ['table1','table2'],
'bizId': []
}}" @exposure-start="handleExposureStart" @exposure-end="handleExposureEnd"
@exposure-periodic="handleExposurePeriodic" @exposure-ignored="handleExposureIgnored">
<scroll-view scroll-y style="height: 600px;background-color: aqua" class="container">
<view v-for="item in list" :key="item.id" class="list-item" :data-id="[item.id,'虚拟id2']" style="height: 200px;">
{{ item.text }}
</view>
</scroll-view>
</exposure-tracker>
</view>
</template>
<script>
import ExposureTracker from '@/components/exposureTracker/exposureTracker.vue';
import {
trackingpointConfig
} from '@/utils/trackingpoint';
import {
getUserInfo
} from "@/api/business/my.js"
export default {
components: {
ExposureTracker
},
data() {
return {
list: [
{
id: 1,
text: "11111"
},
{
id: 2,
text: "22222"
},
{
id: 3,
text: "33333"
},
{
id: 4,
text: "44444"
},
{
id: 5,
text: "55555"
},
{
id: 6,
text: "66666"
},
{
id: 7,
text: "77777"
},
{
id: 8,
text: "88888"
},
{
id: 9,
text: "99999"
},
{
id: 10,
text: "10101010"
},
{
id: 11,
text: "11111111111111"
},
{
id: 12,
text: "12121212"
},
{
id: 13,
text: "131313131313"
},
{
id: 14,
text: "141414141414"
},
{
id: 15,
text: "151515151515"
},
{
id: 16,
text: "1616161616"
},
{
id: 17,
text: "171717"
},
{
id: 18,
text: "181818"
},
{
id: 19,
text: "19191919"
},
{
id: 20,
text: "20202020"
},
{
id: 21,
text: "21212121"
},
{
id: 22,
text: "22222222"
},
{
id: 23,
text: "23232323"
},
{
id: 24,
text: "24242424"
},
{
id: 25,
text: "25252525"
},
{
id: 26,
text: "2626262626"
},
{
id: 27,
text: "2727272727"
},
{
id: 28,
text: "2828282828"
},
{
id: 29,
text: "29292929"
},
{
id: 30,
text: "3030303030"
},
{
id: 31,
text: "3131313131"
},
{
id: 32,
text: "3232323232"
},
{
id: 33,
text: "3333333333"
},
{
id: 34,
text: "343434343434"
},
{
id: 35,
text: "353535353535"
},
]
}
},
onLoad(option) {
//页面加载成功,上报埋点
this.$performanceMonitor.record({pagePath:'页面路径',pageName:'页面名称',event:'open',eventParameterJson:{...option}});//页面加载成功记录埋点
},
onUnload(){
this.$performanceMonitor.record({pagePath:'页面路径',pageName:'页面名称',event:'close',eventParameterJson:{}});//页面卸载成功记录埋点
},
methods: {
handleExposureStart(event) {
//console.log('开始曝光:', event.id);
// 记录开始曝光
},
handleExposureEnd(event) {
//console.log(`曝光结束: ${event.id}, 时长: ${event.duration}ms`);
// 上报到服务器
},
handleExposurePeriodic(event) {
//console.log(`周期性上报: ${event.id}, 当前时长: ${event.currentDuration}ms`);
// 中间状态处理
},
handleExposureIgnored(event) {
//console.log(`曝光被忽略: ${event.id}, 原因: ${event.reason}`);
},
testClick(){
this.$performanceMonitor.monitorApiPerformance(getUserInfo,[{aaa:999}],{pagePath:'页面路径',pageName:'页面名称',event:'click',eventParameterJson:{aaa:999}}).then(res => {
console.log('实例,已经拿到响应数据');
console.log(res);
})
}
}
}
</script>
<style>
</style>
记录页面时间的参数格式,与代码调用保持一致,用于数据存储后的分析 trackingpoint.js
const trackingpointConfig = {
'pages/daygoodness/index': {
'open': {
'eventParameter': [{
'bizName': '打开页面xxx1',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
},
{
'bizName': '打开页面xxx1',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
}
]
},
'close': {
'eventParameter': [{
'bizName': '关闭页面xxx1',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
},
{
'bizName': '关闭页面xxx1',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
}
]
},
'click': {
'eventParameter': [{
'bizName': '点击xxx1',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
},
{
'bizName': '点击xxx2',
'bizTable': ['t1','t2'],
'apiParameter':[
{
'url':'url1',
'parameter':[]
},
{
'url':'url2',
'parameter':[]
}
]
}
]
},
'stay': {
'eventParameter': [{
'bizName': '停留xxx1',
'bizTable': ['table1','table2'],
'bizId': ['id1','id2']
},
{
'bizName': '停留xxx1',
'bizTable': ['table1','table2'],
'bizId': ['id1','id2']
}
]
}
}
};
更多推荐



所有评论(0)