Browse Source

feate:新增页面失活事件上报,页面初始化监听

luoy 4 weeks ago
parent
commit
c13060e4e9
1 changed files with 87 additions and 29 deletions
  1. 87 29
      buriedPiont.js

+ 87 - 29
buriedPiont.js

@@ -9,43 +9,48 @@ class Tracker {
     this.blockCookieTimeout = options.blockCookieTimeout || 180; // 默认封锁cookie有效期180分钟
     this.myEventSend = options.myEventSend || (() => {}); // 是否开启自定义事件上报
     this.beforeDestroy = options.beforeDestroy || (() => {}); // 销毁前的回调函数
+    this.crashTime =  options.crashTime || 3; // 闪退阈值
+    this.idleTimeout = options.idleTimeout || 300000; // 默认 5 分钟无操作视为离开
   }
 
-init() {
-  const visitCookieName = 'userVisitCount';
-  const blockCookieName = 'userBlocked';
-  const isBlocked = getCookie(blockCookieName);
-  const doneFN = () => {
-    this.startTime = Date.now();
-    this.bindEvents();
-    this.startHeartbeat();
-  }
-  if (isBlocked) {
-    // restoreBlock();
-    return;
-  }
-
-  let visitCount = getCookie(visitCookieName);
-  if (visitCount) {
-    visitCount = parseInt(visitCount, 10);
-    if (visitCount >= this.maxVisitCount) {
-      setCookie(blockCookieName, 'true', this.blockCookieTimeout); // 封锁用户 180 分钟
-      deleteCookie(visitCookieName);
+  init() {
+    const visitCookieName = 'userVisitCount';
+    const blockCookieName = 'userBlocked';
+    const isBlocked = getCookie(blockCookieName);
+    const doneFN = () => {
+      this.startTime = Date.now();
+      this.bindEvents();
+      this.startHeartbeat();
+    }
+    if (isBlocked) {
       // restoreBlock();
+      return;
+    }
+
+    let visitCount = getCookie(visitCookieName);
+    if (visitCount) {
+      visitCount = parseInt(visitCount, 10);
+      if (visitCount >= this.maxVisitCount) {
+        setCookie(blockCookieName, 'true', this.blockCookieTimeout); // 封锁用户 180 分钟
+        deleteCookie(visitCookieName);
+        // restoreBlock();
+      } else {
+        visitCount += 1;
+        setCookie(visitCookieName, visitCount, this.visitCookieTimeout); // 每次访问 +1,有效期 1 分钟
+        doneFN();
+      }
     } else {
-      visitCount += 1;
-      setCookie(visitCookieName, visitCount, this.visitCookieTimeout); // 每次访问 +1,有效期 1 分钟
+      setCookie(visitCookieName, '1', this.visitCookieTimeout); // 首次访问,设置访问次数为 1
       doneFN();
     }
-  } else {
-    setCookie(visitCookieName, '1', this.visitCookieTimeout); // 首次访问,设置访问次数为 1
-    doneFN();
   }
-}
 
   bindEvents() {
     window.addEventListener('beforeunload', this.handleBeforeUnload);
     window.addEventListener('click', this.handleClickEvent);
+    document.addEventListener('visibilitychange', this.handleVisibilityChange);
+    window.addEventListener('mousemove', this.resetIdleTimer);
+    window.addEventListener('keydown', this.resetIdleTimer);
     this.myEventSend(this.sendData.bind(this)); // 绑定自定义事件上报
   }
 
@@ -58,10 +63,33 @@ init() {
     }, this.heartbeatInterval);
   }
 
+  // 页面初始化调用,手动在不同框架的生命周期中调用
+  markContentLoaded() {
+    this.sendData({
+      eventType: 'content_loaded',
+      loadTime: Math.round((Date.now() - this.startTime) / 1000),
+    });
+  }
+
+  // 用户无操作时发送不活跃事件
+  resetIdleTimer() {
+    if (this.idleTimer) {
+      clearTimeout(this.idleTimer);
+    }
+    this.idleTimer = setTimeout(() => {
+      this.sendData({
+        eventType: 'user_inactive',
+        duration: Math.round((Date.now() - this.startTime) / 1000),
+      });
+    }, this.idleTimeout);
+  }
+
   handleBeforeUnload =  (e) => {
+    const duration = Math.round((Date.now() - this.startTime) / 1000);
     const data = {
       eventType: 'page_close',
-      duration: Math.round((Date.now() - this.startTime) / 1000),
+      duration,
+      exitType: duration > this.crashTime ? 'crash' : 'normal',
     };
     const payload = JSON.stringify(data);
     if (navigator.sendBeacon) {
@@ -85,14 +113,44 @@ init() {
     }
   };
 
+  handleVisibilityChange = () => {
+    if (document.hidden) {
+      // 用户切换到其他 tab
+      this.sendData({ eventType: 'tab_inactive' });
+    } else {
+      // 用户回到当前 tab
+      this.sendData({ eventType: 'tab_active' });
+    }
+  };
+  queueIdleTask(task, timeout = 1000) {
+    if (typeof requestIdleCallback === 'function') {
+      requestIdleCallback(() => {
+        task();
+      }, { timeout });
+    } else {
+      // 兜底方案:使用 setTimeout 模拟
+      setTimeout(task, 0);
+    }
+  }
   sendData(data, headers = {}) {
-    console.log('上报埋点数据:', data);
-    fetch(`${this.baseUrl}`, { method: 'POST', headers: { 'Content-Type': 'application/json' ,...headers}, body: JSON.stringify(data) });
+    this.queueIdleTask(() => {
+      console.log('上报埋点数据:', data);
+      fetch(`${this.baseUrl}`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json', ...headers },
+        body: JSON.stringify(data)
+      }).catch(err => {
+        console.error('埋点失败:', err);
+      });
+    });
   }
 
   destroy() {
     window.removeEventListener('beforeunload', this.handleBeforeUnload);
     window.removeEventListener('click', this.handleClickEvent);
+    document.removeEventListener('visibilitychange', this.handleVisibilityChange);
+    window.removeEventListener('mousemove', this.resetIdleTimer);
+    window.removeEventListener('keydown', this.resetIdleTimer);
     this.beforeDestroy();
     if (this.timer) {
       clearInterval(this.timer);