jcq 3 дней назад
Родитель
Сommit
5d26eabd1c
8 измененных файлов с 829 добавлено и 238 удалено
  1. 606 190
      README.md
  2. 1 0
      demo-utm-vue/package.json
  3. 36 10
      demo-utm-vue/src/App.vue
  4. 1 0
      demo-utm-vue3/package.json
  5. 11 6
      demo-utm-vue3/src/App.vue
  6. 1 1
      package.json
  7. 171 31
      src/index.js
  8. 2 0
      webpack.config.js

+ 606 - 190
README.md

@@ -1,253 +1,669 @@
-### utm-params-extractor-test
+# utm-params-extractor-test
 
-#### 使用示例
+一个功能强大的 UTM 参数提取和浏览器指纹生成库,支持自动上报、跨环境指纹一致性、多种使用方式。
+
+## ✨ 主要特性
+
+- 🔍 **UTM 参数自动提取** - 支持 utm_source、utm_medium、utm_campaign、utm_term、utm_content
+- 🌐 **浏览器信息识别** - 自动识别浏览器类型、操作系统、设备类型
+- 🎯 **唯一指纹生成** - 基于设备特征生成稳定的浏览器指纹
+- 📊 **自动上报功能** - 支持自动/手动数据上报到后端
+- 🔄 **跨环境一致性** - 同一设备在不同项目环境下生成相同指纹
+- 📱 **多端支持** - 支持 PC、移动端、各种浏览器环境
+- 🛠 **灵活配置** - 支持多种使用方式和自定义配置
+- 📦 **多模块支持** - 支持 ES6、CommonJS、UMD 等多种模块规范
 
-##### 1. 安装包
+## 📦 安装
 
 ```bash
+# 安装主包
 npm install utm-params-extractor-test
+
+# 安装依赖(FingerprintJS)
+npm install @fingerprintjs/fingerprintjs
 ```
 
-##### 2. 在项目中使用
+## 🚀 快速开始
+
+### 基础用法
 
 ```javascript
-// ES6 模块
 import UtmTracker from 'utm-params-extractor-test';
 
-// 方法1:使用静态方法直接获取
-const utmParams = UtmTracker.get();
-console.log('UTM参数:', utmParams);
+// 创建实例
+const tracker = new UtmTracker({
+  reportUrl: 'https://your-api.com/collect',
+  autoSend: false
+});
 
-// 方法2:实例化后获取(适合扩展)
-const tracker = new UtmTracker();
+// 获取参数
 const params = tracker.getParams();
+console.log('UTM参数:', params);
 
-// 自行处理数据(例如发送到后端)
-fetch('https://your-api.com/track', {
-  method: 'POST',
-  headers: {
-    'Content-Type': 'application/json'
-  },
-  body: JSON.stringify(params)
-})
-.then(response => response.json())
-.then(data => console.log('发送成功:', data))
-.catch(error => console.error('发送失败:', error));
+// 手动上报
+tracker.send();
 ```
 
-##### 3. 在 HTML 中直接使用
+### 自动上报
 
-```html
-<script src="dist/main.js"></script>
-<script>
-  // 你的库会挂载在 window.UtmTracker
-  var utmParams = window.UtmTracker.get();
-  console.log('UTM参数:', utmParams);
-</script>
+```javascript
+// 实例化时自动上报
+new UtmTracker({
+  reportUrl: 'https://your-api.com/collect',
+  autoSend: true
+});
 ```
 
-> **注意:**
-> - HTML 直接引用时,npm 包名(如 utm-params-extractor-test)与全局变量名无关。
-> - 全局变量名由 webpack.config.js 的 `library.name` 决定,默认是 `UtmTracker`。
-> - 如需自定义全局变量名,请在 webpack.config.js 中修改:
->   ```js
->   output: {
->     // ...
->     library: {
->       name: 'UtmTracker', // 你想要的全局变量名
->       type: 'umd',
->     },
->     globalObject: 'this',
->   }
->   ```
->   这样 HTML 里就可以直接用 `window.UtmTracker.get()`。
- ---
-
-## 使用示例
+### 静态方法
 
-### 自动上报
-```js
-new UtmTracker({
-  reportUrl: 'https://your-server.com/collect',
+```javascript
+// 快速获取参数
+const params = await UtmTracker.get({
+  reportUrl: 'https://your-api.com/collect',
   autoSend: true
 });
 ```
 
-### 手动上报
-```js
-const tracker = new UtmTracker({ reportUrl: 'https://your-server.com/collect' });
+## 📋 API 文档
+
+### UtmTracker 构造函数
+
+```javascript
+new UtmTracker(config)
+```
+
+#### 配置参数 (config)
+
+| 参数 | 类型 | 必填 | 默认值 | 说明 |
+|------|------|------|--------|------|
+| `reportUrl` | string | ✅ | `''` | 数据上报的接口地址 |
+| `autoSend` | boolean | ❌ | `true` | 是否在实例化时自动上报 |
+| `method` | string | ❌ | `'POST'` | 请求方法,支持 'POST' 或 'GET' |
+| `headers` | object | ❌ | `{ 'Content-Type': 'application/json' }` | 请求头设置 |
+| `extra` | object | ❌ | `{}` | 额外的自定义参数 |
+| `onParams` | function | ❌ | `null` | 获取参数后的回调函数 |
+| `onSend` | function | ❌ | `null` | 发送成功后的回调函数 |
+| `onError` | function | ❌ | `null` | 发送失败后的回调函数 |
+
+### 实例方法
+
+#### `getParams()` - 同步获取参数
+
+```javascript
 const params = tracker.getParams();
-tracker.send(params);
 ```
 
-### 静态用法
-```js
-UtmTracker.get({
-  reportUrl: 'https://your-server.com/collect',
+返回当前页面的 UTM 参数和浏览器信息(不包含指纹)。
+
+#### `getParamsAsync()` - 异步获取参数(含指纹)
+
+```javascript
+const params = await tracker.getParamsAsync();
+```
+
+返回包含浏览器指纹的完整参数对象。
+
+#### `send(data)` - 发送数据
+
+```javascript
+// 发送默认参数
+tracker.send();
+
+// 发送自定义数据
+tracker.send(customData);
+```
+
+### 静态方法
+
+#### `UtmTracker.get(config)` - 快速获取参数
+
+```javascript
+const params = await UtmTracker.get({
+  reportUrl: 'https://your-api.com/collect',
   autoSend: true
 });
 ```
 
-### 返回参数说明
-
-`UtmTracker.get()` 或 `tracker.getParams()` 返回对象结构如下:
-
-| 字段         | 类型    | 说明                                                                 |
-| ------------ | ------- | -------------------------------------------------------------------- |
-| utm_source   | string  | UTM 来源渠道参数(如广告平台、搜索引擎等)                           |
-| utm_medium   | string  | UTM 媒介参数(如 cpc、email、banner 等)                              |
-| utm_campaign | string  | UTM 活动名称参数                                                     |
-| utm_term     | string  | UTM 关键词参数                                                       |
-| utm_content  | string  | UTM 内容参数(用于区分广告内容)                                     |
-| referrer     | string  | 上一个页面的 URL                                                     |
-| browser      | object  | 浏览器和设备信息对象(见下表)                                       |
-| timestamp    | string  | 获取参数的时间戳(ISO 格式)                                         |
-| url          | string  | 当前页面完整 URL                                                     |
-
-#### browser 字段结构
-
-| 字段       | 类型    | 说明                                                         |
-| ---------- | ------- | ------------------------------------------------------------ |
-| isMobile   | boolean | 是否为移动端设备                                              |
-| browser    | string  | 浏览器类型(如 Chrome、Safari、Edge、WeChat、HuaweiBrowser 等)|
-| userAgent  | string  | 浏览器的 User-Agent 字符串                                    |
-| osType     | string  | 操作系统类型(iOS、Android、HarmonyOS、Unknown)              |
-| osVersion  | string  | 操作系统版本号(如 16.6、13.0、3.0.0、Unknown)              |
-
-##### browser.browser 可能的值
-- Chrome
-- Safari
-- Edge
-- Firefox
-- IE
-- WeChat
-- QQBrowser
-- UCBrowser
-- HuaweiBrowser
-- Telegram
-- 其他
-
-##### browser.osType 可能的值
-- iOS
-- Android
-- HarmonyOS
-- Unknown
-
-##### 示例返回
-
-```json
+#### `UtmTracker.send(data, config)` - 快速发送数据
+
+```javascript
+await UtmTracker.send(customData, {
+  reportUrl: 'https://your-api.com/collect'
+});
+```
+
+## 📊 返回数据结构
+
+### 基础参数结构
+
+```javascript
 {
-  "utm_source": "google",
-  "utm_medium": "cpc",
-  "utm_campaign": "summer_sale",
-  "utm_term": "shoes",
-  "utm_content": "ad1",
-  "referrer": "https://www.example.com/",
-  "browser": {
-    "isMobile": true,
-    "browser": "Chrome",
-    "userAgent": "Mozilla/5.0 (Linux; Android 13; ...)",
-    "osType": "Android",
-    "osVersion": "13"
+  // UTM 参数
+  utmSource: "google",           // UTM 来源
+  utmMedium: "cpc",             // UTM 媒介
+  utmCampaign: "summer_sale",   // UTM 活动
+  utmTerm: "shoes",             // UTM 关键词
+  utmContent: "ad1",            // UTM 内容
+  
+  // 页面信息
+  referrer: "https://www.google.com/",  // 来源页面
+  url: "https://yoursite.com/?utm_source=google",  // 当前页面
+  domain: "yoursite.com",       // 当前域名
+  timestamp: "2024-01-01T12:00:00.000Z",  // 时间戳
+  
+  // 设备信息
+  isMobile: true,               // 是否移动端
+  browser: "Chrome",            // 浏览器类型
+  browserVersion: "120.0.0.0",  // 浏览器版本号
+  userAgent: "Mozilla/5.0...",  // 用户代理
+  osType: "Android",            // 操作系统类型
+  osVersion: "13",              // 操作系统版本
+  
+  // 指纹信息
+  fingerprint: "stable_fp_1a2b3c4d5e"  // 浏览器唯一指纹
+}
+```
+
+### 浏览器类型支持
+
+| 浏览器 | 识别标识 |
+|--------|----------|
+| Chrome | `Chrome` |
+| Safari | `Safari` |
+| Firefox | `Firefox` |
+| Edge | `Edge` |
+| IE | `IE` |
+| 微信 | `WeChat` |
+| QQ浏览器 | `QQBrowser` |
+| UC浏览器 | `UCBrowser` |
+| 华为浏览器 | `HuaweiBrowser` |
+| Telegram | `Telegram` |
+
+### 操作系统支持
+
+| 系统 | 识别标识 | 版本获取 |
+|------|----------|----------|
+| iOS | `iOS` | ✅ |
+| Android | `Android` | ✅ |
+| HarmonyOS | `HarmonyOS` | ✅ |
+| 其他 | `Unknown` | ❌ |
+
+## 🔧 使用示例
+
+### Vue.js 项目
+
+```vue
+<template>
+  <div>
+    <h1>UTM 参数追踪</h1>
+    <button @click="sendData">发送数据</button>
+    <pre>{{ utmParams }}</pre>
+  </div>
+</template>
+
+<script>
+import UtmTracker from 'utm-params-extractor-test';
+
+export default {
+  data() {
+    return {
+      tracker: null,
+      utmParams: null
+    };
+  },
+  async mounted() {
+    this.tracker = new UtmTracker({
+      reportUrl: 'https://your-api.com/collect',
+      autoSend: false,
+      onSend: (response, params) => {
+        console.log('数据发送成功:', response);
+      },
+      onError: (error, params) => {
+        console.error('数据发送失败:', error);
+      }
+    });
+    
+    // 获取包含指纹的完整参数
+    try {
+      this.utmParams = await this.tracker.getParamsAsync();
+      console.log('参数获取成功:', this.utmParams);
+    } catch (error) {
+      console.error('参数获取失败:', error);
+      // 如果异步获取失败,使用同步方法
+      this.utmParams = this.tracker.getParams();
+    }
   },
-  "timestamp": "2024-07-01T12:00:00.000Z",
-  "url": "https://your-site.com/?utm_source=google&utm_medium=cpc"
+  methods: {
+    async sendData() {
+      if (this.tracker) {
+        await this.tracker.send();
+      }
+    }
+  }
+};
+</script>
+```
+
+### React 项目
+
+#### 安装依赖
+```bash
+npm install utm-params-extractor-test @fingerprintjs/fingerprintjs
+```
+
+#### 使用示例
+```jsx
+import React, { useEffect, useState } from 'react';
+
+// 方式1:标准导入(推荐)
+import UtmTracker from 'utm-params-extractor-test';
+
+// 方式2:如果标准导入失败,使用 require
+// const UtmTracker = require('utm-params-extractor-test');
+
+// 方式3:如果使用 CDN 或全局引入
+// const UtmTracker = window.UtmTracker;
+
+function App() {
+  const [utmParams, setUtmParams] = useState(null);
+  const [tracker, setTracker] = useState(null);
+  const [loading, setLoading] = useState(true);
+
+  useEffect(() => {
+    const initTracker = async () => {
+      const utmTracker = new UtmTracker({
+        reportUrl: 'https://your-api.com/collect',
+        autoSend: false,
+        onSend: (response, params) => {
+          console.log('数据发送成功:', response);
+        },
+        onError: (error, params) => {
+          console.error('数据发送失败:', error);
+        }
+      });
+      
+      setTracker(utmTracker);
+      
+      // 获取包含指纹的完整参数
+      try {
+        const params = await utmTracker.getParamsAsync();
+        setUtmParams(params);
+        console.log('参数获取成功:', params);
+      } catch (error) {
+        console.error('参数获取失败:', error);
+        // 如果异步获取失败,使用同步方法
+        const syncParams = utmTracker.getParams();
+        setUtmParams(syncParams);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    initTracker();
+  }, []);
+
+  const handleSend = async () => {
+    if (tracker) {
+      try {
+        await tracker.send();
+      } catch (error) {
+        console.error('发送失败:', error);
+      }
+    }
+  };
+
+  if (loading) {
+    return <div>加载中...</div>;
+  }
+
+  return (
+    <div>
+      <h1>UTM 参数追踪</h1>
+      <button onClick={handleSend}>发送数据</button>
+      <pre>{JSON.stringify(utmParams, null, 2)}</pre>
+    </div>
+  );
 }
+
+export default App;
 ```
 
-## 跳转倒计时弹窗使用示例
+#### 常见问题解决
 
-当后端返回 { downloadUrl, type } 时,自动弹出3秒倒计时弹窗,倒计时结束后自动跳转到 downloadUrl。
+**问题1:`UtmTracker is not defined` 或 `is not a constructor` 错误**
 
-```js
-new UtmTracker({
-  reportUrl: 'https://your-server.com/collect',
-  autoSend: true,
-  // 只需后端返回 { downloadUrl: '...', type: 'video'|'audio'|'image' },无需额外配置
+如果遇到这些错误,请尝试以下解决方案:
+
+```jsx
+// 方案1:标准导入(推荐)
+import UtmTracker from 'utm-params-extractor-test';
+
+// 方案2:如果标准导入失败,使用 require
+const UtmTracker = require('utm-params-extractor-test');
+
+// 方案3:如果使用 CDN 或全局引入
+const UtmTracker = window.UtmTracker;
+
+// 方案4:动态导入
+import('utm-params-extractor-test').then(module => {
+  const UtmTracker = module.default || module;
+  // 使用 UtmTracker
 });
+
+// 方案5:检查导入的模块
+console.log('UtmTracker:', UtmTracker);
+console.log('typeof UtmTracker:', typeof UtmTracker);
 ```
 
-// 后端返回示例:
-// {
-//   downloadUrl: 'https://your-server.com/file.mp4',
-//   type: 'video'
-// }
+**问题2:FingerprintJS 依赖问题**
+
+确保安装了 FingerprintJS 依赖:
+```bash
+npm install @fingerprintjs/fingerprintjs
+```
 
-// 前端会自动弹出“即将跳转,3秒...”的弹窗,3秒后自动跳转到 downloadUrl。
+**问题3:ESLint 配置**
 
----
+如果 ESLint 报错,可以在 `.eslintrc.js` 中添加:
+```javascript
+module.exports = {
+  globals: {
+    UtmTracker: 'readonly'
+  }
+};
+```
 
-如需更多帮助或定制返回内容,请联系作者。
+**问题4:调试导入问题**
 
-## 配置项说明(config 参数)
+如果遇到导入问题,可以添加以下调试代码:
 
-| 配置项      | 类型     | 说明                                                         | 是否必填 | 默认值                      |
-|-------------|----------|--------------------------------------------------------------|----------|-----------------------------|
-| reportUrl   | string   | 上报数据的接口地址                                           | 是       | ''                          |
-| autoSend    | boolean  | 是否在实例化时自动上报一次                                   | 否       | true                       |
-| method      | string   | 请求方式,支持 'POST' 或 'GET'                               | 否       | 'POST'                     |
-| headers     | object   | 请求头设置                                                   | 否       | { 'Content-Type': 'application/json' } |
-| extra       | object   | 额外自定义参数,会合并进最终上报数据                         | 否       | {}                          |
-| onParams    | function | 获取参数后回调,参数为 params                                 | 否       | null                        |
-| onSend      | function | 发送成功回调,参数为 (response, params)                      | 否       | null                        |
-| onError     | function | 发送失败回调,参数为 (error, params)                         | 否       | null                        |
+```jsx
+// 在组件顶部添加调试代码
+console.log('=== 调试信息 ===');
+console.log('window.UtmTracker:', window.UtmTracker);
+console.log('typeof window.UtmTracker:', typeof window.UtmTracker);
 
-### 示例
+// 尝试不同的导入方式
+try {
+  const module = require('utm-params-extractor-test');
+  console.log('require result:', module);
+  console.log('module.default:', module.default);
+} catch (e) {
+  console.log('require failed:', e);
+}
 
-```js
-import UtmTracker from 'utm-params-extractor-test';
+try {
+  import('utm-params-extractor-test').then(module => {
+    console.log('dynamic import result:', module);
+    console.log('module.default:', module.default);
+  });
+} catch (e) {
+  console.log('dynamic import failed:', e);
+}
+```
+
+### 普通 JavaScript
+
+```html
+<!DOCTYPE html>
+<html>
+<head>
+    <title>UTM 参数追踪</title>
+</head>
+<body>
+    <h1>UTM 参数追踪</h1>
+    <button onclick="sendData()">发送数据</button>
+    <pre id="params"></pre>
+
+    <script src="https://openfpcdn.io/fingerprintjs/v4"></script>
+    <script src="dist/main.js"></script>
+    <script>
+        let tracker = null;
+
+        // 页面加载完成后初始化
+        document.addEventListener('DOMContentLoaded', async function() {
+            tracker = new window.UtmTracker({
+                reportUrl: 'https://your-api.com/collect',
+                autoSend: false,
+                onSend: function(response, params) {
+                    console.log('数据发送成功:', response);
+                    alert('数据发送成功!');
+                },
+                onError: function(error, params) {
+                    console.error('数据发送失败:', error);
+                    alert('数据发送失败!');
+                }
+            });
+
+            // 获取包含指纹的完整参数
+            try {
+                const params = await tracker.getParamsAsync();
+                document.getElementById('params').textContent = 
+                    JSON.stringify(params, null, 2);
+                console.log('参数获取成功:', params);
+            } catch (error) {
+                console.error('参数获取失败:', error);
+                // 如果异步获取失败,使用同步方法
+                const syncParams = tracker.getParams();
+                document.getElementById('params').textContent = 
+                    JSON.stringify(syncParams, null, 2);
+            }
+        });
+
+        async function sendData() {
+            if (tracker) {
+                await tracker.send();
+            }
+        }
+    </script>
+</body>
+</html>
+```
+
+## 🎯 浏览器指纹说明
+
+### 指纹生成原理
+
+本库使用两种指纹生成方式:
+
+1. **FingerprintJS 方式**(默认)
+   - 使用 FingerprintJS 库生成
+   - 基于多种浏览器特征
+   - 更准确和稳定
+
+2. **自定义稳定指纹**
+   - 基于设备固定特征
+   - 确保跨环境一致性
+   - 包含屏幕信息、用户代理、时区等
+
+### 指纹特征
+
+指纹生成基于以下稳定特征:
+- 屏幕分辨率、颜色深度
+- 用户代理字符串(前50字符)
+- 时区信息
+- 语言设置
+- 平台信息
+- CPU核心数
+- 设备内存
+- Canvas指纹
+- WebGL渲染器信息
+
+### 指纹存储
+
+- 指纹生成后存储在 `localStorage` 中
+- 键名:`fingerprint_id`
+- 后续请求优先使用已存储的指纹
+- 清除 localStorage 后重新生成
+
+## 🔄 跨环境一致性
+
+### 问题背景
+
+在不同项目环境下,同一设备可能生成不同的指纹ID,影响用户追踪的准确性。
+
+### 解决方案
+
+本库通过以下方式确保跨环境一致性:
+
+1. **使用稳定的设备特征**
+   - 避免使用可能变化的特征
+   - 对长字符串进行截取,减少细微差异
+
+2. **统一的指纹算法**
+   - 相同的哈希算法
+   - 相同的特征组合方式
+
+3. **环境无关性**
+   - 不依赖域名、路径等环境相关特征
+   - 专注于设备本身的稳定特征
+
+### 测试验证
+
+```javascript
+// 在项目A中
+const trackerA = new UtmTracker({ reportUrl: 'http://localhost:3000/api' });
+const paramsA = await trackerA.getParamsAsync();
+console.log('项目A指纹:', paramsA.fingerprint);
+
+// 在项目B中(同一设备)
+const trackerB = new UtmTracker({ reportUrl: 'http://localhost:3001/api' });
+const paramsB = await trackerB.getParamsAsync();
+console.log('项目B指纹:', paramsB.fingerprint);
+
+// 两个指纹应该相同
+console.log('指纹一致:', paramsA.fingerprint === paramsB.fingerprint);
+```
+
+## 🛠 高级配置
+
+### 自定义请求头
+
+```javascript
+const tracker = new UtmTracker({
+  reportUrl: 'https://your-api.com/collect',
+  headers: {
+    'Content-Type': 'application/json',
+    'Authorization': 'Bearer your-token',
+    'X-Custom-Header': 'custom-value'
+  }
+});
+```
+
+### 添加额外参数
+
+```javascript
+const tracker = new UtmTracker({
+  reportUrl: 'https://your-api.com/collect',
+  extra: {
+    userId: '12345',
+    sessionId: 'abc123',
+    customField: 'customValue'
+  }
+});
+```
 
+### 自定义回调处理
+
+```javascript
 const tracker = new UtmTracker({
-  reportUrl: 'https://your-server.com/collect',
-  autoSend: false,
-  method: 'POST',
-  headers: { 'Content-Type': 'application/json' },
-  extra: { foo: 'bar' },
-  onParams: params => console.log(params),
-  onSend: (res, params) => console.log('sent', res, params),
-  onError: (err, params) => console.error('error', err, params)
+  reportUrl: 'https://your-api.com/collect',
+  onParams: (params) => {
+    // 参数获取后的处理
+    console.log('获取到参数:', params);
+    
+    // 可以修改参数
+    params.customField = 'modified';
+    
+    // 可以存储到其他地方
+    localStorage.setItem('utm_params', JSON.stringify(params));
+  },
+  onSend: (response, params) => {
+    // 发送成功的处理
+    console.log('发送成功:', response);
+    
+    // 可以触发其他操作
+    if (response.success) {
+      showSuccessMessage('数据上报成功');
+    }
+  },
+  onError: (error, params) => {
+    // 发送失败的处理
+    console.error('发送失败:', error);
+    
+    // 可以重试或显示错误信息
+    showErrorMessage('数据上报失败,请重试');
+  }
 });
 ```
 
-## 浏览器指纹(fingerprintId)说明
+## 🔧 构建和部署
 
-- `fingerprintId` 字段会自动添加到所有 UTM 参数和上报数据中。
-- 该字段用于唯一标识当前浏览器环境,便于后端做用户去重、分析等。
-- 指纹生成后会存储在本地 localStorage(key: fingerprint_id),后续请求会优先复用,确保唯一性和高效性。
-- **本包已自动集成 [FingerprintJS](https://github.com/fingerprintjs/fingerprintjs),无需单独安装或引入,无论 npm 还是浏览器全局都能直接用指纹功能。**
+### 开发环境
 
-### 示例
+```bash
+# 安装依赖
+npm install
 
-```js
-const tracker = new UtmTracker({ ... });
-const params = await tracker.getParamsAsync();
-console.log(params.fingerprintId); // 浏览器唯一指纹
+# 构建项目
+npm run build
+
+# 运行测试
+npm test
 ```
 
-## FingerprintJS 依赖说明
+### 生产环境
 
-本库自动适配两种场景:
+```bash
+# 构建生产版本
+npm run build
 
-### 1. npm/打包工具用户
-- 请在你的项目中安装依赖:
-  ```bash
-  npm install @fingerprintjs/fingerprintjs
-  ```
-- 本库会自动 require 该依赖,无需手动引入。
-- 推荐在 package.json 中声明 peerDependencies:
-  ```json
-  "peerDependencies": {
-    "@fingerprintjs/fingerprintjs": ">=4.0.0"
-  }
-  ```
+# 构建后的文件在 dist/ 目录
+# - main.js: 主文件
+# - main.js.map: 源码映射文件
+```
+
+### Webpack 配置
+
+```javascript
+// webpack.config.js
+module.exports = {
+  entry: './src/index.js',
+  output: {
+    filename: 'main.js',
+    path: path.resolve(__dirname, 'dist'),
+    library: {
+      name: 'UtmTracker',
+      type: 'umd',
+    },
+    globalObject: 'this',
+  },
+  // ... 其他配置
+};
+```
+
+## 🐛 常见问题
+
+### Q: 指纹ID在不同环境下不一致怎么办?
+
+**A:** 确保使用相同版本的库,并检查是否有环境特定的配置影响了指纹生成。
+
+### Q: 如何自定义指纹生成算法?
+
+**A:** 可以修改源码中的 `generateStableFingerprint()` 函数,或通过配置参数调整 FingerprintJS 的设置。
+
+### Q: 上报失败如何处理?
+
+**A:** 使用 `onError` 回调函数处理错误,可以实现重试机制或错误提示。
+
+### Q: 如何禁用指纹功能?
+
+**A:** 目前不支持完全禁用指纹功能,但可以通过配置调整指纹生成方式。
+
+### Q: 支持哪些浏览器?
+
+**A:** 支持所有现代浏览器,包括 Chrome、Firefox、Safari、Edge 等,以及移动端浏览器。
 
-### 2. 浏览器全局用户
-- 请在你的 HTML 中**先**引入 FingerprintJS,再引入本库:
-  ```html
-  <script src="https://openfpcdn.io/fingerprintjs/v4"></script>
-  <script src="dist/your-utm-bundle.js"></script>
-  ```
-- 本库会自动检测 window.FingerprintJS。
 
-如未正确引入依赖,指纹功能将无法使用,fingerprintId 字段会为空字符串。
+**注意:** 使用本库时请遵守相关法律法规和隐私政策,确保用户数据的安全和隐私保护。
 

+ 1 - 0
demo-utm-vue/package.json

@@ -8,6 +8,7 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "@fingerprintjs/fingerprintjs": "^4.6.2",
     "core-js": "^3.8.3",
     "utm-params-extractor-test": "file:..",
     "vue": "^3.2.13"

+ 36 - 10
demo-utm-vue/src/App.vue

@@ -1,32 +1,58 @@
 <template>
-  <div style="padding: 24px;">
+  <div style="padding: 24px">
     <h1>UTM Params Extractor Demo (Vue)</h1>
-    <p>本页面演示如何在 Vue 项目中使用 <code>utm-params-extractor-test</code> 包。</p>
+    <p>
+      本页面演示如何在 Vue 项目中使用
+      <code>utm-params-extractor-test</code> 包。
+    </p>
     <h2>获取到的参数:</h2>
-    <pre style="background: #f6f8fa; padding: 16px; border-radius: 8px;">
+    <pre style="background: #f6f8fa; padding: 16px; border-radius: 8px">
       {{ utmJson }}
     </pre>
+    <button @click="sendUtm" style="margin-top: 16px; padding: 8px 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">
+      发送数据
+    </button>
   </div>
 </template>
 
 <script>
-import UtmTracker from 'utm-params-extractor-test';
+// 导入 UTM 参数提取器
+import "../../src/index.js";
 
 export default {
-  name: 'App',
+  name: "App",
   data() {
     return {
-      utm: null
+      utm: null,
+      tracker: null,
     };
   },
   computed: {
     utmJson() {
-      return this.utm ? JSON.stringify(this.utm, null, 2) : '正在获取...';
-    }
+      return this.utm ? JSON.stringify(this.utm, null, 2) : "正在获取...";
+    },
+  },
+  methods: {
+    sendUtm() {
+      if (this.tracker) {
+        this.tracker.send();
+      } else {
+        console.warn("UtmTracker 尚未初始化");
+      }
+    },
   },
   mounted() {
-    this.utm = UtmTracker.get();
-  }
+    // 确保 UtmTracker 在全局可用
+    if (typeof window.UtmTracker !== 'undefined') {
+      this.tracker = new window.UtmTracker({
+        reportUrl: "http://192.168.3.17:9999/marketing/data/report",
+        autoSend: false,
+      });
+      this.utm = this.tracker.getParams();
+    } else {
+      console.error('UtmTracker 未找到,请检查导入是否正确');
+    }
+  },
 };
 </script>
 

+ 1 - 0
demo-utm-vue3/package.json

@@ -9,6 +9,7 @@
     "preview": "vite preview"
   },
   "dependencies": {
+    "@fingerprintjs/fingerprintjs": "^4.6.2",
     "utm-params-extractor-test": "^1.0.12",
     "vue": "^3.5.17"
   },

+ 11 - 6
demo-utm-vue3/src/App.vue

@@ -3,17 +3,22 @@
 <script setup>
 import { ref, computed, onMounted } from 'vue'
 // import UtmTracker from 'utm-params-extractor-test'
-import '../../src/index'
+import '../../src/index.js'
 
 const utm = ref(null)
 let tracker = null
 
 onMounted(() => {
-  tracker = new UtmTracker({
-    reportUrl: 'http://192.168.3.17:9999/marketing/data/report',
-    autoSend: false
-  });
-  utm.value = tracker.getParams()
+  // 确保 UtmTracker 在全局可用
+  if (typeof window.UtmTracker !== 'undefined') {
+    tracker = new window.UtmTracker({
+      reportUrl: 'http://192.168.3.17:9999/marketing/data/report',
+      autoSend: false
+    });
+    utm.value = tracker.getParams()
+  } else {
+    console.error('UtmTracker 未找到,请检查导入是否正确')
+  }
 })
 
 const utmJson = computed(() => utm.value ? JSON.stringify(utm.value, null, 2) : '正在获取...')

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "utm-params-extractor-test",
-  "version": "1.0.12",
+  "version": "1.0.16",
   "description": "Extract UTM parameters from URL and browser information",
   "main": "dist/main.js",
   "scripts": {

+ 171 - 31
src/index.js

@@ -32,6 +32,7 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
     function getBrowserInfo() {
         var ua = navigator.userAgent;
         var browser = 'Unknown';
+        var browserVersion = 'Unknown';
         var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua);
         var osType = 'Unknown';
         var osVersion = 'Unknown';
@@ -59,35 +60,76 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
             }
         }
 
-        // 浏览器类型判断
+        // 浏览器类型和版本判断
         if (/Edg/i.test(ua)) {
             browser = 'Edge';
+            var edgeVersionMatch = ua.match(/Edg\/([\d.]+)/i);
+            if (edgeVersionMatch) {
+                browserVersion = edgeVersionMatch[1];
+            }
         } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) {
             browser = 'HuaweiBrowser';
+            var huaweiVersionMatch = ua.match(/HuaweiBrowser\/([\d.]+)/i) || ua.match(/HMS\/([\d.]+)/i);
+            if (huaweiVersionMatch) {
+                browserVersion = huaweiVersionMatch[1];
+            }
         } else if (/Chrome/i.test(ua)) {
             browser = 'Chrome';
+            var chromeVersionMatch = ua.match(/Chrome\/([\d.]+)/i);
+            if (chromeVersionMatch) {
+                browserVersion = chromeVersionMatch[1];
+            }
         } else if (/Firefox/i.test(ua)) {
             browser = 'Firefox';
+            var firefoxVersionMatch = ua.match(/Firefox\/([\d.]+)/i);
+            if (firefoxVersionMatch) {
+                browserVersion = firefoxVersionMatch[1];
+            }
         } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) {
             browser = 'Safari';
+            var safariVersionMatch = ua.match(/Version\/([\d.]+)/i);
+            if (safariVersionMatch) {
+                browserVersion = safariVersionMatch[1];
+            }
         } else if (/MSIE|Trident/i.test(ua)) {
             browser = 'IE';
+            var ieVersionMatch = ua.match(/MSIE ([\d.]+)/i) || ua.match(/Trident\/[\d.]+; rv:([\d.]+)/i);
+            if (ieVersionMatch) {
+                browserVersion = ieVersionMatch[1];
+            }
         }
 
         // 特殊浏览器环境检测
         if (/MicroMessenger/i.test(ua)) {
             browser = 'WeChat';
+            var wechatVersionMatch = ua.match(/MicroMessenger\/([\d.]+)/i);
+            if (wechatVersionMatch) {
+                browserVersion = wechatVersionMatch[1];
+            }
         } else if (/QQBrowser/i.test(ua)) {
             browser = 'QQBrowser';
+            var qqVersionMatch = ua.match(/QQBrowser\/([\d.]+)/i);
+            if (qqVersionMatch) {
+                browserVersion = qqVersionMatch[1];
+            }
         } else if (/UCBrowser/i.test(ua)) {
             browser = 'UCBrowser';
+            var ucVersionMatch = ua.match(/UCBrowser\/([\d.]+)/i);
+            if (ucVersionMatch) {
+                browserVersion = ucVersionMatch[1];
+            }
         } else if (/Telegram/i.test(ua)) {
             browser = 'Telegram';
+            var telegramVersionMatch = ua.match(/Telegram\/([\d.]+)/i);
+            if (telegramVersionMatch) {
+                browserVersion = telegramVersionMatch[1];
+            }
         }
 
         return {
             isMobile: isMobile,
             browser: browser,
+            browserVersion: browserVersion,
             userAgent: ua,
             osType: osType,
             osVersion: osVersion
@@ -148,6 +190,7 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
             domain: window.location.hostname,
             isMobile: browser.isMobile,
             browser: browser.browser,
+            browserVersion: browser.browserVersion,
             userAgent: browser.userAgent,
             osType: browser.osType,
             osVersion: browser.osVersion
@@ -163,39 +206,99 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
         return result;
     };
 
-    const USE_FAKE_FINGERPRINT = true; // 切换开关
+    const USE_FAKE_FINGERPRINT = false; // 切换开关,使用自定义稳定指纹生成
+
+    function generateStableFingerprint() {
+        // 基于设备固定特征生成稳定的指纹ID
+        // 只使用最稳定的设备特征,避免环境差异
+
+        // 1. 屏幕信息(最稳定)
+        const screenInfo = `${screen.width}x${screen.height}x${screen.colorDepth}`;
+
+        // 2. 用户代理(稳定,但可能因浏览器版本变化)
+        const userAgent = navigator.userAgent || '';
 
-    function generateIdList() {
-        const arr = [];
-        for (let i = 0; i < 10; i++) {
-            arr.push('id_' + Math.random().toString(36).substr(2, 10) + Date.now() + '_' + i);
+        // 3. 时区(稳定)
+        const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || '';
+
+        // 4. 语言(稳定)
+        const language = navigator.language || '';
+
+        // 5. 平台(稳定)
+        const platform = navigator.platform || '';
+
+        // 6. 硬件信息(稳定)
+        const hardwareConcurrency = navigator.hardwareConcurrency || '0';
+        const deviceMemory = navigator.deviceMemory || '0';
+
+        // 7. Canvas指纹(相对稳定)
+        let canvasFingerprint = '';
+        try {
+            const canvas = document.createElement('canvas');
+            const ctx = canvas.getContext('2d');
+            ctx.textBaseline = 'top';
+            ctx.font = '14px Arial';
+            ctx.fillText('Stable fingerprint', 2, 2);
+            canvasFingerprint = canvas.toDataURL().substring(0, 100); // 只取前100个字符
+        } catch (e) {
+            canvasFingerprint = 'canvas_error';
         }
-        return arr;
-    }
 
-    function getFixedIdList() {
-        let idList = [];
+        // 8. WebGL信息(稳定)
+        let webglFingerprint = '';
         try {
-            idList = JSON.parse(localStorage.getItem('fingerprint_id_list') || '[]');
-        } catch (e) { }
-        if (!Array.isArray(idList) || idList.length !== 10) {
-            idList = generateIdList();
-            try {
-                localStorage.setItem('fingerprint_id_list', JSON.stringify(idList));
-            } catch (e) { }
+            const canvas = document.createElement('canvas');
+            const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
+            if (gl) {
+                const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
+                if (debugInfo) {
+                    webglFingerprint = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
+                }
+            }
+        } catch (e) {
+            webglFingerprint = 'webgl_error';
+        }
+
+        // 组合最稳定的特征,按重要性排序
+        const stableFeatures = [
+            screenInfo,
+            userAgent.substring(0, 50), // 只取前50个字符,减少版本差异
+            timezone,
+            language,
+            platform,
+            hardwareConcurrency,
+            deviceMemory,
+            canvasFingerprint,
+            webglFingerprint
+        ].join('|');
+
+        // 使用更稳定的哈希算法
+        let hash = 0;
+        for (let i = 0; i < stableFeatures.length; i++) {
+            const char = stableFeatures.charCodeAt(i);
+            hash = ((hash << 5) - hash) + char;
+            hash = hash & hash; // 转换为32位整数
         }
-        return idList;
+
+        // 返回稳定的指纹ID
+        return 'stable_fp_' + Math.abs(hash).toString(36);
     }
 
     async function getFingerprintId() {
         if (USE_FAKE_FINGERPRINT) {
-            // 每次都从固定的10个ID里随机取一个
-            const idList = getFixedIdList();
-            const idx = Math.floor(Math.random() * 10);
-            const fingerprint = idList[idx];
+            // 先检查本地存储中是否已有指纹
+            let fingerprint = '';
             try {
-                localStorage.setItem('fingerprint_id', fingerprint);
+                fingerprint = localStorage.getItem('fingerprint_id') || '';
             } catch (e) { }
+
+            // 如果没有指纹,才生成新的
+            if (!fingerprint) {
+                fingerprint = generateStableFingerprint();
+                try {
+                    localStorage.setItem('fingerprint_id', fingerprint);
+                } catch (e) { }
+            }
             return fingerprint;
         } else {
             // 原FingerprintJS方案
@@ -207,7 +310,19 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
                 return fingerprint;
             } else if (FingerprintJS && typeof FingerprintJS.load === 'function') {
                 try {
-                    const fp = await FingerprintJS.load();
+                    const fp = await FingerprintJS.load({
+                        // 去掉项目区分和域名区分
+                        preprocessor: null,
+                        // 禁用域名相关的指纹组件
+                        components: {
+                            domain: false,
+                            subdomain: false,
+                            path: false,
+                            queryKey: false,
+                            queryValue: false,
+                            hash: false
+                        }
+                    });
                     const result = await fp.get();
                     fingerprint = result.visitorId;
                     try {
@@ -524,17 +639,42 @@ import FingerprintJS from '@fingerprintjs/fingerprintjs';
         try {
             fingerprint = localStorage.getItem('fingerprint_id') || '';
         } catch (e) { }
-        if (!fingerprint && FingerprintJS && typeof FingerprintJS.load === 'function') {
-            try {
-                const fp = await FingerprintJS.load();
-                const result = await fp.get();
-                fingerprint = result.visitorId;
+        if (!fingerprint) {
+            if (USE_FAKE_FINGERPRINT) {
+                // 使用新的生成方式
+                fingerprint = generateStableFingerprint();
                 try {
                     localStorage.setItem('fingerprint_id', fingerprint);
                 } catch (e) { }
-            } catch (e) { }
+            } else if (FingerprintJS && typeof FingerprintJS.load === 'function') {
+                try {
+                    const fp = await FingerprintJS.load({
+                        // 去掉项目区分和域名区分
+                        preprocessor: null,
+                        // 禁用域名相关的指纹组件
+                        components: {
+                            domain: false,
+                            subdomain: false,
+                            path: false,
+                            queryKey: false,
+                            queryValue: false,
+                            hash: false
+                        }
+                    });
+                    const result = await fp.get();
+                    fingerprint = result.visitorId;
+                    try {
+                        localStorage.setItem('fingerprint_id', fingerprint);
+                    } catch (e) { }
+                } catch (e) { }
+            }
         }
     })();
 
     return UtmTracker;
-}));
+}));
+
+// 添加ES模块默认导出支持
+if (typeof module !== 'undefined' && module.exports) {
+    module.exports = module.exports.default || module.exports;
+}

+ 2 - 0
webpack.config.js

@@ -12,6 +12,8 @@ module.exports = {
       type: 'umd',
     },
     globalObject: 'this',
+    // 添加ES模块支持
+    libraryExport: 'default',
   },
   mode: 'production',
   module: {