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']
				}
			]
		}
	}
};

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐