export {
initializeBuriedPoints,
isApp,
sendEventPoint,
sendPvPoint,
getUrlQuery,
getUuid
}
//初始化友盟埋点方法
const initializeBuriedPoints = () => {
(function (w, d, s, q, i) {
w[q] = w[q] || [];
var f = d.getElementsByTagName(s)[0], j = d.createElement(s);
j.async = true;
j.id = 'beacon-aplus';
j.src = 'https://d.alicdn.com/alilog/mlog/aplus/' + i + '.js';
f.parentNode.insertBefore(j, f);
})(window, document, 'script', 'aplus_queue', '203467608');
if (!window.aplus_queue) {
setTimeout(() => {
console.log('---埋点准备---loading---')
initAplusQueue()
}, 500)
return
} else {
console.log('---埋点准备---succcess---')
}
//集成应用的appKey
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['appKey', '6538768f58a9eb5b0af56cbf'],
})
/************************以下内容为可选配置内容****************************/
//sdk提供手动pv发送机制,启用手动pv(即关闭自动pv),需设置aplus-waiting=MAN;
//注意:由于单页面路由改变时不会刷新页面,无法自动发送pv,所以对于单页应用,强烈建议您关闭自动PV, 手动控制PV事件
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['aplus-waiting', 'MAN'],
})
//是否开启调试模式 可以依据自己的条件看是否开启
aplus_queue.push({
action: 'aplus.setMetaInfo',
arguments: ['DEBUG', true],
})
// sdk 初始化是否完成
window.isInitAplusQueue = true
}
// 判断环境
// 判断是否运行在app环境里,此处可排除一些不需要上报日志的环境
const isApp = () => {
return new Promise((resolve) => {
if (window.location.host.includes('http:xxx.com') {
resolve(false)
} else {
resolve(true)
}
})
}
// 手动事件埋点
// eventParams.params参数中 event_name_cn、event_name_en 必填
const sendEventPoint = async (eventParams) => {
console.log('手动事件埋点:',eventParams)
// if (!await isApp()) return
const { eventId, params } = eventParams || {}
// eslint-disable-next-line
const { aplus_queue, isInitAplusQueue } = window
if (!isInitAplusQueue) {
console.log('----埋点未初始化---')
setTimeout(() => sendEventPoint(eventParams), 300)
return
}
// eslint-disable-next-line
aplus_queue.push({
action: 'aplus.record',
arguments: [
eventId,
'CLK',
{
current_path: window.location.href, // 页面路径
...params,
},
],
})
}
// pv埋点
const sendPvPoint = async (eventParams) => {
console.log('pv埋点:',eventParams)
try {
// if (!await isApp()) return
const { aplus_queue, isInitAplusQueue } = window
if (!isInitAplusQueue) {
console.log('----埋点未初始化-pv--')
setTimeout(() => sendPvPoint(eventParams), 300)
return
}
console.log('----埋点-pv-send-')
aplus_queue?.push({
action: 'aplus.sendPV',
arguments: [
{
is_auto: false,
},
{
current_path: window.location.href, // 页面路径
event_name_cn: '浏览',
event_name_en: 'pv',
event_type: 'browse',
...(eventParams || {}),
},
],
})
} catch (error) {
console.error(error)
}
}
const getUrlQuery = () => {
const kvObj = {}
if (window.location.search) {
const searchStr = window.location.search.substring(1)
const searchList = searchStr.split('&')
searchList.forEach((item) => {
const kv = item.split('=')
kvObj[kv[0]] = kv[1]
})
}
return kvObj
}
const getUuid = () => {
let d = new Date().getTime()
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
d += performance.now() // 使用性能计时器的值增加UUID的唯一性
}
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = (d + Math.random() * 16) % 16 | 0
d = Math.floor(d / 16)
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16)
})
return uuid
}
配合浏览器观察者PerformanceObserver
//性能检测
export const launchPerformance = function () {
const supportedEntryTypes = PerformanceObserver.supportedEntryTypes;
var regex = /(localhost|test|dev)/i; // 正则表达式匹配 localhost、test 或 dev
var filterateUrl = !regex.test(window.location.hostname);
// console.log('filterateUrl:',filterateUrl)
// 创建一个PerformanceObserver对象
const paintObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
// 遍历所有 paint 类型的性能条目
for (const entry of entries) {
//首次绘制,绘制Body
if (entry.name === 'first-paint') {
let t = (entry.startTime / 1000).toFixed(2)
console.log('白屏 时间:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 first-paint')
} else {
let str = `time=${t}&msg=first-paint`
}
}
//首次有内容的绘制,第一个dom元素绘制完成
if (entry.name === 'first-contentful-paint') {
let t = (entry.startTime / 1000).toFixed(2)
// console.log('首次有内容的绘制 时间:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 first-contentful-paint')
} else {
let str = `time=${t}&msg=first-contentful-paint`
}
}
}
});
// 开始观察 paint 类型的性能条目
// if (supportedEntryTypes.includes('paint')) {
try {
paintObserver.observe({ entryTypes: ['paint'] });
} catch (error) {
console.log(' paintObserver.observe({ entryTypes: [paint] }); 不支持')
}
// }
// 网页需要多长时间才能提供完整交互功能 长任务
const longtaskobserver = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
let t = (entry.startTime / 1000).toFixed(2)
// console.log('完整交互功能 时间:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 longtask')
} else {
let str = `time=${t}&msg=longtask`
}
}
});
try {
longtaskobserver.observe({ entryTypes: ['longtask'] });
} catch (error) {
console.log('longtaskobserver.observe({ entryTypes: [longtask] }) 不支持')
}
// 计算一些关键的性能指标 TTI
window.addEventListener('load', (event) => {
//可交互时间是指网页需要多长时间才能提供完整交互功能
let timing = performance.getEntriesByType('navigation')[0];
let diff = timing.domInteractive - timing.fetchStart;
let t = (diff / 1000).toFixed(2)
// console.log('网页需要多长时间才能提供完整交互功能: 时间:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 tti' )
} else {
let str = `time=${t}&msg=tti`
}
})
//“累积布局偏移”旨在衡量可见元素在视口内的移动情况 正常是小于0.1
let cls = 0;
var clsVal = null
const entryHandler = (list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
cls += entry.value;
}
}
clsVal && clearTimeout(clsVal)
clsVal = setTimeout(() => {
let t = (cls / 1000).toFixed(0)
// console.log('累计偏移量 时间:', t, '毫秒');
if (!filterateUrl) {
// console.log('本地调试不上报 cls')
} else {
let str = `time=${t}&msg=cls`
}
}, 2000)
};
const clsObserver = new PerformanceObserver(entryHandler);
try {
clsObserver.observe({ type: 'layout-shift', buffered: true });
} catch (error) {
console.log(error)
console.log('clsObserver.observe({ type: layout-shift, buffered: true });; 不支持')
}
// 首次内容渲染和可交互时间之间的所有时间段的总和
const tblObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
let totalBlockingTime = 0;
for (const entry of entries) {
if (entry.entryType === 'longtask') {
totalBlockingTime += entry.duration;
}
}
// console.log('首次内容渲染和可交互时间之间的所有时间段的总和:', totalBlockingTime, '毫秒');
if (!filterateUrl) {
// console.log('本地调试不上报 totalBlockingTime')
} else {
let str = `time=${t}&msg=totalBlockingTime`
}
});
try {
tblObserver.observe({ type: 'longtask', buffered: true });
} catch (error) {
console.log(error)
console.log('tblObserver.observe({ type: longtask, buffered: true }) 不支持')
}
//标记了渲染出最大文本或图片的时间
var lcpTime = 0
var lcpTimeVal = null
window.addEventListener('DOMContentLoaded', function () {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries();
const lcpEntry = entries[entries.length - 1];
lcpTime += lcpEntry.renderTime || lcpEntry.loadTime;
// 如果还没有显示过结果,则输出
lcpTimeVal && clearTimeout(lcpTimeVal)
lcpTimeVal = setTimeout(() => {
let t = (lcpTime / 1000).toFixed(1)
// console.log('最大文本或图片的时间:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 largest-contentful-paint')
} else {
let str = `time=${t}&msg=largest-contentful-paint`
}
}, 2000)
});
try {
observer.observe({ type: 'largest-contentful-paint', buffered: true })
} catch (error) {
console.log('observer.observe({ type: largest-contentful-paint, buffered: true }) 不支持')
}
// }
});
//通常情况下认为没有请求了就代表页面完全加载完毕可以上报数据了
const ob = new PerformanceObserver((list) => {
if (list.getEntries().length) {
const timer = setTimeout(() => {
// 如果在一定时间内没有再获取到资源请求的条目
// 则发起上传程序
//这里写上报数据
console.log('没有请求了')
}, 500); // 根据应用情况设置足够长的时间
clearTimeout(timer);
}
});
ob.observe({ entryTypes: ['resource'] })
//速度指数表明了网页内容的可见填充速度
// function calculateSpeedIndex() {
// 测量页面开始渲染的时间
performance.mark('startRender');
// 等待页面完全加载
window.addEventListener('load', () => {
// 测量页面渲染完成的时间
performance.mark('endRender');
// 计算Speed Index
performance.measure('renderTime', 'startRender', 'endRender');
const renderTime = performance.getEntriesByName('renderTime')[0].duration;
const speedIndex = renderTime / 2; // 根据您的具体需求进行调整
let t = (speedIndex / 1000).toFixed(2)
// console.log('网页内容的可见填充速度:', t, '秒');
if (!filterateUrl) {
// console.log('本地调试不上报 speedIndex')
} else {
let str = `time=${t}&msg=speedIndex`
}
});
//页面卸载的时候上报
window.addEventListener('unload', () => {
//不要用异步请求去上报,异步上报会丢失
});
}
手动发送埋点消息
import {sendEventPoint} from "./index.js" //这里就是上面的js文件导出的方法
<button onClick={handleClickBtn}>发送埋点</button>
const handleClickBtn = ()=>{
// ...正常业务逻辑
//发送埋点
sendEvent("indexDragStart", {
"event_name_cn": '埋点的事件名称中文', //必填
"event_name_en": "埋点的事件名称英文", //必填
"a":"自定义字段的自定义内容",
"b":"自定义字段的自定义内容",
...
})
}
//发送友盟埋点
const sendEvent = (eventId, params) => {
let eventParams = {
"eventId": eventId,
"params": {
"userId": "用户id",
"curPage": "页面路径",
"optime": dayjs().format('YYYY-MM-DD HH:mm'),
...params
}
}
sendEventPoint(eventParams)
}