PageSourceSearch

https://trade.313.com/webstatic/js/lib/utm-tracker.js?v=202609281745

js 313.com collected 2026-10-08 19:44:53 UTC 5,111 bytes, 166 lines download raw bytes

1/**
2 * 轻量级用户行为追踪脚本
3 * 功能:上报页面访问(visit)与有效活跃(active)事件
4 * 前置条件:必须存在 cookie "utm_track" 才会触发任何上报
5 * 活跃判定条件(满足任一即可):点击 / 滚动 / 路由变化 / 停留≥30s
6 */
7;(function () {
8  // SSR 环境安全检查,避免在服务端执行时报错
9  if (typeof window === 'undefined' || typeof document === 'undefined') return;
10
11  // 没有 utm_track 标识,直接退出,不绑定任何事件、不发任何请求
12  if (!getCookie('utm_track')) return;
13
14  // ==================== 配置常量 ====================
15  /** 访问上报接口 */
16  const VISIT_API = `${webDomain}/api/utm/visit`;
17  /** 活跃上报接口 */
18  const ACTIVE_API = `${webDomain}/api/utm/active`;
19  /** 判定为有效活跃的最低停留秒数 */
20  const ACTIVE_STAY_SECONDS = 30;
21
22  // ==================== 状态变量 ====================
23  /** 访问事件是否已上报(每个页面生命周期仅一次) */
24  let hasVisitReported = false;
25  /** 活跃事件是否已上报(每个页面生命周期仅一次) */
26  let hasActiveReported = false;
27  /** 页面进入时间戳(ms),用于计算停留时长 */
28  const enterAt = Date.now();
29  /** 停留时长定时器 ID,用于在页面销毁时清理 */
30  let stayTimer = null;
31  /** 滚动事件是否已触发过(仅首次滚动触发活跃上报) */
32  let scrollTriggered = false;
33  /** 声明缓存变量 */
34  let cachedBody = null;
35
36  // ==================== 网络请求工具 ====================
37
38  /**
39   * 通过 fetch 发送 POST JSON 请求
40   * @param {string} url - 目标接口地址
41   * @returns {Promise<boolean>} 请求是否成功
42   */
43  async function postJSON(url) {
44    try {
45      const data = await formatParams();
46
47      await $.ajax({
48        url,
49        data,
50        type: 'POST',
51        xhrFields: { withCredentials: true}, // 允许发送 Cookie
52        crossDomain: true, // 明确跨域请求
53      });
54
55      // 上报成功后更新缓存
56      cachedBody = data;
57      return true;
58    } catch {
59      return false;
60    }
61  }
62
63  /**
64   * 页面卸载时的兜底上报方法
65   * 优先使用 sendBeacon(不阻塞页面关闭),不可用时降级为 fetch keepalive
66   * @param {string} url - 目标接口地址
67   */
68  function sendBeaconFallback(url) {
69    if (typeof navigator === 'undefined') return;
70
71    if (typeof navigator.sendBeacon === 'function') {
72      const blob = new Blob([cachedBody], { type: 'application/json' });
73      navigator.sendBeacon(url, blob);
74      return;
75    }
76
77    // sendBeacon 不可用时降级为 fetch
78    postJSON(url);
79  }
80
81  // ==================== 上报逻辑 ====================
82
83  /** 上报访问事件(仅首次调用生效) */
84  function reportVisit() {
85    if (hasVisitReported) return;
86    hasVisitReported = true;
87    postJSON(VISIT_API);
88  }
89
90  /** 上报活跃事件(仅首次调用生效) */
91  function reportActive() {
92    if (hasActiveReported) return;
93    hasActiveReported = true;
94    postJSON(ACTIVE_API);
95  }
96
97  /**
98   * 页面离开时的活跃兜底上报
99   * 仅在未上报过活跃 且 累计停留 ≥ ACTIVE_STAY_SECONDS 时触发
100   */
101  function reportActiveOnLeave() {
102    if (hasActiveReported) return;
103
104    const stayedMs = Date.now() - enterAt;
105    if (stayedMs >= ACTIVE_STAY_SECONDS * 1000) {
106      hasActiveReported = true;
107      sendBeaconFallback(ACTIVE_API);
108    }
109  }
110
111  // ==================== 事件绑定 ====================
112
113  /** 绑定所有活跃判定信号及页面生命周期监听器 */
114  function bindActiveSignals() {
115    // 1. 交互信号:点击、首次滚动、路由变化
116    document.addEventListener('click', reportActive, { passive: true });
117
118    window.addEventListener(
119      'scroll',
120      () => {
121        if (scrollTriggered) return;
122        scrollTriggered = true;
123        reportActive();
124      },
125      { passive: true },
126    );
127
128    window.addEventListener('hashchange', reportActive, { passive: true });
129    window.addEventListener('popstate', reportActive, { passive: true });
130
131    // 2. 停留时长信号:达到阈值自动判定为活跃
132    stayTimer = window.setTimeout(reportActive, ACTIVE_STAY_SECONDS * 1000);
133
134    // 3. 页面卸载/隐藏信号:兜底上报
135    window.addEventListener('beforeunload', reportActiveOnLeave);
136    window.addEventListener('pagehide', reportActiveOnLeave);
137
138    document.addEventListener('visibilitychange', () => {
139      if (document.visibilityState === 'hidden') {
140        reportActiveOnLeave();
141      }
142    });
143  }
144
145  // ==================== 初始化入口 ====================
146
147  function init() {
148    reportVisit();
149    bindActiveSignals();
150  }
151
152  // 兼容 DOM 尚未就绪的情况
153  if (document.readyState === 'loading') {
154    document.addEventListener('DOMContentLoaded', init);
155  } else {
156    init();
157  }
158
159  // 页面隐藏时清理定时器,防止后台标签页误触发
160  window.addEventListener('pagehide', () => {
161    if (stayTimer) {
162      window.clearTimeout(stayTimer);
163      stayTimer = null;
164    }
165  });
166})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.