user-behavior-monitor 1.0.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.
@@ -0,0 +1,394 @@
1
+ <template>
2
+ <div ref="behaviorMonitor" class="user-behavior-monitor">
3
+ <!-- 提示框 -->
4
+ <el-dialog
5
+ :visible.sync="showWarning"
6
+ :show-close="false"
7
+ :modal="true"
8
+ width="30%"
9
+ center
10
+ custom-class="behavior-warning-dialog"
11
+ >
12
+ <span>{{ warningMessage }}</span>
13
+ </el-dialog>
14
+ </div>
15
+ </template>
16
+
17
+ <script>
18
+ export default {
19
+ name: 'UserBehaviorMonitor',
20
+ props: {
21
+ // WebSocket服务器地址
22
+ websocketUrl: {
23
+ type: String,
24
+ required: true
25
+ },
26
+ // 倒计时时长(分钟)
27
+ timeoutMinutes: {
28
+ type: Number,
29
+ default: 10
30
+ },
31
+ // 警告提前时间(分钟)
32
+ warningMinutes: {
33
+ type: Number,
34
+ default: 1
35
+ }
36
+ },
37
+ data() {
38
+ return {
39
+ mouseMoveThrottled:false,
40
+ websocket: null,
41
+ countdownTimer: null,
42
+ warningTimer: null,
43
+ showWarning: false,
44
+ warningMessage: `您已${this.timeoutMinutes}分钟未操作,将在${this.warningMinutes}分钟后自动退出`,
45
+ lastActivityTime: null,
46
+ isMonitoring: false
47
+ };
48
+ },
49
+ mounted() {
50
+ this.initMonitor();
51
+ },
52
+ beforeDestroy() {
53
+ this.destroyMonitor();
54
+ },
55
+ methods: {
56
+ updateTimeoutSettings(timeoutMinutes, warningMinutes) {
57
+ if (timeoutMinutes !== undefined) {
58
+ this.timeoutMinutes = timeoutMinutes;
59
+ localStorage.setItem('userBehavior_timeoutMinutes', timeoutMinutes.toString());
60
+ }
61
+
62
+ if (warningMinutes !== undefined) {
63
+ this.warningMinutes = warningMinutes;
64
+ localStorage.setItem('userBehavior_warningMinutes', warningMinutes.toString());
65
+ }
66
+
67
+ // 更新警告消息
68
+ this.warningMessage = `您已${this.timeoutMinutes}分钟未操作,将在${this.warningMinutes}分钟后自动退出`;
69
+
70
+ // 重置计时器
71
+ this.resetTimer();
72
+ },
73
+ // 初始化监控
74
+ initMonitor() {
75
+ if (this.isMonitoring) return;
76
+
77
+ this.isMonitoring = true;
78
+ this.lastActivityTime = Date.now();
79
+
80
+ // 初始化WebSocket连接
81
+ this.initWebSocket();
82
+
83
+ // 启动倒计时
84
+ this.startCountdown();
85
+
86
+ // 绑定事件监听器
87
+ this.bindEventListeners();
88
+ },
89
+
90
+ // 销毁监控
91
+ destroyMonitor() {
92
+ this.isMonitoring = false;
93
+
94
+ // 清除定时器
95
+ if (this.countdownTimer) clearInterval(this.countdownTimer);
96
+ if (this.warningTimer) clearTimeout(this.warningTimer);
97
+
98
+ // 关闭WebSocket连接
99
+ if (this.websocket) {
100
+ this.websocket.close();
101
+ this.websocket = null;
102
+ }
103
+
104
+ // 解绑事件监听器
105
+ this.unbindEventListeners();
106
+ },
107
+
108
+ // 初始化WebSocket连接
109
+ initWebSocket() {
110
+ try {
111
+ this.websocket = new WebSocket(this.websocketUrl);
112
+
113
+ this.websocket.onopen = () => {
114
+ console.log('WebSocket连接已建立');
115
+ this.$emit('websocket-open');
116
+ };
117
+
118
+ this.websocket.onmessage = (event) => {
119
+ console.log('收到WebSocket消息:', event.data);
120
+ this.$emit('websocket-message', event.data);
121
+ };
122
+
123
+ this.websocket.onerror = (error) => {
124
+ console.error('WebSocket错误:', error);
125
+ this.$emit('websocket-error', error);
126
+ };
127
+
128
+ this.websocket.onclose = () => {
129
+ console.log('WebSocket连接已关闭');
130
+ this.$emit('websocket-close');
131
+ };
132
+ } catch (error) {
133
+ console.error('WebSocket初始化失败:', error);
134
+ this.$emit('websocket-error', error);
135
+ }
136
+ },
137
+
138
+ // 发送用户行为数据到后端
139
+ sendUserBehavior(data) {
140
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
141
+ const message = {
142
+ type: 'user_behavior',
143
+ timestamp: Date.now(),
144
+ data: data
145
+ };
146
+ console.log('用户行为监测:', JSON.stringify(message));
147
+ this.websocket.send(JSON.stringify(message));
148
+ }
149
+ },
150
+
151
+ // 绑定事件监听器
152
+ bindEventListeners() {
153
+ // 鼠标事件
154
+ document.addEventListener('click', this.handleUserActivity, true);
155
+ document.addEventListener('dblclick', this.handleUserActivity, true);
156
+ document.addEventListener('mousedown', this.handleUserActivity, true);
157
+ document.addEventListener('mouseup', this.handleUserActivity, true);
158
+ document.addEventListener('mousemove', this.handleMouseMove, true);
159
+ document.addEventListener('mouseover', this.handleUserActivity, true);
160
+ document.addEventListener('mouseout', this.handleUserActivity, true);
161
+
162
+ // 键盘事件
163
+ document.addEventListener('keydown', this.handleUserActivity, true);
164
+ document.addEventListener('keyup', this.handleUserActivity, true);
165
+
166
+ // 表单事件
167
+ document.addEventListener('input', this.handleUserActivity, true);
168
+ document.addEventListener('change', this.handleUserActivity, true);
169
+ document.addEventListener('focus', this.handleUserActivity, true);
170
+ document.addEventListener('blur', this.handleUserActivity, true);
171
+
172
+ // 滚动事件(防抖处理)
173
+ document.addEventListener('scroll', this.debounce(this.handleUserActivity, 300), true);
174
+
175
+ // 窗口事件
176
+ window.addEventListener('resize', this.handleUserActivity, true);
177
+ window.addEventListener('beforeunload', this.handleBeforeUnload);
178
+ },
179
+
180
+ // 解绑事件监听器
181
+ unbindEventListeners() {
182
+ document.removeEventListener('click', this.handleUserActivity, true);
183
+ document.removeEventListener('dblclick', this.handleUserActivity, true);
184
+ document.removeEventListener('mousedown', this.handleUserActivity, true);
185
+ document.removeEventListener('mouseup', this.handleUserActivity, true);
186
+ document.removeEventListener('mousemove', this.handleMouseMove, true);
187
+ document.removeEventListener('mouseover', this.handleUserActivity, true);
188
+ document.removeEventListener('mouseout', this.handleUserActivity, true);
189
+
190
+ document.removeEventListener('keydown', this.handleUserActivity, true);
191
+ document.removeEventListener('keyup', this.handleUserActivity, true);
192
+
193
+ document.removeEventListener('input', this.handleUserActivity, true);
194
+ document.removeEventListener('change', this.handleUserActivity, true);
195
+ document.removeEventListener('focus', this.handleUserActivity, true);
196
+ document.removeEventListener('blur', this.handleUserActivity, true);
197
+
198
+ document.removeEventListener('scroll', this.debounce(this.handleUserActivity, 300), true);
199
+
200
+ window.removeEventListener('resize', this.handleUserActivity, true);
201
+ window.removeEventListener('beforeunload', this.handleBeforeUnload);
202
+ },
203
+
204
+ // 处理用户活动
205
+ handleUserActivity(event) {
206
+ // 过滤掉自动触发的事件
207
+ if (this.isAutomaticEvent(event)) return;
208
+
209
+ this.resetTimer();
210
+
211
+ // 发送用户行为数据
212
+ const behaviorData = {
213
+ eventType: event.type,
214
+ target: event.target.tagName,
215
+ timestamp: Date.now(),
216
+ url: window.location.href
217
+ };
218
+
219
+ // 添加特定事件的额外信息
220
+ if (event.type === 'click') {
221
+ behaviorData.clientX = event.clientX;
222
+ behaviorData.clientY = event.clientY;
223
+ } else if (event.type === 'keydown') {
224
+ behaviorData.key = event.key;
225
+ behaviorData.ctrlKey = event.ctrlKey;
226
+ behaviorData.altKey = event.altKey;
227
+ behaviorData.shiftKey = event.shiftKey;
228
+ }
229
+
230
+ this.sendUserBehavior(behaviorData);
231
+ },
232
+ handleMouseMove(event) {
233
+ if (!this.mouseMoveThrottled) {
234
+ this.handleUserActivity(event);
235
+ this.mouseMoveThrottled = true;
236
+ setTimeout(() => {
237
+ this.mouseMoveThrottled = false;
238
+ }, 500);
239
+ }
240
+ },
241
+
242
+ // 处理鼠标移动(降低频率)
243
+ // handleMouseMove: function() {
244
+ // let isThrottled = false;
245
+ // return (event) => {
246
+ // if (!isThrottled) {
247
+ // this.handleUserActivity(event);
248
+ // isThrottled = true;
249
+ // setTimeout(() => {
250
+ // isThrottled = false;
251
+ // }, 500);
252
+ // }
253
+ // };
254
+ // }(),
255
+
256
+ // 判断是否为自动触发事件
257
+ isAutomaticEvent(event) {
258
+ // 自动刷新等非用户主动触发的事件
259
+ if (event.type === 'scroll' && !this.isUserScrolling) {
260
+ return true;
261
+ }
262
+ return false;
263
+ },
264
+
265
+ // 防抖函数
266
+ debounce(func, wait) {
267
+ let timeout;
268
+ return function executedFunction(...args) {
269
+ const later = () => {
270
+ clearTimeout(timeout);
271
+ func.apply(this, args);
272
+ };
273
+ clearTimeout(timeout);
274
+ timeout = setTimeout(later, wait);
275
+ };
276
+ },
277
+
278
+ // 重置计时器
279
+ resetTimer() {
280
+ this.lastActivityTime = Date.now();
281
+ this.showWarning = false;
282
+
283
+ // 清除警告定时器
284
+ if (this.warningTimer) {
285
+ clearTimeout(this.warningTimer);
286
+ this.warningTimer = null;
287
+ }
288
+
289
+ // 重新启动倒计时
290
+ this.startCountdown();
291
+
292
+ this.$emit('user-active');
293
+ },
294
+
295
+ // 启动倒计时
296
+ startCountdown() {
297
+ // 清除现有定时器
298
+ if (this.countdownTimer) clearInterval(this.countdownTimer);
299
+
300
+ // 设置新的倒计时
301
+ this.countdownTimer = setInterval(() => {
302
+ const now = Date.now();
303
+ const elapsedMinutes = (now - this.lastActivityTime) / (1000 * 60);
304
+
305
+ // 检查是否需要显示警告
306
+ if (elapsedMinutes >= (this.timeoutMinutes - this.warningMinutes) && !this.warningTimer) {
307
+ this.showWarningWarning();
308
+ }
309
+
310
+ // 检查是否超时
311
+ if (elapsedMinutes >= this.timeoutMinutes) {
312
+ this.handleTimeout();
313
+ }
314
+ }, 1000);
315
+ },
316
+
317
+ // 显示超时警告
318
+ showWarningWarning() {
319
+ this.showWarning = true;
320
+ this.$emit('timeout-warning');
321
+
322
+ // 设置超时处理
323
+ this.warningTimer = setTimeout(() => {
324
+ this.handleTimeout();
325
+ }, this.warningMinutes * 60 * 1000);
326
+ },
327
+
328
+ // 处理超时
329
+ handleTimeout() {
330
+ this.showWarning = false;
331
+ this.$emit('timeout');
332
+
333
+ // 执行登出逻辑
334
+ this.logout();
335
+ },
336
+
337
+ // 登出操作
338
+ logout() {
339
+ // 发送登出消息到后端
340
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
341
+ const message = {
342
+ type: 'logout',
343
+ timestamp: Date.now()
344
+ };
345
+ this.websocket.send(JSON.stringify(message));
346
+ }
347
+
348
+ // 触发登出事件
349
+ this.$emit('logout');
350
+
351
+ // 清除定时器
352
+ if (this.countdownTimer) clearInterval(this.countdownTimer);
353
+ if (this.warningTimer) clearTimeout(this.warningTimer);
354
+ },
355
+
356
+ // 处理页面卸载前的操作
357
+ handleBeforeUnload(event) {
358
+ // 发送页面关闭消息
359
+ if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
360
+ const message = {
361
+ type: 'page_unload',
362
+ timestamp: Date.now()
363
+ };
364
+ this.websocket.send(JSON.stringify(message));
365
+ }
366
+ },
367
+
368
+ // 手动重置监控
369
+ reset() {
370
+ this.resetTimer();
371
+ },
372
+
373
+ // 重新连接WebSocket
374
+ reconnect() {
375
+ if (this.websocket) {
376
+ this.websocket.close();
377
+ }
378
+ this.initWebSocket();
379
+ }
380
+ }
381
+ };
382
+ </script>
383
+
384
+ <style scoped>
385
+ .user-behavior-monitor {
386
+ display: none;
387
+ }
388
+
389
+ .behavior-warning-dialog >>> .el-dialog__body {
390
+ text-align: center;
391
+ font-size: 16px;
392
+ padding: 30px 20px;
393
+ }
394
+ </style>
package/src/index.js ADDED
@@ -0,0 +1,17 @@
1
+ import UserBehaviorMonitor from './components/UserBehaviorMonitor.vue';
2
+
3
+ // 为使用 CDN 方式引入的用户提供组件注册方法
4
+ UserBehaviorMonitor.install = function(Vue) {
5
+ Vue.component(UserBehaviorMonitor.name, UserBehaviorMonitor);
6
+ };
7
+
8
+ // 导出组件
9
+ export default UserBehaviorMonitor;
10
+
11
+ // 如果是直接引入文件,则自动注册组件
12
+ if (typeof window !== 'undefined' && window.Vue) {
13
+ UserBehaviorMonitor.install(window.Vue);
14
+ }
15
+
16
+ // 同时支持按需导入
17
+ export { UserBehaviorMonitor };