index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. // index.js
  2. (function (root, factory) {
  3. if (typeof define === 'function' && define.amd) {
  4. // AMD
  5. define([], factory);
  6. } else if (typeof module === 'object' && module.exports) {
  7. // CommonJS
  8. module.exports = factory();
  9. } else {
  10. // 全局变量
  11. root.UtmTracker = factory();
  12. }
  13. }(this, function () {
  14. 'use strict';
  15. // 工具函数
  16. function getUrlParam(name) {
  17. var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
  18. var r = window.location.search.substr(1).match(reg);
  19. if (r != null) return decodeURIComponent(r[2]);
  20. return null;
  21. }
  22. // 获取浏览器信息
  23. function getBrowserInfo() {
  24. var ua = navigator.userAgent;
  25. var browser = 'Unknown';
  26. var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua);
  27. var osType = 'Unknown';
  28. var osVersion = 'Unknown';
  29. // 系统类型和版本号判断
  30. if (/iPhone|iPad|iPod/i.test(ua)) {
  31. osType = 'iOS';
  32. var iosVersionMatch = ua.match(/OS (\d+)[_.](\d+)?([_.](\d+))?/i);
  33. if (iosVersionMatch) {
  34. osVersion = iosVersionMatch[1];
  35. if (iosVersionMatch[2]) osVersion += '.' + iosVersionMatch[2];
  36. if (iosVersionMatch[4]) osVersion += '.' + iosVersionMatch[4];
  37. }
  38. } else if (/Android/i.test(ua)) {
  39. osType = 'Android';
  40. var androidVersionMatch = ua.match(/Android ([\d.]+)/i);
  41. if (androidVersionMatch) {
  42. osVersion = androidVersionMatch[1];
  43. }
  44. } else if (/HarmonyOS|HMS/i.test(ua)) {
  45. osType = 'HarmonyOS';
  46. var harmonyVersionMatch = ua.match(/HarmonyOS[\s/]?([\d.]+)/i) || ua.match(/HMSCore[\s/]?([\d.]+)/i);
  47. if (harmonyVersionMatch) {
  48. osVersion = harmonyVersionMatch[1];
  49. }
  50. }
  51. // 浏览器类型判断
  52. if (/Edg/i.test(ua)) {
  53. browser = 'Edge';
  54. } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) {
  55. browser = 'HuaweiBrowser';
  56. } else if (/Chrome/i.test(ua)) {
  57. browser = 'Chrome';
  58. } else if (/Firefox/i.test(ua)) {
  59. browser = 'Firefox';
  60. } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) {
  61. browser = 'Safari';
  62. } else if (/MSIE|Trident/i.test(ua)) {
  63. browser = 'IE';
  64. }
  65. // 特殊浏览器环境检测
  66. if (/MicroMessenger/i.test(ua)) {
  67. browser = 'WeChat';
  68. } else if (/QQBrowser/i.test(ua)) {
  69. browser = 'QQBrowser';
  70. } else if (/UCBrowser/i.test(ua)) {
  71. browser = 'UCBrowser';
  72. } else if (/Telegram/i.test(ua)) {
  73. browser = 'Telegram';
  74. }
  75. return {
  76. isMobile: isMobile,
  77. browser: browser,
  78. userAgent: ua,
  79. osType: osType,
  80. osVersion: osVersion
  81. };
  82. }
  83. //获取上一级url
  84. function getPreviousUrl() {
  85. let referrer = document.referrer;
  86. if (referrer) {
  87. return referrer;
  88. }
  89. // else if (window.history.length > 1) {
  90. // // 如果有历史记录
  91. // window.history.back();
  92. // return window.location.href;
  93. // } else {
  94. // // 如果没有历史记录
  95. // return null;
  96. // }
  97. }
  98. // 主类
  99. function UtmTracker(config) {
  100. this.config = Object.assign({
  101. reportUrl: '', // 上报地址
  102. autoSend: true, // 是否自动上报
  103. method: 'POST', // 请求方式
  104. headers: { 'Content-Type': 'application/json' }, // 请求头
  105. extra: {}, // 额外参数
  106. }, config || {});
  107. if (this.config.autoSend && this.config.reportUrl) {
  108. this.send();
  109. }
  110. }
  111. // 获取UTM参数
  112. UtmTracker.prototype.getParams = function() {
  113. const browser = getBrowserInfo()
  114. const params = {
  115. utm_source: getUrlParam('utm_source') || '',
  116. utm_medium: getUrlParam('utm_medium') || '',
  117. utm_campaign: getUrlParam('utm_campaign') || '',
  118. utm_term: getUrlParam('utm_term') || '',
  119. utm_content: getUrlParam('utm_content') || '',
  120. referrer: getPreviousUrl() || '',
  121. timestamp: new Date().toISOString(),
  122. url: window.location.href,
  123. isMobile: browser.isMobile,
  124. browser: browser.browser,
  125. userAgent: browser.ua,
  126. osType: browser.osType,
  127. osVersion: browser.osVersion
  128. };
  129. // 合并额外参数
  130. let result = params;
  131. if (this.config && this.config.extra && typeof this.config.extra === 'object') {
  132. result = Object.assign({}, params, this.config.extra);
  133. }
  134. if (typeof this.config.onParams === 'function') {
  135. this.config.onParams(result);
  136. }
  137. return result;
  138. };
  139. // 发送请求
  140. UtmTracker.prototype.send = function (data) {
  141. const cfg = this.config || {};
  142. const payload = data || this.getParams();
  143. console.log(payload);
  144. if (!cfg.reportUrl) return;
  145. fetch(cfg.reportUrl, {
  146. method: 'POST',
  147. headers: cfg.headers || { 'Content-Type': 'application/json' },
  148. body: JSON.stringify(payload)
  149. })
  150. .then(res => {
  151. // 假设res为Response对象,需要解析json
  152. if (typeof res.json === 'function') {
  153. res.json().then(data => {
  154. if (data.downloadUrl) {
  155. // 倒计时弹窗
  156. let countdown = 3;
  157. const countdownMask = document.createElement('div');
  158. countdownMask.style.position = 'fixed';
  159. countdownMask.style.top = 0;
  160. countdownMask.style.left = 0;
  161. countdownMask.style.width = '100vw';
  162. countdownMask.style.height = '100vh';
  163. countdownMask.style.background = 'rgba(0,0,0,0.5)';
  164. countdownMask.style.zIndex = 9999;
  165. countdownMask.style.display = 'flex';
  166. countdownMask.style.alignItems = 'center';
  167. countdownMask.style.justifyContent = 'center';
  168. const countdownModal = document.createElement('div');
  169. countdownModal.style.background = '#fff';
  170. countdownModal.style.borderRadius = '8px';
  171. countdownModal.style.padding = '32px 48px';
  172. countdownModal.style.fontSize = '20px';
  173. countdownModal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
  174. countdownModal.style.display = 'flex';
  175. countdownModal.style.flexDirection = 'column';
  176. countdownModal.style.alignItems = 'center';
  177. const text = document.createElement('div');
  178. text.innerText = `即将跳转,${countdown}秒...`;
  179. countdownModal.appendChild(text);
  180. countdownMask.appendChild(countdownModal);
  181. document.body.appendChild(countdownMask);
  182. const timer = setInterval(() => {
  183. countdown--;
  184. text.innerText = `即将跳转,${countdown}秒...`;
  185. if (countdown <= 0) {
  186. clearInterval(timer);
  187. document.body.removeChild(countdownMask);
  188. // 跳转
  189. console.log(data.downloadUrl);
  190. window.location.href = data.downloadUrl
  191. return
  192. }
  193. }, 1000);
  194. return
  195. // 创建遮罩
  196. const mask = document.createElement('div');
  197. mask.style.position = 'fixed';
  198. mask.style.top = 0;
  199. mask.style.left = 0;
  200. mask.style.width = '100vw';
  201. mask.style.height = '100vh';
  202. mask.style.background = 'rgba(0,0,0,0.5)';
  203. mask.style.zIndex = 9999;
  204. mask.style.display = 'flex';
  205. mask.style.alignItems = 'center';
  206. mask.style.justifyContent = 'center';
  207. // 弹窗容器
  208. const modal = document.createElement('div');
  209. modal.style.background = '#fff';
  210. modal.style.borderRadius = '8px';
  211. modal.style.padding = '24px';
  212. modal.style.maxWidth = '90vw';
  213. modal.style.maxHeight = '90vh';
  214. modal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
  215. modal.style.position = 'relative';
  216. modal.style.display = 'flex';
  217. modal.style.flexDirection = 'column';
  218. modal.style.alignItems = 'center';
  219. // 关闭按钮
  220. const closeBtn = document.createElement('button');
  221. closeBtn.innerText = '关闭';
  222. closeBtn.style.position = 'absolute';
  223. closeBtn.style.top = '8px';
  224. closeBtn.style.right = '8px';
  225. closeBtn.style.background = '#f44336';
  226. closeBtn.style.color = '#fff';
  227. closeBtn.style.border = 'none';
  228. closeBtn.style.borderRadius = '4px';
  229. closeBtn.style.padding = '4px 12px';
  230. closeBtn.style.cursor = 'pointer';
  231. closeBtn.onclick = function () {
  232. document.body.removeChild(mask);
  233. };
  234. modal.appendChild(closeBtn);
  235. // 内容区
  236. let contentEl;
  237. if (data.type === 'video') {
  238. contentEl = document.createElement('video');
  239. contentEl.src = data.downloadUrl;
  240. contentEl.controls = true;
  241. contentEl.autoplay = true;
  242. contentEl.style.maxWidth = '80vw';
  243. contentEl.style.maxHeight = '70vh';
  244. } else if (data.type === 'audio') {
  245. contentEl = document.createElement('audio');
  246. contentEl.src = data.downloadUrl;
  247. contentEl.controls = true;
  248. contentEl.autoplay = true;
  249. contentEl.style.width = '100%';
  250. } else if (data.type === 'image') {
  251. contentEl = document.createElement('img');
  252. contentEl.src = data.downloadUrl;
  253. contentEl.style.maxWidth = '80vw';
  254. contentEl.style.maxHeight = '70vh';
  255. } else {
  256. contentEl = document.createElement('a');
  257. contentEl.href = data.downloadUrl;
  258. contentEl.innerText = '下载文件';
  259. contentEl.target = '_blank';
  260. }
  261. modal.appendChild(contentEl);
  262. mask.appendChild(modal);
  263. document.body.appendChild(mask);
  264. }
  265. });
  266. }
  267. })
  268. .catch(err => {
  269. console.log(err);
  270. });
  271. };
  272. // 静态方法:快速获取(无需实例化)
  273. UtmTracker.get = function (config) {
  274. const tracker = new UtmTracker(config);
  275. const params = tracker.getParams();
  276. if (config && config.autoSend && config.reportUrl) {
  277. tracker.send(params);
  278. }
  279. return params;
  280. };
  281. return UtmTracker;
  282. }));