@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 +40 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +15 -11
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,15 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
+
const importantTags = [
|
|
119
144
|
"a",
|
|
120
145
|
"button",
|
|
121
146
|
"input",
|
|
122
147
|
"select",
|
|
123
148
|
"textarea"
|
|
124
|
-
]
|
|
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,
|
|
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
|
-
]
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
120
|
+
const importantTags = [
|
|
119
121
|
"a",
|
|
120
122
|
"button",
|
|
121
123
|
"input",
|
|
122
124
|
"select",
|
|
123
125
|
"textarea"
|
|
124
|
-
]
|
|
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
|
-
]
|
|
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.
|
|
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.
|
|
30
|
+
"@gctrack/core": "0.2.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsdown",
|