1/** 2 * CMS Analytics Tracker (è¿½è¸ªèæ¬) 3 */ 4(function() { 5 'use strict'; 6 7 const script = document.currentScript || document.querySelector('script[data-site]'); 8 if (!script) return; 9 10 const SITE_ID = script.getAttribute('data-site'); 11 const TRACK_URL = script.getAttribute('data-endpoint') || '/track.php'; 12 const HEATMAP = script.getAttribute('data-heatmap') !== 'false'; 13 14 if (!SITE_ID) return; 15 16 // Cookie utilities 17 function setCookie(name, val, days) { 18 const d = new Date(); 19 d.setTime(d.getTime() + days * 86400000); 20 document.cookie = name + '=' + val + ';expires=' + d.toUTCString() + ';path=/;SameSite=Lax'; 21 } 22 23 function getCookie(name) { 24 const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); 25 return match ? match[2] : null; 26 } 27 28 // UUID generator 29 function uuid() { 30 return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, c => { 31 const r = Math.random() * 16 | 0; 32 return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 33 }); 34 } 35 36 // Send data (prefer sendBeacon) 37 function send(action, data) { 38 data.site_id = SITE_ID; 39 const url = TRACK_URL + '?a=' + action; 40 const body = JSON.stringify(data); 41 42 try { 43 if (navigator.sendBeacon) { 44 navigator.sendBeacon(url, new Blob([body], {type: 'application/json'})); 45 } else { 46 fetch(url, { 47 method: 'POST', 48 headers: {'Content-Type': 'application/json'}, 49 body: body, 50 keepalive: true 51 }).catch(() => {}); 52 } 53 } catch (e) {} 54 } 55 56 // Initialize session 57 let VID = getCookie('_cms_vid'); 58 if (!VID) { 59 VID = uuid().replace(/-/g, ''); 60 setCookie('_cms_vid', VID, 365); 61 } 62 63 let SID = sessionStorage.getItem('_cms_sid'); 64 if (!SID) { 65 66 SID = uuid().replace(/-/g, ''); 67 sessionStorage.setItem('_cms_sid', SID); 68 } 69 70 // Pageview tracking 71 const pageData = { 72 vid: VID, 73 sid: SID, 74 url: location.href, 75 ref: document.referrer, 76 post_id: window.__POST_ID__ || 0, 77 lang: navigator.language, 78 sw: screen.width, 79 sh: screen.height 80 }; 81 82 if (document.readyState === 'complete') { 83 send('pv', pageData); 84 } else { 85 window.addEventListener('load', () => send('pv', pageData)); 86 } 87 88 // Engagement tracking 89 const startTime = Date.now(); 90 let maxScroll = 0; 91 let lastSent = 0; 92 93 function getScrollPercent() { 94 const doc = document.documentElement; 95 const body = document.body; 96 const scrollTop = window.pageYOffset || doc.scrollTop; 97 const scrollHeight = Math.max(doc.scrollHeight, body.scrollHeight); 98 const clientHeight = doc.clientHeight; 99 if (scrollHeight <= clientHeight) return 100; 100 return Math.round((scrollTop + clientHeight) / scrollHeight * 100); 101 } 102 103 function throttle(fn, delay) { 104 let last = 0, timer; 105 return function(...args) { 106 const now = Date.now(); 107 if (now - last >= delay) { 108 last = now; 109 fn.apply(this, args); 110 } else { 111 clearTimeout(timer); 112 timer = setTimeout(() => { last = Date.now(); fn.apply(this, args); }, delay); 113 } 114 }; 115 } 116 117 window.addEventListener('scroll', throttle(() => { 118 const pct = getScrollPercent(); 119 if (pct > maxScroll) maxScroll = pct; 120 }, 500)); 121 122 function sendEngagement() { 123 const duration = Math.round((Date.now() - startTime) / 1000); 124 if (duration < 2 || duration - lastSent < 5) return; 125 lastSent = duration; 126 send('eng', { 127 vid: VID, 128 url: location.href, 129 duration: duration, 130 scroll: maxScroll 131 }); 132 } 133 134 setInterval(sendEngagement, 15000); 135 window.addEventListener('beforeunload', sendEngagement); 136 document.addEventListener('visibilitychange', () => { 137 if (document.visibilityState === 'hidden') sendEngagement(); 138 }); 139 140 // Public event tracking API 141 window.cmsTrack = function(eventName, category, data) { 142 send('ev', { 143 vid: VID, sid: SID, 144 name: eventName, 145 category: category || 'general', 146 data: data || {}, 147 url: location.href 148 }); 149 }; 150 151 // Auto-track outbound links & downloads 152 document.addEventListener('click', function(e) { 153 const link = e.target.closest('a'); 154 if (!link) return; 155 156 const href = link.href; 157 if (href && link.hostname && link.hostname !== location.hostname) { 158 send('ev', { 159 vid: VID, sid: SID, 160 name: 'outbound_click', 161 category: 'link', 162 data: {url: href, text: link.innerText.substring(0, 100)}, 163 url: location.href 164 }); 165 } 166
167 if (href && /\.(pdf|zip|doc|docx|xls|xlsx|mp3|mp4|rar|7z)$/i.test(href)) { 168 send('ev', { 169 vid: VID, sid: SID, 170 name: 'download', 171 category: 'file', 172 data: {url: href}, 173 url: location.href 174 }); 175 } 176 }); 177 178 // Form submit tracking 179 document.addEventListener('submit', function(e) { 180 const form = e.target; 181 send('ev', { 182 vid: VID, sid: SID, 183 name: 'form_submit', 184 category: 'form', 185 data: {id: form.id || '', action: form.action || ''}, 186 url: location.href 187 }); 188 }); 189 190 // Heatmap tracking 191 if (HEATMAP) { 192 const device = /Mobile|Android|iPhone/.test(navigator.userAgent) ? 'mobile' : 'desktop'; 193 194 document.addEventListener('click', function(e) { 195 const target = e.target; 196 let selector = target.tagName.toLowerCase(); 197 if (target.id) selector += '#' + target.id; 198 else if (target.className && typeof target.className === 'string') { 199 selector += '.' + target.className.split(' ').slice(0, 2).join('.'); 200 } 201 202 send('hm', { 203 url: location.href, 204 type: 'click', 205 x: Math.round(e.pageX), 206 y: Math.round(e.pageY), 207 vw: window.innerWidth, 208 vh: window.innerHeight, 209 element: selector.substring(0, 255), 210 device: device 211 }); 212 }); 213 } 214})();
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.