Parcourir la source

feate:完善js脚本以及vue的demo流通

luoy il y a 4 semaines
Parent
commit
0ff442139c

+ 182 - 29
buriedPiont.js

@@ -11,7 +11,7 @@ window.JSBridge = {
     // Android WebView
     if (window.Android && typeof window.Android.getUserId === 'function') {
       callback(window.Android.getUserId());
-      return;
+      return ;
     }
     // iOS WebView
     if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.getUserId) {
@@ -32,28 +32,173 @@ window.JSBridge = {
   }
 };
 
+// 工具函数
+function getUrlParam(name) {
+    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
+    var r = window.location.search.substr(1).match(reg);
+    if (r != null) return decodeURIComponent(r[2]);
+    return null;
+}
+function getCookie(name) {
+  const matches = document.cookie.match(new RegExp(
+    `(?:^|; )${name.replace(/([.$?*|{}()[]\\\/\+^])/g, '\\$1')}=([^;]*)`
+  ));
+  return matches ? decodeURIComponent(matches[1]) : undefined;
+}
+function setCookie(name, value, minutes) {
+  let expires = "";
+  if (minutes) {
+    const date = new Date();
+    date.setTime(date.getTime() + (minutes * 60 * 1000));
+    expires = "; expires=" + date.toUTCString();
+  }
+  document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`;
+}  
+function deleteCookie(name) {
+  document.cookie = `${name}=; Max-Age=-99999999; path=/`;
+}
+// 获取浏览器信息
+function getBrowserInfo() {
+    var ua = navigator.userAgent;
+    var browser = 'Unknown';
+    var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua);
+    var osType = 'Unknown';
+    var osVersion = 'Unknown';
+
+    // 系统类型和版本号判断
+    if (/iPhone|iPad|iPod/i.test(ua)) {
+        osType = 'iOS';
+        var iosVersionMatch = ua.match(/OS (\d+)[_.](\d+)?([_.](\d+))?/i);
+        if (iosVersionMatch) {
+            osVersion = iosVersionMatch[1];
+            if (iosVersionMatch[2]) osVersion += '.' + iosVersionMatch[2];
+            if (iosVersionMatch[4]) osVersion += '.' + iosVersionMatch[4];
+        }
+    } else if (/Android/i.test(ua)) {
+        osType = 'Android';
+        var androidVersionMatch = ua.match(/Android ([\d.]+)/i);
+        if (androidVersionMatch) {
+            osVersion = androidVersionMatch[1];
+        }
+    } else if (/HarmonyOS|HMS/i.test(ua)) {
+        osType = 'HarmonyOS';
+        var harmonyVersionMatch = ua.match(/HarmonyOS[\s/]?([\d.]+)/i) || ua.match(/HMSCore[\s/]?([\d.]+)/i);
+        if (harmonyVersionMatch) {
+            osVersion = harmonyVersionMatch[1];
+        }
+    }
 
-let userId = '';
-JSBridge.getUserId(function (id) {
-  userId = id;
-});
-class Tracker {
+    // 浏览器类型判断
+    if (/Edg/i.test(ua)) {
+        browser = 'Edge';
+    } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) {
+        browser = 'HuaweiBrowser';
+    } else if (/Chrome/i.test(ua)) {
+        browser = 'Chrome';
+    } else if (/Firefox/i.test(ua)) {
+        browser = 'Firefox';
+    } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) {
+        browser = 'Safari';
+    } else if (/MSIE|Trident/i.test(ua)) {
+        browser = 'IE';
+    }
 
-  constructor(options = {}) {
-    this.baseUrl = options.baseUrl || '';
+    // 特殊浏览器环境检测
+    if (/MicroMessenger/i.test(ua)) {
+        browser = 'WeChat';
+    } else if (/QQBrowser/i.test(ua)) {
+        browser = 'QQBrowser';
+    } else if (/UCBrowser/i.test(ua)) {
+        browser = 'UCBrowser';
+    } else if (/Telegram/i.test(ua)) {
+        browser = 'Telegram';
+    }
+
+    return {
+        isMobile: isMobile,
+        browser: browser,
+        userAgent: ua,
+        osType: osType,
+        osVersion: osVersion
+    };
+}
+
+// 主类
+function UtmTracker() {}
+
+// 获取UTM参数
+UtmTracker.prototype.getParams = function() {
+    return {
+        utm_source: getUrlParam('utm_source') || '',
+        utm_medium: getUrlParam('utm_medium') || '',
+        utm_campaign: getUrlParam('utm_campaign') || '',
+        utm_term: getUrlParam('utm_term') || '',
+        utm_content: getUrlParam('utm_content') || '',
+        referrer: document.referrer || '',
+        browser: getBrowserInfo(),
+        timestamp: new Date().toISOString(),
+        url: window.location.href
+    };
+};
+
+// 静态方法:快速获取(无需实例化)
+UtmTracker.get = function() {
+    return new UtmTracker().getParams();
+};
+
+class Tracker {
+  initCofig = {
+    baseUrl: '',
+    heartbeatInterval: 30000, // 心跳间隔,默认30秒
+    visitCookieTimeout: 1, // 访问次数cookie有效期1分钟
+    maxVisitCount: 5, // 最大访问次数5次
+    blockCookieTimeout: 180, // 封锁cookie有效期180分钟
+    myEventSend: () => {}, // 自定义事件上报函数
+    beforeDestroy: () => {}, // 销毁前的回调函数
+    crashTime: 3, // 闪退阈值(几秒内关闭)
+    idleTimeout: 300000, // 默认 5 分钟无操作视为离开
+    
+  }
+  hasInitialized = false; // 是否已初始化
+  initialize(options) {
+    this.initCofig = {
+      ...this.initCofig,
+      ...options,
+    }
+    this.baseUrl = options.baseUrl || this.initCofig.baseUrl || 'https://your-default-tracking-api.com'; // 默认上报地址
     this.timer = null;
+    this.userId = 'user'; // 用户ID
     this.startTime = 0;
-    this.heartbeatInterval = options.heartbeatInterval || 30000; // 默认30秒发送一次心跳
-    this.visitCookieTimeout = options.visitCookieTimeout || 1; // 默认访问次数cookie有效期1分钟
-    this.maxVisitCount = options.maxVisitCount || 5; // 默认最大访问次数5次
-    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 分钟无操作视为离开
+    this.heartbeatInterval = options.heartbeatInterval || this.initCofig.heartbeatInterval; // 默认30秒发送一次心跳
+    this.visitCookieTimeout = options.visitCookieTimeout || this.initCofig.visitCookieTimeout; // 默认访问次数cookie有效期1分钟
+    this.maxVisitCount = options.maxVisitCount || this.initCofig.maxVisitCount; // 默认最大访问次数5次
+    this.blockCookieTimeout = options.blockCookieTimeout || this.initCofig.blockCookieTimeout; // 默认封锁cookie有效期180分钟
+    this.myEventSend = options.myEventSend || this.initCofig.myEventSend; // 是否开启自定义事件上报
+    this.beforeDestroy = options.beforeDestroy || this.initCofig.beforeDestroy; // 销毁前的回调函数
+    this.crashTime = options.crashTime || this.initCofig.crashTime; // 闪退阈值(几秒内关闭页面被认为是闪退)
+    this.idleTimeout = options.idleTimeout || this.initCofig.idleTimeout; // 默认 5 分钟无操作视为离开
+
+    window.JSBridge.getUserId((id) => {
+      this.userId = id;
+    });
+
+    this.headers = {
+      'Content-Type': 'application/json',
+      heartbeatSeconds: this.heartbeatInterval,
+      userId: this.userId,
+    };
+    options.headers && Object.assign(this.headers, options.headers);
+  }
+  constructor(options = {}) {
+    this.initialize(options);
   }
 
-  init() {
+  init(options = {}) {
+    if (this.hasInitialized) {
+      console.warn('埋点已初始化,请勿重复调用');
+      return
+    }
+    this.initialize(options);
     const visitCookieName = 'userVisitCount';
     const blockCookieName = 'userBlocked';
     const isBlocked = getCookie(blockCookieName);
@@ -61,10 +206,12 @@ class Tracker {
       this.startTime = Date.now();
       this.bindEvents();
       this.startHeartbeat();
+      console.log(`埋点初始化成功,用户ID: ${this.userId}`);
+      this.hasInitialized = true;
     }
     if (isBlocked) {
       // restoreBlock();
-      return;
+      return `用户访问次数超过限制(${this.maxVisitCount}次),已封锁 ${this.blockCookieTimeout} 分钟`;
     }
 
     let visitCount = getCookie(visitCookieName);
@@ -74,6 +221,7 @@ class Tracker {
         setCookie(blockCookieName, 'true', this.blockCookieTimeout); // 封锁用户 180 分钟
         deleteCookie(visitCookieName);
         // restoreBlock();
+        console.warn(`用户访问次数超过限制(${this.maxVisitCount}次),已封锁 ${this.blockCookieTimeout} 分钟`);
       } else {
         visitCount += 1;
         setCookie(visitCookieName, visitCount, this.visitCookieTimeout); // 每次访问 +1,有效期 1 分钟
@@ -89,19 +237,17 @@ class Tracker {
     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);
+    window.addEventListener('mousemove', this.resetIdleTimer.bind(this));
+    window.addEventListener('keydown', this.resetIdleTimer.bind(this));
     this.myEventSend(this.sendData.bind(this)); // 绑定自定义事件上报
   }
 
   startHeartbeat() {
     this.timer = setInterval(() => {
-      console.log(userId);
-
       this.sendData({
         eventType: 'heartbeat',
         duration: Math.round((Date.now() - this.startTime) / 1000),
-      }, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
+      });
     }, this.heartbeatInterval);
   }
 
@@ -137,7 +283,7 @@ class Tracker {
     if (navigator.sendBeacon) {
       navigator.sendBeacon(this.baseUrl, payload);
     } else {
-      this.sendData(payload, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
+      this.sendData(payload);
     }
   };
 
@@ -151,7 +297,7 @@ class Tracker {
         text: target.innerText || target.textContent || '',
         timestamp: new Date().toISOString(),
       };
-      this.sendData(trackInfo, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
+      this.sendData(trackInfo);
     }
   };
 
@@ -176,11 +322,16 @@ class Tracker {
   }
   sendData(data, headers = {}) {
     this.queueIdleTask(() => {
-      console.log('上报埋点数据:', data);
+      const params =  {
+        ...data,
+        ...UtmTracker.get()
+      };
+      params.browser = params.browser.browser || 'Unknown';
+      console.log('上报埋点数据:', params);
       fetch(`${this.baseUrl}`, {
         method: 'POST',
-        headers: { 'Content-Type': 'application/json', ...headers },
-        body: JSON.stringify(data)
+        headers: this.headers,
+        body: JSON.stringify(params)
       }).catch(err => {
         console.error('埋点失败:', err);
       });
@@ -200,7 +351,9 @@ class Tracker {
     this.sendData({
       eventType: 'tracker_destroyed',
       duration: Math.round((Date.now() - this.startTime) / 1000),
-    }, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
+    });
+    this.hasInitialized = false;
+    return '销毁埋点成功'
   }
 }
 

+ 3 - 3
vue-test/public/index.html

@@ -14,9 +14,9 @@
     <div id="app"></div>
     <!-- built files will be auto injected -->
   </body>
-  <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script>
+  <!-- <script src="https://cdn.bootcdn.net/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script> -->
   <!-- <script src="https://cdn.jsdelivr.net/npm/@fingerprintjs/fingerprintjs@3/dist/fp.min.js"></script> -->
-  <script>
+  <!-- <script>
     (async () => {
 
       const v = new window.VConsole()
@@ -27,5 +27,5 @@
       // console.log('------------(Visitor ID):', visitorId);
 
     })();
-</script>
+</script> -->
 </html>

+ 366 - 0
vue-test/src/buriedPiont.js

@@ -0,0 +1,366 @@
+
+// 通用 JSBridge
+window.JSBridge = {
+  getOS: function () {
+    const ua = navigator.userAgent || navigator.vendor || window.opera;
+    if (/android/i.test(ua)) return 'Android';
+    if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) return 'iOS';
+    return 'Web';
+  },
+  getUserId: function (callback) {
+    // Android WebView
+    if (window.Android && typeof window.Android.getUserId === 'function') {
+      callback(window.Android.getUserId());
+      return ;
+    }
+    // iOS WebView
+    if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.getUserId) {
+      window.webkit.messageHandlers.getUserId.postMessage(null);
+      window.onUserIdReceived = callback; // iOS原生需回调window.onUserIdReceived(userId)
+      return;
+    }
+    // Web环境(用 FingerprintJS 生成指纹)
+    if (window.FingerprintJS) {
+      FingerprintJS.load().then(fp => {
+        fp.get().then(result => {
+          callback(result.visitorId);
+        });
+      });
+    } else {
+      callback('unsupported');
+    }
+  }
+};
+
+// 工具函数
+function getUrlParam(name) {
+    var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
+    var r = window.location.search.substr(1).match(reg);
+    if (r != null) return decodeURIComponent(r[2]);
+    return null;
+}
+function getCookie(name) {
+  const matches = document.cookie.match(new RegExp(
+    `(?:^|; )${name.replace(/([.$?*|{}()[]\\\/\+^])/g, '\\$1')}=([^;]*)`
+  ));
+  return matches ? decodeURIComponent(matches[1]) : undefined;
+}
+function setCookie(name, value, minutes) {
+  let expires = "";
+  if (minutes) {
+    const date = new Date();
+    date.setTime(date.getTime() + (minutes * 60 * 1000));
+    expires = "; expires=" + date.toUTCString();
+  }
+  document.cookie = `${name}=${encodeURIComponent(value)}${expires}; path=/`;
+}  
+function deleteCookie(name) {
+  document.cookie = `${name}=; Max-Age=-99999999; path=/`;
+}
+// 获取浏览器信息
+function getBrowserInfo() {
+    var ua = navigator.userAgent;
+    var browser = 'Unknown';
+    var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua);
+    var osType = 'Unknown';
+    var osVersion = 'Unknown';
+
+    // 系统类型和版本号判断
+    if (/iPhone|iPad|iPod/i.test(ua)) {
+        osType = 'iOS';
+        var iosVersionMatch = ua.match(/OS (\d+)[_.](\d+)?([_.](\d+))?/i);
+        if (iosVersionMatch) {
+            osVersion = iosVersionMatch[1];
+            if (iosVersionMatch[2]) osVersion += '.' + iosVersionMatch[2];
+            if (iosVersionMatch[4]) osVersion += '.' + iosVersionMatch[4];
+        }
+    } else if (/Android/i.test(ua)) {
+        osType = 'Android';
+        var androidVersionMatch = ua.match(/Android ([\d.]+)/i);
+        if (androidVersionMatch) {
+            osVersion = androidVersionMatch[1];
+        }
+    } else if (/HarmonyOS|HMS/i.test(ua)) {
+        osType = 'HarmonyOS';
+        var harmonyVersionMatch = ua.match(/HarmonyOS[\s/]?([\d.]+)/i) || ua.match(/HMSCore[\s/]?([\d.]+)/i);
+        if (harmonyVersionMatch) {
+            osVersion = harmonyVersionMatch[1];
+        }
+    }
+
+    // 浏览器类型判断
+    if (/Edg/i.test(ua)) {
+        browser = 'Edge';
+    } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) {
+        browser = 'HuaweiBrowser';
+    } else if (/Chrome/i.test(ua)) {
+        browser = 'Chrome';
+    } else if (/Firefox/i.test(ua)) {
+        browser = 'Firefox';
+    } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) {
+        browser = 'Safari';
+    } else if (/MSIE|Trident/i.test(ua)) {
+        browser = 'IE';
+    }
+
+    // 特殊浏览器环境检测
+    if (/MicroMessenger/i.test(ua)) {
+        browser = 'WeChat';
+    } else if (/QQBrowser/i.test(ua)) {
+        browser = 'QQBrowser';
+    } else if (/UCBrowser/i.test(ua)) {
+        browser = 'UCBrowser';
+    } else if (/Telegram/i.test(ua)) {
+        browser = 'Telegram';
+    }
+
+    return {
+        isMobile: isMobile,
+        browser: browser,
+        userAgent: ua,
+        osType: osType,
+        osVersion: osVersion
+    };
+}
+
+// 主类
+function UtmTracker() {}
+
+// 获取UTM参数
+UtmTracker.prototype.getParams = function() {
+    return {
+        utm_source: getUrlParam('utm_source') || '',
+        utm_medium: getUrlParam('utm_medium') || '',
+        utm_campaign: getUrlParam('utm_campaign') || '',
+        utm_term: getUrlParam('utm_term') || '',
+        utm_content: getUrlParam('utm_content') || '',
+        referrer: document.referrer || '',
+        browser: getBrowserInfo(),
+        timestamp: new Date().toISOString(),
+        url: window.location.href
+    };
+};
+
+// 静态方法:快速获取(无需实例化)
+UtmTracker.get = function() {
+    return new UtmTracker().getParams();
+};
+
+class Tracker {
+  initCofig = {
+    baseUrl: '',
+    heartbeatInterval: 30000, // 心跳间隔,默认30秒
+    visitCookieTimeout: 1, // 访问次数cookie有效期1分钟
+    maxVisitCount: 5, // 最大访问次数5次
+    blockCookieTimeout: 180, // 封锁cookie有效期180分钟
+    myEventSend: () => {}, // 自定义事件上报函数
+    beforeDestroy: () => {}, // 销毁前的回调函数
+    crashTime: 3, // 闪退阈值(几秒内关闭)
+    idleTimeout: 300000, // 默认 5 分钟无操作视为离开
+    
+  }
+  hasInitialized = false; // 是否已初始化
+  initialize(options) {
+    this.initCofig = {
+      ...this.initCofig,
+      ...options,
+    }
+    this.baseUrl = options.baseUrl || this.initCofig.baseUrl || 'https://your-default-tracking-api.com'; // 默认上报地址
+    this.timer = null;
+    this.userId = 'user'; // 用户ID
+    this.startTime = 0;
+    this.heartbeatInterval = options.heartbeatInterval || this.initCofig.heartbeatInterval; // 默认30秒发送一次心跳
+    this.visitCookieTimeout = options.visitCookieTimeout || this.initCofig.visitCookieTimeout; // 默认访问次数cookie有效期1分钟
+    this.maxVisitCount = options.maxVisitCount || this.initCofig.maxVisitCount; // 默认最大访问次数5次
+    this.blockCookieTimeout = options.blockCookieTimeout || this.initCofig.blockCookieTimeout; // 默认封锁cookie有效期180分钟
+    this.myEventSend = options.myEventSend || this.initCofig.myEventSend; // 是否开启自定义事件上报
+    this.beforeDestroy = options.beforeDestroy || this.initCofig.beforeDestroy; // 销毁前的回调函数
+    this.crashTime = options.crashTime || this.initCofig.crashTime; // 闪退阈值(几秒内关闭页面被认为是闪退)
+    this.idleTimeout = options.idleTimeout || this.initCofig.idleTimeout; // 默认 5 分钟无操作视为离开
+
+    window.JSBridge.getUserId((id) => {
+      this.userId = id;
+    });
+
+    this.headers = {
+      'Content-Type': 'application/json',
+      heartbeatSeconds: this.heartbeatInterval,
+      userId: this.userId,
+    };
+    options.headers && Object.assign(this.headers, options.headers);
+  }
+  constructor(options = {}) {
+    this.initialize(options);
+  }
+
+  init(options = {}) {
+    if (this.hasInitialized) {
+      console.warn('埋点已初始化,请勿重复调用');
+      return
+    }
+    this.initialize(options);
+    const visitCookieName = 'userVisitCount';
+    const blockCookieName = 'userBlocked';
+    const isBlocked = getCookie(blockCookieName);
+    const doneFN = () => {
+      this.startTime = Date.now();
+      this.bindEvents();
+      this.startHeartbeat();
+      console.log(`埋点初始化成功,用户ID: ${this.userId}`);
+      this.hasInitialized = true;
+    }
+    if (isBlocked) {
+      // restoreBlock();
+      return `用户访问次数超过限制(${this.maxVisitCount}次),已封锁 ${this.blockCookieTimeout} 分钟`;
+    }
+
+    let visitCount = getCookie(visitCookieName);
+    if (visitCount) {
+      visitCount = parseInt(visitCount, 10);
+      if (visitCount >= this.maxVisitCount) {
+        setCookie(blockCookieName, 'true', this.blockCookieTimeout); // 封锁用户 180 分钟
+        deleteCookie(visitCookieName);
+        // restoreBlock();
+        console.warn(`用户访问次数超过限制(${this.maxVisitCount}次),已封锁 ${this.blockCookieTimeout} 分钟`);
+      } else {
+        visitCount += 1;
+        setCookie(visitCookieName, visitCount, this.visitCookieTimeout); // 每次访问 +1,有效期 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.bind(this));
+    window.addEventListener('keydown', this.resetIdleTimer.bind(this));
+    this.myEventSend(this.sendData.bind(this)); // 绑定自定义事件上报
+  }
+
+  startHeartbeat() {
+    this.timer = setInterval(() => {
+      this.sendData({
+        eventType: 'heartbeat',
+        duration: Math.round((Date.now() - this.startTime) / 1000),
+      });
+    }, 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,
+      exitType: duration > this.crashTime ? 'crash' : 'normal',
+    };
+    const payload = JSON.stringify(data);
+    if (navigator.sendBeacon) {
+      navigator.sendBeacon(this.baseUrl, payload);
+    } else {
+      this.sendData(payload);
+    }
+  };
+
+  handleClickEvent = (event) => {
+    const target = event.target;
+    if (target.matches('[data-track]')) {
+      const trackInfo = {
+        eventType: 'button_click',
+        elementId: target.id || '无ID',
+        elementClass: target.className || '无class',
+        text: target.innerText || target.textContent || '',
+        timestamp: new Date().toISOString(),
+      };
+      this.sendData(trackInfo);
+    }
+  };
+
+  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 = {}) {
+    this.queueIdleTask(() => {
+      const UTMP = UtmTracker.get();
+      const params =  {
+        ...data,
+        ...UTMP,
+        ...UTMP.browser,
+      };
+      console.log('上报埋点数据:', params);
+      fetch(`${this.baseUrl}`, {
+        method: 'POST',
+        headers: this.headers,
+        body: JSON.stringify(params)
+      }).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);
+    }
+    this.sendData({
+      eventType: 'tracker_destroyed',
+      duration: Math.round((Date.now() - this.startTime) / 1000),
+    });
+    this.hasInitialized = false;
+    return '销毁埋点成功'
+  }
+}
+
+// 使用示例:
+// const tracker = new Tracker({ baseUrl: 'https://your-tracking-api.com' });
+// tracker.init();
+
+// 暴露给全局或者作为模块导出
+window.Tracker = Tracker;

+ 1 - 78
vue-test/src/components/HelloWorld.vue

@@ -1,87 +1,10 @@
 <template>
  <div>
   这是一个空页面
-  <button @click="generateAndAppendUtmParams">
+  <button data-track="点了按钮">
    点击按钮,发送数据
   </button>
  </div>
 </template>
 <script setup>
-import UtmTracker from "utm-params-extractor-test";
-import {ref} from 'vue';
-
-const userId = ref('');
-
-function generateAndAppendUtmParams() {
-  // 获取随机合法值的函数
-  function getRandomValue() {
-    return Math.random().toString(36).substring(2, 7);
-  }
-
-  // utm_source 合法取值范围
-  const utmSourceOptions = ['baidu', 'bd', 'google', 'safari', 'tx', 'qq', 'wx'];
-
-  // 随机选择一个 utm_source 值
-  const utmSource = utmSourceOptions[
-    Math.floor(Math.random() * utmSourceOptions.length)
-  ];
-
-  // 生成其他UTM参数的随机值
-  const utmParams = {
-    utm_source: utmSource,
-    utm_medium: getRandomValue(),
-    utm_campaign: getRandomValue(),
-    utm_term: getRandomValue(),
-    utm_content: getRandomValue()
-  };
-
-  // 构造带UTM参数的URL
-  let urlWithParams = window.location.href.split('?')[0] + '?';
-  const paramsArray = [];
-
-  for (const key in utmParams) {
-    paramsArray.push(`${key}=${encodeURIComponent(utmParams[key])}`);
-  }
-
-  urlWithParams += paramsArray.join('&');
-
-  // 更新当前页面的URL
-  window.history.pushState({}, '', urlWithParams);
-
-  // 触发按钮点击事件
-  fetchData();
-}
-
-const fetchData = () => {
-  const utm = UtmTracker.get()
-  console.log(utm);
-  const requestData = {
-    utm_source: utm.utm_source || '',
-    utm_medium: utm.utm_medium || '',
-    utm_campaign: utm.utm_campaign || '',
-    utm_term: utm.utm_term || '',
-    utm_content: utm.utm_content || '',
-    referrer: utm.referrer || '',
-    isMobile: utm.browser.isMobile || '',
-    browser: utm.browser.browser || '',
-    userAgent: utm.browser.userAgent || '',
-    osType: utm.browser.osType || '',
-    osVersion: utm.browser.osVersion || '',
-    timestamp: utm.timestamp || '',
-    url: utm.url || ''
-  };
-  console.log(requestData);
-  fetch('http://192.168.3.9:3000/api/insert', {
-    method: 'POST',
-    headers: { 'Content-Type': 'application/json',userid: userId },
-    body: JSON.stringify(requestData)
-  })
-   .then(response => response.json())
-   .then(data => {
-     console.log(data);
-   })
-   .catch(error => {
-     console.error("Error fetching data:", error);
-   });
-}
 </script>

+ 8 - 1
vue-test/src/main.js

@@ -1,4 +1,11 @@
 import { createApp } from 'vue'
 import App from './App.vue'
-
+import './buriedPiont.js'
+// 方便测试修改配置项所有挂载到window对象上
+window.tracker = new Tracker({ 
+  baseUrl: 'http://192.168.3.16:3888/api/log',
+  heartbeatInterval: 50000,
+});
+tracker.init();
 createApp(App).mount('#app')
+