@gctrack/behavior 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,15 +1,39 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- let _gctrack_core = require("@gctrack/core");
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ const __gctrack_core = __toESM(require("@gctrack/core"));
25
+
3
26
  //#region src/index.ts
4
27
  /**
5
28
  * 行为监控插件
6
29
  */
7
30
  var BehaviorMonitor = class {
31
+ tracker;
32
+ sessionStartTime = Date.now();
33
+ pageStartTime = Date.now();
34
+ clickCount = 0;
35
+ scrollDepth = 0;
8
36
  constructor(tracker) {
9
- this.sessionStartTime = Date.now();
10
- this.pageStartTime = Date.now();
11
- this.clickCount = 0;
12
- this.scrollDepth = 0;
13
37
  this.tracker = tracker;
14
38
  }
15
39
  /**
@@ -86,7 +110,8 @@ var BehaviorMonitor = class {
86
110
  * 获取元素文本内容
87
111
  */
88
112
  getElementText(element) {
89
- return (element.innerText || element.textContent || "").trim().substring(0, 50);
113
+ const text = element.innerText || element.textContent || "";
114
+ return text.trim().substring(0, 50);
90
115
  }
91
116
  /**
92
117
  * 获取元素路径
@@ -115,13 +140,14 @@ var BehaviorMonitor = class {
115
140
  */
116
141
  isImportantElement(element) {
117
142
  const tagName = element.tagName.toLowerCase();
118
- if ([
143
+ const importantTags = [
119
144
  "a",
120
145
  "button",
121
146
  "input",
122
147
  "select",
123
148
  "textarea"
124
- ].includes(tagName)) return true;
149
+ ];
150
+ if (importantTags.includes(tagName)) return true;
125
151
  if (element.onclick || element.getAttribute("onclick")) return true;
126
152
  if (element.hasAttribute("data-track") || element.classList.contains("track-click")) return true;
127
153
  return false;
@@ -130,7 +156,7 @@ var BehaviorMonitor = class {
130
156
  * 追踪滚动深度
131
157
  */
132
158
  trackScroll() {
133
- const handleScroll = (0, _gctrack_core.throttle)(() => {
159
+ const handleScroll = (0, __gctrack_core.throttle)(() => {
134
160
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
135
161
  const scrollHeight = document.documentElement.scrollHeight;
136
162
  const clientHeight = window.innerHeight;
@@ -170,13 +196,14 @@ var BehaviorMonitor = class {
170
196
  });
171
197
  }, 6e4);
172
198
  };
173
- [
199
+ const events = [
174
200
  "mousedown",
175
201
  "mousemove",
176
202
  "keypress",
177
203
  "scroll",
178
204
  "touchstart"
179
- ].forEach((event) => {
205
+ ];
206
+ events.forEach((event) => {
180
207
  document.addEventListener(event, resetInactiveTimer, true);
181
208
  });
182
209
  resetInactiveTimer();
@@ -237,8 +264,8 @@ var BehaviorMonitor = class {
237
264
  function createBehaviorMonitor(tracker) {
238
265
  return new BehaviorMonitor(tracker);
239
266
  }
267
+
240
268
  //#endregion
241
269
  exports.BehaviorMonitor = BehaviorMonitor;
242
270
  exports.createBehaviorMonitor = createBehaviorMonitor;
243
-
244
271
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Tracker } from '@gctrack/core'\nimport { throttle } from '@gctrack/core'\n\n/**\n * 行为监控插件\n */\nexport class BehaviorMonitor {\n private tracker: Tracker\n private sessionStartTime = Date.now()\n private pageStartTime = Date.now()\n private clickCount = 0\n private scrollDepth = 0\n\n constructor(tracker: Tracker) {\n this.tracker = tracker\n }\n\n /**\n * 启动行为监控\n */\n start(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n this.trackPageView()\n this.trackPageStay()\n this.trackClick()\n this.trackScroll()\n this.trackVisibility()\n this.trackUserEngagement()\n }\n\n /**\n * 追踪页面浏览\n */\n private trackPageView(): void {\n this.tracker.track('page_view', {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪页面停留时间\n */\n private trackPageStay(): void {\n // 页面卸载时上报停留时间\n const reportStayTime = () => {\n const stayTime = Date.now() - this.pageStartTime\n\n this.tracker.track('page_stay', {\n stayTime,\n clickCount: this.clickCount,\n maxScrollDepth: this.scrollDepth,\n timestamp: Date.now(),\n })\n }\n\n window.addEventListener('beforeunload', reportStayTime)\n\n // 页面隐藏时也上报(移动端)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n reportStayTime()\n }\n else if (document.visibilityState === 'visible') {\n // 页面重新可见时重置计时\n this.pageStartTime = Date.now()\n this.clickCount = 0\n }\n })\n }\n\n /**\n * 追踪点击行为\n */\n private trackClick(): void {\n document.addEventListener(\n 'click',\n (event) => {\n this.clickCount++\n\n const target = event.target as HTMLElement\n const tagName = target.tagName.toLowerCase()\n\n // 获取元素的唯一标识\n const elementId = target.id || ''\n const elementClass = target.className || ''\n const elementText = this.getElementText(target)\n const elementPath = this.getElementPath(target)\n\n // 只上报重要元素的点击\n if (this.isImportantElement(target)) {\n this.tracker.track('click', {\n tagName,\n elementId,\n elementClass,\n elementText,\n elementPath,\n x: event.clientX,\n y: event.clientY,\n timestamp: Date.now(),\n })\n }\n },\n true,\n )\n }\n\n /**\n * 获取元素文本内容\n */\n private getElementText(element: HTMLElement): string {\n const text = element.innerText || element.textContent || ''\n return text.trim().substring(0, 50) // 限制长度\n }\n\n /**\n * 获取元素路径\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = []\n let current: HTMLElement | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n if (current.id) {\n selector += `#${current.id}`\n path.unshift(selector)\n break // ID 是唯一的,可以停止\n }\n else if (current.className) {\n const classes = current.className.split(' ').filter(c => c).join('.')\n if (classes) {\n selector += `.${classes}`\n }\n }\n\n path.unshift(selector)\n current = current.parentElement\n\n // 限制路径深度\n if (path.length >= 5)\n break\n }\n\n return path.join(' > ')\n }\n\n /**\n * 判断是否为重要元素\n */\n private isImportantElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase()\n const importantTags = ['a', 'button', 'input', 'select', 'textarea']\n\n // 标签类型判断\n if (importantTags.includes(tagName)) {\n return true\n }\n\n // 带有点击事件的元素\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true\n }\n\n // 带有特定 class 或 data 属性的元素\n if (\n element.hasAttribute('data-track')\n || element.classList.contains('track-click')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * 追踪滚动深度\n */\n private trackScroll(): void {\n const handleScroll = throttle(() => {\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop\n const scrollHeight = document.documentElement.scrollHeight\n const clientHeight = window.innerHeight\n\n const scrollPercent = Math.round((scrollTop / (scrollHeight - clientHeight)) * 100)\n\n // 更新最大滚动深度\n if (scrollPercent > this.scrollDepth) {\n this.scrollDepth = scrollPercent\n\n // 每 25% 上报一次\n if (scrollPercent >= 25 && scrollPercent % 25 === 0) {\n this.tracker.track('scroll_depth', {\n depth: scrollPercent,\n timestamp: Date.now(),\n })\n }\n }\n }, 300)\n\n window.addEventListener('scroll', handleScroll)\n }\n\n /**\n * 追踪页面可见性\n */\n private trackVisibility(): void {\n document.addEventListener('visibilitychange', () => {\n this.tracker.track('visibility_change', {\n state: document.visibilityState,\n timestamp: Date.now(),\n })\n })\n }\n\n /**\n * 追踪用户参与度\n */\n private trackUserEngagement(): void {\n let inactiveTimer: ReturnType<typeof setTimeout> | null = null\n\n const resetInactiveTimer = () => {\n if (inactiveTimer) {\n clearTimeout(inactiveTimer)\n }\n\n inactiveTimer = setTimeout(() => {\n this.tracker.track('user_inactive', {\n inactiveDuration: 60000, // 1 分钟\n timestamp: Date.now(),\n })\n }, 60000)\n }\n\n // 监听用户活动\n const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']\n events.forEach((event) => {\n document.addEventListener(event, resetInactiveTimer, true)\n })\n\n resetInactiveTimer()\n }\n\n /**\n * 追踪表单交互\n */\n trackFormInteraction(formName: string): {\n onFocus: () => void\n onSubmit: (success: boolean, data?: Record<string, any>) => void\n onAbandon: (fieldName: string) => void\n } {\n return {\n // 表单开始填写\n onFocus: () => {\n this.tracker.track('form_start', {\n formName,\n timestamp: Date.now(),\n })\n },\n\n // 表单提交\n onSubmit: (success: boolean, data: Record<string, any> = {}) => {\n this.tracker.track('form_submit', {\n formName,\n success,\n ...data,\n timestamp: Date.now(),\n })\n },\n\n // 表单放弃\n onAbandon: (fieldName: string) => {\n this.tracker.track('form_abandon', {\n formName,\n fieldName,\n timestamp: Date.now(),\n })\n },\n }\n }\n\n /**\n * 追踪搜索行为\n */\n trackSearch(keyword: string, resultCount: number): void {\n this.tracker.track('search', {\n keyword,\n resultCount,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪视频播放\n */\n trackVideo(videoId: string, action: string, data: Record<string, any> = {}): void {\n this.tracker.track('video_action', {\n videoId,\n action, // play, pause, ended, error\n ...data,\n timestamp: Date.now(),\n })\n }\n}\n\n/**\n * 创建行为监控实例\n */\nexport function createBehaviorMonitor(tracker: Tracker): BehaviorMonitor {\n return new BehaviorMonitor(tracker)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["tracker: Tracker","element: HTMLElement","path: string[]","current: HTMLElement | null","inactiveTimer: ReturnType<typeof setTimeout> | null","formName: string","success: boolean","data: Record<string, any>","fieldName: string","keyword: string","resultCount: number","videoId: string","action: string"],"sources":["../src/index.ts"],"sourcesContent":["import type { Tracker } from '@gctrack/core'\nimport { throttle } from '@gctrack/core'\n\n/**\n * 行为监控插件\n */\nexport class BehaviorMonitor {\n private tracker: Tracker\n private sessionStartTime = Date.now()\n private pageStartTime = Date.now()\n private clickCount = 0\n private scrollDepth = 0\n\n constructor(tracker: Tracker) {\n this.tracker = tracker\n }\n\n /**\n * 启动行为监控\n */\n start(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n this.trackPageView()\n this.trackPageStay()\n this.trackClick()\n this.trackScroll()\n this.trackVisibility()\n this.trackUserEngagement()\n }\n\n /**\n * 追踪页面浏览\n */\n private trackPageView(): void {\n this.tracker.track('page_view', {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪页面停留时间\n */\n private trackPageStay(): void {\n // 页面卸载时上报停留时间\n const reportStayTime = () => {\n const stayTime = Date.now() - this.pageStartTime\n\n this.tracker.track('page_stay', {\n stayTime,\n clickCount: this.clickCount,\n maxScrollDepth: this.scrollDepth,\n timestamp: Date.now(),\n })\n }\n\n window.addEventListener('beforeunload', reportStayTime)\n\n // 页面隐藏时也上报(移动端)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n reportStayTime()\n }\n else if (document.visibilityState === 'visible') {\n // 页面重新可见时重置计时\n this.pageStartTime = Date.now()\n this.clickCount = 0\n }\n })\n }\n\n /**\n * 追踪点击行为\n */\n private trackClick(): void {\n document.addEventListener(\n 'click',\n (event) => {\n this.clickCount++\n\n const target = event.target as HTMLElement\n const tagName = target.tagName.toLowerCase()\n\n // 获取元素的唯一标识\n const elementId = target.id || ''\n const elementClass = target.className || ''\n const elementText = this.getElementText(target)\n const elementPath = this.getElementPath(target)\n\n // 只上报重要元素的点击\n if (this.isImportantElement(target)) {\n this.tracker.track('click', {\n tagName,\n elementId,\n elementClass,\n elementText,\n elementPath,\n x: event.clientX,\n y: event.clientY,\n timestamp: Date.now(),\n })\n }\n },\n true,\n )\n }\n\n /**\n * 获取元素文本内容\n */\n private getElementText(element: HTMLElement): string {\n const text = element.innerText || element.textContent || ''\n return text.trim().substring(0, 50) // 限制长度\n }\n\n /**\n * 获取元素路径\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = []\n let current: HTMLElement | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n if (current.id) {\n selector += `#${current.id}`\n path.unshift(selector)\n break // ID 是唯一的,可以停止\n }\n else if (current.className) {\n const classes = current.className.split(' ').filter(c => c).join('.')\n if (classes) {\n selector += `.${classes}`\n }\n }\n\n path.unshift(selector)\n current = current.parentElement\n\n // 限制路径深度\n if (path.length >= 5)\n break\n }\n\n return path.join(' > ')\n }\n\n /**\n * 判断是否为重要元素\n */\n private isImportantElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase()\n const importantTags = ['a', 'button', 'input', 'select', 'textarea']\n\n // 标签类型判断\n if (importantTags.includes(tagName)) {\n return true\n }\n\n // 带有点击事件的元素\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true\n }\n\n // 带有特定 class 或 data 属性的元素\n if (\n element.hasAttribute('data-track')\n || element.classList.contains('track-click')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * 追踪滚动深度\n */\n private trackScroll(): void {\n const handleScroll = throttle(() => {\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop\n const scrollHeight = document.documentElement.scrollHeight\n const clientHeight = window.innerHeight\n\n const scrollPercent = Math.round((scrollTop / (scrollHeight - clientHeight)) * 100)\n\n // 更新最大滚动深度\n if (scrollPercent > this.scrollDepth) {\n this.scrollDepth = scrollPercent\n\n // 每 25% 上报一次\n if (scrollPercent >= 25 && scrollPercent % 25 === 0) {\n this.tracker.track('scroll_depth', {\n depth: scrollPercent,\n timestamp: Date.now(),\n })\n }\n }\n }, 300)\n\n window.addEventListener('scroll', handleScroll)\n }\n\n /**\n * 追踪页面可见性\n */\n private trackVisibility(): void {\n document.addEventListener('visibilitychange', () => {\n this.tracker.track('visibility_change', {\n state: document.visibilityState,\n timestamp: Date.now(),\n })\n })\n }\n\n /**\n * 追踪用户参与度\n */\n private trackUserEngagement(): void {\n let inactiveTimer: ReturnType<typeof setTimeout> | null = null\n\n const resetInactiveTimer = () => {\n if (inactiveTimer) {\n clearTimeout(inactiveTimer)\n }\n\n inactiveTimer = setTimeout(() => {\n this.tracker.track('user_inactive', {\n inactiveDuration: 60000, // 1 分钟\n timestamp: Date.now(),\n })\n }, 60000)\n }\n\n // 监听用户活动\n const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']\n events.forEach((event) => {\n document.addEventListener(event, resetInactiveTimer, true)\n })\n\n resetInactiveTimer()\n }\n\n /**\n * 追踪表单交互\n */\n trackFormInteraction(formName: string): {\n onFocus: () => void\n onSubmit: (success: boolean, data?: Record<string, any>) => void\n onAbandon: (fieldName: string) => void\n } {\n return {\n // 表单开始填写\n onFocus: () => {\n this.tracker.track('form_start', {\n formName,\n timestamp: Date.now(),\n })\n },\n\n // 表单提交\n onSubmit: (success: boolean, data: Record<string, any> = {}) => {\n this.tracker.track('form_submit', {\n formName,\n success,\n ...data,\n timestamp: Date.now(),\n })\n },\n\n // 表单放弃\n onAbandon: (fieldName: string) => {\n this.tracker.track('form_abandon', {\n formName,\n fieldName,\n timestamp: Date.now(),\n })\n },\n }\n }\n\n /**\n * 追踪搜索行为\n */\n trackSearch(keyword: string, resultCount: number): void {\n this.tracker.track('search', {\n keyword,\n resultCount,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪视频播放\n */\n trackVideo(videoId: string, action: string, data: Record<string, any> = {}): void {\n this.tracker.track('video_action', {\n videoId,\n action, // play, pause, ended, error\n ...data,\n timestamp: Date.now(),\n })\n }\n}\n\n/**\n * 创建行为监控实例\n */\nexport function createBehaviorMonitor(tracker: Tracker): BehaviorMonitor {\n return new BehaviorMonitor(tracker)\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
- import "node:module";
2
1
  import { throttle } from "@gctrack/core";
2
+
3
3
  //#region src/index.ts
4
4
  /**
5
5
  * 行为监控插件
6
6
  */
7
7
  var BehaviorMonitor = class {
8
+ tracker;
9
+ sessionStartTime = Date.now();
10
+ pageStartTime = Date.now();
11
+ clickCount = 0;
12
+ scrollDepth = 0;
8
13
  constructor(tracker) {
9
- this.sessionStartTime = Date.now();
10
- this.pageStartTime = Date.now();
11
- this.clickCount = 0;
12
- this.scrollDepth = 0;
13
14
  this.tracker = tracker;
14
15
  }
15
16
  /**
@@ -86,7 +87,8 @@ var BehaviorMonitor = class {
86
87
  * 获取元素文本内容
87
88
  */
88
89
  getElementText(element) {
89
- return (element.innerText || element.textContent || "").trim().substring(0, 50);
90
+ const text = element.innerText || element.textContent || "";
91
+ return text.trim().substring(0, 50);
90
92
  }
91
93
  /**
92
94
  * 获取元素路径
@@ -115,13 +117,14 @@ var BehaviorMonitor = class {
115
117
  */
116
118
  isImportantElement(element) {
117
119
  const tagName = element.tagName.toLowerCase();
118
- if ([
120
+ const importantTags = [
119
121
  "a",
120
122
  "button",
121
123
  "input",
122
124
  "select",
123
125
  "textarea"
124
- ].includes(tagName)) return true;
126
+ ];
127
+ if (importantTags.includes(tagName)) return true;
125
128
  if (element.onclick || element.getAttribute("onclick")) return true;
126
129
  if (element.hasAttribute("data-track") || element.classList.contains("track-click")) return true;
127
130
  return false;
@@ -170,13 +173,14 @@ var BehaviorMonitor = class {
170
173
  });
171
174
  }, 6e4);
172
175
  };
173
- [
176
+ const events = [
174
177
  "mousedown",
175
178
  "mousemove",
176
179
  "keypress",
177
180
  "scroll",
178
181
  "touchstart"
179
- ].forEach((event) => {
182
+ ];
183
+ events.forEach((event) => {
180
184
  document.addEventListener(event, resetInactiveTimer, true);
181
185
  });
182
186
  resetInactiveTimer();
@@ -237,7 +241,7 @@ var BehaviorMonitor = class {
237
241
  function createBehaviorMonitor(tracker) {
238
242
  return new BehaviorMonitor(tracker);
239
243
  }
244
+
240
245
  //#endregion
241
246
  export { BehaviorMonitor, createBehaviorMonitor };
242
-
243
247
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { Tracker } from '@gctrack/core'\nimport { throttle } from '@gctrack/core'\n\n/**\n * 行为监控插件\n */\nexport class BehaviorMonitor {\n private tracker: Tracker\n private sessionStartTime = Date.now()\n private pageStartTime = Date.now()\n private clickCount = 0\n private scrollDepth = 0\n\n constructor(tracker: Tracker) {\n this.tracker = tracker\n }\n\n /**\n * 启动行为监控\n */\n start(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n this.trackPageView()\n this.trackPageStay()\n this.trackClick()\n this.trackScroll()\n this.trackVisibility()\n this.trackUserEngagement()\n }\n\n /**\n * 追踪页面浏览\n */\n private trackPageView(): void {\n this.tracker.track('page_view', {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪页面停留时间\n */\n private trackPageStay(): void {\n // 页面卸载时上报停留时间\n const reportStayTime = () => {\n const stayTime = Date.now() - this.pageStartTime\n\n this.tracker.track('page_stay', {\n stayTime,\n clickCount: this.clickCount,\n maxScrollDepth: this.scrollDepth,\n timestamp: Date.now(),\n })\n }\n\n window.addEventListener('beforeunload', reportStayTime)\n\n // 页面隐藏时也上报(移动端)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n reportStayTime()\n }\n else if (document.visibilityState === 'visible') {\n // 页面重新可见时重置计时\n this.pageStartTime = Date.now()\n this.clickCount = 0\n }\n })\n }\n\n /**\n * 追踪点击行为\n */\n private trackClick(): void {\n document.addEventListener(\n 'click',\n (event) => {\n this.clickCount++\n\n const target = event.target as HTMLElement\n const tagName = target.tagName.toLowerCase()\n\n // 获取元素的唯一标识\n const elementId = target.id || ''\n const elementClass = target.className || ''\n const elementText = this.getElementText(target)\n const elementPath = this.getElementPath(target)\n\n // 只上报重要元素的点击\n if (this.isImportantElement(target)) {\n this.tracker.track('click', {\n tagName,\n elementId,\n elementClass,\n elementText,\n elementPath,\n x: event.clientX,\n y: event.clientY,\n timestamp: Date.now(),\n })\n }\n },\n true,\n )\n }\n\n /**\n * 获取元素文本内容\n */\n private getElementText(element: HTMLElement): string {\n const text = element.innerText || element.textContent || ''\n return text.trim().substring(0, 50) // 限制长度\n }\n\n /**\n * 获取元素路径\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = []\n let current: HTMLElement | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n if (current.id) {\n selector += `#${current.id}`\n path.unshift(selector)\n break // ID 是唯一的,可以停止\n }\n else if (current.className) {\n const classes = current.className.split(' ').filter(c => c).join('.')\n if (classes) {\n selector += `.${classes}`\n }\n }\n\n path.unshift(selector)\n current = current.parentElement\n\n // 限制路径深度\n if (path.length >= 5)\n break\n }\n\n return path.join(' > ')\n }\n\n /**\n * 判断是否为重要元素\n */\n private isImportantElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase()\n const importantTags = ['a', 'button', 'input', 'select', 'textarea']\n\n // 标签类型判断\n if (importantTags.includes(tagName)) {\n return true\n }\n\n // 带有点击事件的元素\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true\n }\n\n // 带有特定 class 或 data 属性的元素\n if (\n element.hasAttribute('data-track')\n || element.classList.contains('track-click')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * 追踪滚动深度\n */\n private trackScroll(): void {\n const handleScroll = throttle(() => {\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop\n const scrollHeight = document.documentElement.scrollHeight\n const clientHeight = window.innerHeight\n\n const scrollPercent = Math.round((scrollTop / (scrollHeight - clientHeight)) * 100)\n\n // 更新最大滚动深度\n if (scrollPercent > this.scrollDepth) {\n this.scrollDepth = scrollPercent\n\n // 每 25% 上报一次\n if (scrollPercent >= 25 && scrollPercent % 25 === 0) {\n this.tracker.track('scroll_depth', {\n depth: scrollPercent,\n timestamp: Date.now(),\n })\n }\n }\n }, 300)\n\n window.addEventListener('scroll', handleScroll)\n }\n\n /**\n * 追踪页面可见性\n */\n private trackVisibility(): void {\n document.addEventListener('visibilitychange', () => {\n this.tracker.track('visibility_change', {\n state: document.visibilityState,\n timestamp: Date.now(),\n })\n })\n }\n\n /**\n * 追踪用户参与度\n */\n private trackUserEngagement(): void {\n let inactiveTimer: ReturnType<typeof setTimeout> | null = null\n\n const resetInactiveTimer = () => {\n if (inactiveTimer) {\n clearTimeout(inactiveTimer)\n }\n\n inactiveTimer = setTimeout(() => {\n this.tracker.track('user_inactive', {\n inactiveDuration: 60000, // 1 分钟\n timestamp: Date.now(),\n })\n }, 60000)\n }\n\n // 监听用户活动\n const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']\n events.forEach((event) => {\n document.addEventListener(event, resetInactiveTimer, true)\n })\n\n resetInactiveTimer()\n }\n\n /**\n * 追踪表单交互\n */\n trackFormInteraction(formName: string): {\n onFocus: () => void\n onSubmit: (success: boolean, data?: Record<string, any>) => void\n onAbandon: (fieldName: string) => void\n } {\n return {\n // 表单开始填写\n onFocus: () => {\n this.tracker.track('form_start', {\n formName,\n timestamp: Date.now(),\n })\n },\n\n // 表单提交\n onSubmit: (success: boolean, data: Record<string, any> = {}) => {\n this.tracker.track('form_submit', {\n formName,\n success,\n ...data,\n timestamp: Date.now(),\n })\n },\n\n // 表单放弃\n onAbandon: (fieldName: string) => {\n this.tracker.track('form_abandon', {\n formName,\n fieldName,\n timestamp: Date.now(),\n })\n },\n }\n }\n\n /**\n * 追踪搜索行为\n */\n trackSearch(keyword: string, resultCount: number): void {\n this.tracker.track('search', {\n keyword,\n resultCount,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪视频播放\n */\n trackVideo(videoId: string, action: string, data: Record<string, any> = {}): void {\n this.tracker.track('video_action', {\n videoId,\n action, // play, pause, ended, error\n ...data,\n timestamp: Date.now(),\n })\n }\n}\n\n/**\n * 创建行为监控实例\n */\nexport function createBehaviorMonitor(tracker: Tracker): BehaviorMonitor {\n return new BehaviorMonitor(tracker)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["tracker: Tracker","element: HTMLElement","path: string[]","current: HTMLElement | null","inactiveTimer: ReturnType<typeof setTimeout> | null","formName: string","success: boolean","data: Record<string, any>","fieldName: string","keyword: string","resultCount: number","videoId: string","action: string"],"sources":["../src/index.ts"],"sourcesContent":["import type { Tracker } from '@gctrack/core'\nimport { throttle } from '@gctrack/core'\n\n/**\n * 行为监控插件\n */\nexport class BehaviorMonitor {\n private tracker: Tracker\n private sessionStartTime = Date.now()\n private pageStartTime = Date.now()\n private clickCount = 0\n private scrollDepth = 0\n\n constructor(tracker: Tracker) {\n this.tracker = tracker\n }\n\n /**\n * 启动行为监控\n */\n start(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n this.trackPageView()\n this.trackPageStay()\n this.trackClick()\n this.trackScroll()\n this.trackVisibility()\n this.trackUserEngagement()\n }\n\n /**\n * 追踪页面浏览\n */\n private trackPageView(): void {\n this.tracker.track('page_view', {\n screenWidth: window.screen.width,\n screenHeight: window.screen.height,\n viewportWidth: window.innerWidth,\n viewportHeight: window.innerHeight,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪页面停留时间\n */\n private trackPageStay(): void {\n // 页面卸载时上报停留时间\n const reportStayTime = () => {\n const stayTime = Date.now() - this.pageStartTime\n\n this.tracker.track('page_stay', {\n stayTime,\n clickCount: this.clickCount,\n maxScrollDepth: this.scrollDepth,\n timestamp: Date.now(),\n })\n }\n\n window.addEventListener('beforeunload', reportStayTime)\n\n // 页面隐藏时也上报(移动端)\n document.addEventListener('visibilitychange', () => {\n if (document.visibilityState === 'hidden') {\n reportStayTime()\n }\n else if (document.visibilityState === 'visible') {\n // 页面重新可见时重置计时\n this.pageStartTime = Date.now()\n this.clickCount = 0\n }\n })\n }\n\n /**\n * 追踪点击行为\n */\n private trackClick(): void {\n document.addEventListener(\n 'click',\n (event) => {\n this.clickCount++\n\n const target = event.target as HTMLElement\n const tagName = target.tagName.toLowerCase()\n\n // 获取元素的唯一标识\n const elementId = target.id || ''\n const elementClass = target.className || ''\n const elementText = this.getElementText(target)\n const elementPath = this.getElementPath(target)\n\n // 只上报重要元素的点击\n if (this.isImportantElement(target)) {\n this.tracker.track('click', {\n tagName,\n elementId,\n elementClass,\n elementText,\n elementPath,\n x: event.clientX,\n y: event.clientY,\n timestamp: Date.now(),\n })\n }\n },\n true,\n )\n }\n\n /**\n * 获取元素文本内容\n */\n private getElementText(element: HTMLElement): string {\n const text = element.innerText || element.textContent || ''\n return text.trim().substring(0, 50) // 限制长度\n }\n\n /**\n * 获取元素路径\n */\n private getElementPath(element: HTMLElement): string {\n const path: string[] = []\n let current: HTMLElement | null = element\n\n while (current && current !== document.body) {\n let selector = current.tagName.toLowerCase()\n\n if (current.id) {\n selector += `#${current.id}`\n path.unshift(selector)\n break // ID 是唯一的,可以停止\n }\n else if (current.className) {\n const classes = current.className.split(' ').filter(c => c).join('.')\n if (classes) {\n selector += `.${classes}`\n }\n }\n\n path.unshift(selector)\n current = current.parentElement\n\n // 限制路径深度\n if (path.length >= 5)\n break\n }\n\n return path.join(' > ')\n }\n\n /**\n * 判断是否为重要元素\n */\n private isImportantElement(element: HTMLElement): boolean {\n const tagName = element.tagName.toLowerCase()\n const importantTags = ['a', 'button', 'input', 'select', 'textarea']\n\n // 标签类型判断\n if (importantTags.includes(tagName)) {\n return true\n }\n\n // 带有点击事件的元素\n if ((element as any).onclick || element.getAttribute('onclick')) {\n return true\n }\n\n // 带有特定 class 或 data 属性的元素\n if (\n element.hasAttribute('data-track')\n || element.classList.contains('track-click')\n ) {\n return true\n }\n\n return false\n }\n\n /**\n * 追踪滚动深度\n */\n private trackScroll(): void {\n const handleScroll = throttle(() => {\n const scrollTop = window.pageYOffset || document.documentElement.scrollTop\n const scrollHeight = document.documentElement.scrollHeight\n const clientHeight = window.innerHeight\n\n const scrollPercent = Math.round((scrollTop / (scrollHeight - clientHeight)) * 100)\n\n // 更新最大滚动深度\n if (scrollPercent > this.scrollDepth) {\n this.scrollDepth = scrollPercent\n\n // 每 25% 上报一次\n if (scrollPercent >= 25 && scrollPercent % 25 === 0) {\n this.tracker.track('scroll_depth', {\n depth: scrollPercent,\n timestamp: Date.now(),\n })\n }\n }\n }, 300)\n\n window.addEventListener('scroll', handleScroll)\n }\n\n /**\n * 追踪页面可见性\n */\n private trackVisibility(): void {\n document.addEventListener('visibilitychange', () => {\n this.tracker.track('visibility_change', {\n state: document.visibilityState,\n timestamp: Date.now(),\n })\n })\n }\n\n /**\n * 追踪用户参与度\n */\n private trackUserEngagement(): void {\n let inactiveTimer: ReturnType<typeof setTimeout> | null = null\n\n const resetInactiveTimer = () => {\n if (inactiveTimer) {\n clearTimeout(inactiveTimer)\n }\n\n inactiveTimer = setTimeout(() => {\n this.tracker.track('user_inactive', {\n inactiveDuration: 60000, // 1 分钟\n timestamp: Date.now(),\n })\n }, 60000)\n }\n\n // 监听用户活动\n const events = ['mousedown', 'mousemove', 'keypress', 'scroll', 'touchstart']\n events.forEach((event) => {\n document.addEventListener(event, resetInactiveTimer, true)\n })\n\n resetInactiveTimer()\n }\n\n /**\n * 追踪表单交互\n */\n trackFormInteraction(formName: string): {\n onFocus: () => void\n onSubmit: (success: boolean, data?: Record<string, any>) => void\n onAbandon: (fieldName: string) => void\n } {\n return {\n // 表单开始填写\n onFocus: () => {\n this.tracker.track('form_start', {\n formName,\n timestamp: Date.now(),\n })\n },\n\n // 表单提交\n onSubmit: (success: boolean, data: Record<string, any> = {}) => {\n this.tracker.track('form_submit', {\n formName,\n success,\n ...data,\n timestamp: Date.now(),\n })\n },\n\n // 表单放弃\n onAbandon: (fieldName: string) => {\n this.tracker.track('form_abandon', {\n formName,\n fieldName,\n timestamp: Date.now(),\n })\n },\n }\n }\n\n /**\n * 追踪搜索行为\n */\n trackSearch(keyword: string, resultCount: number): void {\n this.tracker.track('search', {\n keyword,\n resultCount,\n timestamp: Date.now(),\n })\n }\n\n /**\n * 追踪视频播放\n */\n trackVideo(videoId: string, action: string, data: Record<string, any> = {}): void {\n this.tracker.track('video_action', {\n videoId,\n action, // play, pause, ended, error\n ...data,\n timestamp: Date.now(),\n })\n }\n}\n\n/**\n * 创建行为监控实例\n */\nexport function createBehaviorMonitor(tracker: Tracker): BehaviorMonitor {\n return new BehaviorMonitor(tracker)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gctrack/behavior",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Behavior monitoring for GCTrack",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "peerDependencies": {
30
- "@gctrack/core": "0.1.1"
30
+ "@gctrack/core": "0.2.0"
31
31
  },
32
32
  "scripts": {
33
33
  "build": "tsdown",