user-behavior-monitor 2.0.0 → 5.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.
@@ -2,6 +2,7 @@
2
2
  <div ref="behaviorMonitor" class="user-behavior-monitor">
3
3
  <!-- 提示框 -->
4
4
  <el-dialog
5
+ title="提示"
5
6
  :visible.sync="showWarning"
6
7
  :show-close="false"
7
8
  :modal="true"
@@ -15,16 +16,14 @@
15
16
  </template>
16
17
 
17
18
  <script>
18
- import { io } from "socket.io-client";
19
-
20
19
  export default {
21
20
  name: 'UserBehaviorMonitor',
22
21
  props: {
23
22
  // WebSocket服务器地址
24
- websocketUrl: {
25
- type: String,
26
- required: true
27
- },
23
+ // websocketUrl: {
24
+ // type: String,
25
+ // required: true
26
+ // },
28
27
  // 倒计时时长(分钟)
29
28
  timeoutMinutes: {
30
29
  type: Number,
@@ -43,20 +42,80 @@ export default {
43
42
  countdownTimer: null,
44
43
  warningTimer: null,
45
44
  showWarning: false,
46
- warningMessage: `您已${this.timeoutMinutes}分钟未操作,将在${this.warningMinutes}分钟后自动退出`,
45
+ // warningMinutes
46
+ warningMessage: `您已${this.timeoutMinutes}分钟未操作,将在1分钟后自动退出`,
47
47
  lastActivityTime: null,
48
48
  isMonitoring: false,
49
49
  currentTimeoutMinutes: 10,
50
- currentWarningMinutes: 1
50
+ currentWarningMinutes: 1,
51
+ reconnectAttempts: 0,
52
+ maxReconnectAttempts: 3,
53
+ reconnectDelay: 3000,
54
+ isLocalDevelopment: this.checkIfLocalDevelopment()
51
55
  };
52
56
  },
53
57
  mounted() {
54
- this.initMonitor();
58
+ // 检查当前路由是否包含/login,如果不包含则初始化监控
59
+ if (!this.isLoginRoute()) {
60
+ this.initMonitor();
61
+ }
55
62
  },
56
63
  beforeDestroy() {
57
64
  this.destroyMonitor();
58
65
  },
66
+ watch: {
67
+ // 监听路由变化
68
+ '$route'(to, from) {
69
+ // 安全检查 from.href 是否存在
70
+ const isFromLogin = from.path.includes('/login') ||
71
+ (from.href && from.href.includes('/login'));
72
+
73
+ // 检查当前是否为登录路由
74
+ const isToLogin = this.isLoginRoute();
75
+
76
+ // 如果从登录页跳转到非登录页,且监控未启动,则初始化监控
77
+ if (isFromLogin && !isToLogin && !this.isMonitoring) {
78
+ this.initMonitor();
79
+ }
80
+ // 如果从非登录页跳转到登录页,且监控正在运行,则销毁监控
81
+ else if (!isFromLogin && isToLogin && this.isMonitoring) {
82
+ this.destroyMonitor();
83
+ }
84
+ }
85
+ },
59
86
  methods: {
87
+ // 检查当前路由是否为登录页面
88
+ isLoginRoute() {
89
+ // 优先检查 Vue Router 路由
90
+ if (this.$route && this.$route.path) {
91
+ if (this.$route.path.includes('/login')) {
92
+ return true;
93
+ }
94
+ }
95
+
96
+ // 检查浏览器地址栏作为后备
97
+ if (typeof window !== 'undefined') {
98
+ return window.location.pathname.includes('/login') ||
99
+ window.location.href.includes('/login');
100
+ }
101
+
102
+ return false;
103
+ },
104
+
105
+ // 检查是否为本地开发环境
106
+ checkIfLocalDevelopment() {
107
+ if (typeof window !== 'undefined') {
108
+ const hostname = window.location.hostname;
109
+ return hostname === 'localhost' ||
110
+ hostname === '127.0.0.1' ||
111
+ hostname === '0.0.0.0' ||
112
+ /^192\.168\./.test(hostname) || // 局域网IP
113
+ /^10\./.test(hostname) || // 局域网IP
114
+ /^172\.(1[6-9]|2[0-9]|3[01])\./.test(hostname); // 局域网IP
115
+ }
116
+ return false;
117
+ },
118
+
60
119
  updateTimeoutSettings(timeoutMinutes, warningMinutes) {
61
120
  if (timeoutMinutes !== undefined) {
62
121
  this.currentTimeoutMinutes = timeoutMinutes;
@@ -69,23 +128,32 @@ export default {
69
128
  }
70
129
 
71
130
  // 更新警告消息
72
- this.warningMessage = `您已${this.currentTimeoutMinutes}分钟未操作,将在${this.currentWarningMinutes}分钟后自动退出`;
131
+ // ${this.currentWarningMinutes}
132
+ this.warningMessage = `您已${this.currentTimeoutMinutes}分钟未操作,将在1分钟后自动退出`;
73
133
 
74
134
  // 重置计时器
75
135
  this.resetTimer();
76
136
  },
77
137
  // 初始化监控
78
138
  initMonitor() {
139
+ // 再次检查确保不在登录页面
140
+ if (this.isLoginRoute()) {
141
+ // 如果在登录页面,确保清理所有监控资源
142
+ this.destroyMonitor();
143
+ return;
144
+ }
145
+
79
146
  if (this.isMonitoring) return;
80
147
 
81
148
  this.isMonitoring = true;
82
149
  this.lastActivityTime = Date.now();
150
+ this.reconnectAttempts = 0; // 重置重连尝试次数
83
151
 
84
- // 初始化Socket.IO连接
85
- this.initSocketIO();
152
+ // 初始化WebSocket连接
153
+ this.initWebSocket();
86
154
 
87
155
  // 启动倒计时
88
- this.startCountdown();
156
+ // this.startCountdown();
89
157
 
90
158
  // 绑定事件监听器
91
159
  this.bindEventListeners();
@@ -99,9 +167,9 @@ export default {
99
167
  if (this.countdownTimer) clearInterval(this.countdownTimer);
100
168
  if (this.warningTimer) clearTimeout(this.warningTimer);
101
169
 
102
- // 关闭Socket.IO连接
170
+ // 关闭WebSocket连接
103
171
  if (this.socket) {
104
- this.socket.disconnect();
172
+ this.socket.close();
105
173
  this.socket = null;
106
174
  }
107
175
 
@@ -109,15 +177,11 @@ export default {
109
177
  this.unbindEventListeners();
110
178
  },
111
179
 
112
- // 初始化Socket.IO连接
113
- // 初始化Socket.IO连接
114
- initSocketIO() {
180
+ // 初始化WebSocket连接
181
+ initWebSocket() {
115
182
  try {
116
- // 使用正确的WebSocket URL和路径配置
117
- const websocketUrl = 'http://100.150.0.4:30612';
118
-
119
- // 从localStorage获取timeoutMinutes,如果没有默认为10
120
- const storedTimeoutMinutes = localStorage.getItem('timeoutMinutes') || 10;
183
+ // 使用传入的WebSocket URL
184
+ const websocketUrl = `wss://${location.host}/xy-api/auth-server/ws/user-activity`;
121
185
 
122
186
  // 获取token(假设存储在localStorage中)
123
187
  let token = '';
@@ -130,53 +194,127 @@ export default {
130
194
  token = localStorage.getItem('token') || '';
131
195
  }
132
196
 
133
- // 创建Socket.IO连接,添加自定义headers
134
- this.socket = io(websocketUrl, {
135
- transports: ['websocket'],
136
- path: '/ws/user-activity', // 指定正确的路径
137
- extraHeaders: {
138
- 'Authorization': token
139
- },
140
- query: {
141
- timeoutMinutes: storedTimeoutMinutes
142
- },
143
- reconnection: true,
144
- reconnectionAttempts: 5,
145
- reconnectionDelay: 1000,
146
- timeout: 20000
147
- });
197
+ // 创建WebSocket连接
198
+ this.socket = new WebSocket(`${websocketUrl}?token=${encodeURIComponent(token)}`);
148
199
 
149
- this.socket.on('connect', () => {
150
- console.log('Socket.IO连接已建立', this.socket.id);
200
+ // 设置连接成功回调
201
+ this.socket.onopen = () => {
202
+ console.log('WebSocket连接已建立');
203
+ this.sendUserBehavior();
151
204
  this.$emit('websocket-open');
152
- });
153
-
154
- this.socket.on('disconnect', (reason) => {
155
- console.log('Socket.IO连接已断开:', reason);
156
- this.$emit('websocket-close');
157
- });
205
+ // 连接成功时重置重连尝试次数
206
+ this.reconnectAttempts = 0;
207
+ };
158
208
 
159
- // 处理后端返回的活动状态信息
160
- this.socket.on('success', (data) => {
161
- console.log('收到用户活动状态消息:', data);
162
- this.handleActivityStatus(data);
163
- this.$emit('websocket-message', data);
164
- });
209
+ // 设置接收消息回调
210
+ this.socket.onmessage = (event) => {
211
+ try {
212
+ const data = JSON.parse(event.data);
213
+ console.log('收到用户活动状态消息:', data);
214
+ this.handleActivityStatus(data);
215
+ this.$emit('websocket-message', data);
216
+ } catch (e) {
217
+ console.error('解析WebSocket消息失败:', e);
218
+ }
219
+ };
165
220
 
166
- this.socket.on('connect_error', (error) => {
167
- console.error('Socket.IO连接错误:', error);
168
- this.$emit('websocket-error', error);
169
- });
221
+ // 设置连接关闭回调
222
+ this.socket.onclose = (event) => {
223
+ console.log('WebSocket连接已断开:', event.reason);
224
+ this.$emit('websocket-close');
225
+
226
+ // 如果不是在登录页面且监控仍在运行,尝试重连
227
+ if (this.shouldAttemptReconnect()) {
228
+ this.attemptReconnect();
229
+ } else if (!this.isLoginRoute() && this.isMonitoring) {
230
+ // 如果不应该重连但仍在监控状态,根据环境决定是否跳转
231
+ if (!this.isLocalDevelopment) {
232
+ this.handleReconnectFailure();
233
+ }
234
+ }
235
+ };
170
236
 
171
- this.socket.on('error', (error) => {
172
- console.error('Socket.IO错误:', error);
237
+ // 设置连接错误回调
238
+ this.socket.onerror = (error) => {
239
+ console.error('WebSocket错误:', error);
173
240
  this.$emit('websocket-error', error);
174
- });
241
+
242
+ // 如果不是在登录页面且监控仍在运行,尝试重连
243
+ if (this.shouldAttemptReconnect()) {
244
+ this.attemptReconnect();
245
+ } else if (!this.isLoginRoute() && this.isMonitoring) {
246
+ // 如果不应该重连但仍在监控状态,根据环境决定是否跳转
247
+ if (!this.isLocalDevelopment) {
248
+ this.handleReconnectFailure();
249
+ }
250
+ }
251
+ };
175
252
 
176
253
  } catch (error) {
177
- console.error('Socket.IO初始化失败:', error);
254
+ console.error('WebSocket初始化失败:', error);
178
255
  this.$emit('websocket-error', error);
256
+
257
+ // 初始化失败时也可以尝试重连(如果不是在登录页面)
258
+ if (this.shouldAttemptReconnect()) {
259
+ this.attemptReconnect();
260
+ } else if (!this.isLoginRoute() && this.isMonitoring) {
261
+ // 如果不应该重连但仍在监控状态,根据环境决定是否跳转
262
+ if (!this.isLocalDevelopment) {
263
+ this.handleReconnectFailure();
264
+ }
265
+ }
266
+ }
267
+ },
268
+
269
+ // 判断是否应该尝试重连
270
+ shouldAttemptReconnect() {
271
+ return this.isMonitoring &&
272
+ !this.isLoginRoute() &&
273
+ this.reconnectAttempts < this.maxReconnectAttempts;
274
+ },
275
+
276
+ // 添加重连方法
277
+ attemptReconnect() {
278
+ // 再次检查是否为登录页面
279
+ if (this.isLoginRoute()) {
280
+ console.log('当前在登录页面,取消重连');
281
+ return;
282
+ }
283
+
284
+ this.reconnectAttempts++;
285
+ console.log(`尝试第${this.reconnectAttempts}次重连...`);
286
+
287
+ // 如果是本地开发环境且重连次数已达到最大值,则不跳转登录页
288
+ if (this.isLocalDevelopment && this.reconnectAttempts >= this.maxReconnectAttempts) {
289
+ console.log('本地开发环境,重连失败但不跳转到登录页');
290
+ this.$emit('websocket-reconnect-failed');
291
+ return;
292
+ }
293
+
294
+ // 如果是线上环境且重连次数已达到最大值,则跳转到登录页
295
+ if (!this.isLocalDevelopment && this.reconnectAttempts >= this.maxReconnectAttempts) {
296
+ console.log('线上环境,重连失败,跳转到登录页');
297
+ this.handleReconnectFailure();
298
+ return;
179
299
  }
300
+
301
+ setTimeout(() => {
302
+ this.initWebSocket();
303
+ }, this.reconnectDelay);
304
+ },
305
+
306
+ // 处理重连失败
307
+ handleReconnectFailure() {
308
+ // 清空缓存
309
+ localStorage.clear();
310
+ sessionStorage.clear();
311
+
312
+ // 跳转到登录页
313
+
314
+ location.reload();
315
+
316
+ // 触发登出事件
317
+ this.$emit('logout');
180
318
  },
181
319
 
182
320
  // 处理活动状态信息
@@ -190,17 +328,22 @@ export default {
190
328
 
191
329
  // 更新警告消息
192
330
  this.warningMessage = `您已${this.currentTimeoutMinutes}分钟未操作,将在${this.currentWarningMinutes}分钟后自动退出`;
193
-
194
- // 检查是否需要显示提醒
195
- if (activityData.needReminder) {
196
- this.showWarningWarning();
331
+ if(activityData.needReminder){
332
+ this.showWarningWarning(activityData.remainingMillis);
197
333
  }
198
-
334
+ // remainingMillis
335
+
336
+
199
337
  // 检查是否仍然活跃
200
- if (!activityData.isActive) {
201
- this.handleInactiveStatus();
202
- }
338
+ //if (!activityData.isActive) {
339
+ //this.handleInactiveStatus();
340
+ //}
203
341
  }
342
+ // 检查是否需要显示提醒
343
+ // let dataReminder=data.data;
344
+ // if (dataReminder&&dataReminder.needReminder) {
345
+ // this.showWarningWarning(dataReminder.remainingSeconds);
346
+ // }
204
347
  },
205
348
 
206
349
  // 处理非活跃状态
@@ -210,10 +353,12 @@ export default {
210
353
  sessionStorage.clear();
211
354
 
212
355
  // 跳转到登录页
213
- this.$router.push('/login');
356
+ //this.$router.push('/login');
214
357
 
215
358
  // 或者使用window.location
216
- // window.location.href = '/login';
359
+
360
+ location.reload();
361
+ // window.location.href = '/login';
217
362
 
218
363
  // 触发登出事件
219
364
  this.$emit('logout');
@@ -221,14 +366,13 @@ export default {
221
366
 
222
367
  // 发送用户行为数据到后端
223
368
  sendUserBehavior(data) {
224
- console.log('用户行为监测:')
225
- if (this.socket && this.socket.connected) {
369
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
226
370
  const message = {
227
371
  "type": "HEARTBEAT",
228
- "message": "心跳"
372
+ "message": "心跳",
229
373
  };
230
374
  console.log('用户行为监测:', JSON.stringify(message));
231
- this.socket.emit('user_behavior', message);
375
+ this.socket.send(JSON.stringify(message));
232
376
  }
233
377
  },
234
378
 
@@ -349,6 +493,7 @@ export default {
349
493
  resetTimer() {
350
494
  this.lastActivityTime = Date.now();
351
495
  this.showWarning = false;
496
+ this.reconnectAttempts = 0; // 重置重连尝试次数
352
497
 
353
498
  // 清除警告定时器
354
499
  if (this.warningTimer) {
@@ -357,7 +502,7 @@ export default {
357
502
  }
358
503
 
359
504
  // 重新启动倒计时
360
- this.startCountdown();
505
+ // this.startCountdown();
361
506
 
362
507
  this.$emit('user-active');
363
508
  },
@@ -370,7 +515,7 @@ export default {
370
515
  // 设置新的倒计时
371
516
  this.countdownTimer = setInterval(() => {
372
517
  const now = Date.now();
373
- const elapsedMinutes = (now - this.lastActivityTime) / (1000 * 60);
518
+ const elapsedMinutes = (now - this.lastActivityTime) / (1000 * 50);
374
519
 
375
520
  // 检查是否需要显示警告
376
521
  if (elapsedMinutes >= (this.currentTimeoutMinutes - this.currentWarningMinutes) && !this.warningTimer) {
@@ -385,14 +530,16 @@ export default {
385
530
  },
386
531
 
387
532
  // 显示超时警告
388
- showWarningWarning() {
533
+ showWarningWarning(timeoutMinutes) {
534
+ let time=timeoutMinutes||50000;
535
+ console.log('Setting showWarning to true');
389
536
  this.showWarning = true;
390
537
  this.$emit('timeout-warning');
391
-
538
+ if (this.warningTimer) clearTimeout(this.warningTimer);
392
539
  // 设置超时处理
393
540
  this.warningTimer = setTimeout(() => {
394
541
  this.handleTimeout();
395
- }, this.currentWarningMinutes * 60 * 1000);
542
+ }, time);
396
543
  },
397
544
 
398
545
  // 处理超时
@@ -406,13 +553,21 @@ export default {
406
553
 
407
554
  // 登出操作
408
555
  logout() {
556
+ localStorage.clear();
557
+ sessionStorage.clear();
558
+ location.reload();
559
+ // 跳转到登录页
560
+ //this.$router.push('/login');
561
+
562
+ // 或者使用window.location
563
+ // window.location.href = '/login';
409
564
  // 发送登出消息到后端
410
- if (this.socket && this.socket.connected) {
565
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
411
566
  const message = {
412
567
  type: 'logout',
413
568
  timestamp: Date.now()
414
569
  };
415
- this.socket.emit('logout', message);
570
+ this.socket.send(JSON.stringify(message));
416
571
  }
417
572
 
418
573
  // 触发登出事件
@@ -426,39 +581,47 @@ export default {
426
581
  // 处理页面卸载前的操作
427
582
  handleBeforeUnload(event) {
428
583
  // 发送页面关闭消息
429
- if (this.socket && this.socket.connected) {
584
+ if (this.socket && this.socket.readyState === WebSocket.OPEN) {
430
585
  const message = {
431
586
  type: 'page_unload',
432
587
  timestamp: Date.now()
433
588
  };
434
- this.socket.emit('page_unload', message);
589
+ this.socket.send(JSON.stringify(message));
435
590
  }
436
591
  },
437
592
 
438
593
  // 手动重置监控
439
594
  reset() {
595
+ // 检查当前路由,如果在登录页面则销毁监控
596
+ if (this.isLoginRoute()) {
597
+ this.destroyMonitor();
598
+ return;
599
+ }
600
+
440
601
  this.resetTimer();
441
602
  },
442
603
 
443
- // 重新连接Socket.IO
604
+ // 重新连接WebSocket
444
605
  reconnect() {
606
+ // 检查当前路由,如果在登录页面则销毁监控
607
+ if (this.isLoginRoute()) {
608
+ this.destroyMonitor();
609
+ return;
610
+ }
611
+
612
+ // 重置重连尝试次数
613
+ this.reconnectAttempts = 0;
614
+
445
615
  if (this.socket) {
446
- this.socket.disconnect();
616
+ this.socket.close();
447
617
  }
448
- this.initSocketIO();
618
+ this.initWebSocket();
449
619
  }
450
620
  }
451
621
  };
452
622
  </script>
453
-
454
- <style scoped>
455
- .user-behavior-monitor {
456
- display: none;
457
- }
458
-
459
- .behavior-warning-dialog >>> .el-dialog__body {
460
- text-align: center;
461
- font-size: 16px;
462
- padding: 30px 20px;
623
+ <style>
624
+ .behavior-warning-dialog.el-dialog.el-dialog--center{
625
+ text-align: left;
463
626
  }
464
627
  </style>
@@ -1 +0,0 @@
1
- {"version":3,"sources":["webpack://user-behavior-monitor/webpack/bootstrap","webpack://user-behavior-monitor/./node_modules/css-loader/lib/css-base.js","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue?b768","webpack://user-behavior-monitor/./node_modules/vue-style-loader/lib/listToStyles.js","webpack://user-behavior-monitor/./node_modules/vue-style-loader/lib/addStylesClient.js","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue?ad85","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue?2816","webpack://user-behavior-monitor/./node_modules/@vue/cli-service/lib/commands/build/setPublicPath.js","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue?198a","webpack://user-behavior-monitor/src/components/UserBehaviorMonitor.vue","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue?8778","webpack://user-behavior-monitor/./node_modules/vue-loader/lib/runtime/componentNormalizer.js","webpack://user-behavior-monitor/./src/components/UserBehaviorMonitor.vue","webpack://user-behavior-monitor/./src/index.js","webpack://user-behavior-monitor/./node_modules/@vue/cli-service/lib/commands/build/entry-lib.js"],"names":[],"mappings":";;QAAA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;;QAGA;QACA;;;;;;;;AClFA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,mCAAmC,gBAAgB;AACnD,IAAI;AACJ;AACA;AACA,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA,gBAAgB,iBAAiB;AACjC;AACA;AACA;AACA;AACA,YAAY,oBAAoB;AAChC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,oDAAoD,cAAc;;AAElE;AACA;;;;;;;;;AC3EA;AAAA;AAAA;;;;;;;;;;;;;;;;ACAA;AACA;AACA;AACA;AACe;AACf;AACA;AACA,iBAAiB,iBAAiB;AAClC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,mCAAmC,wBAAwB;AAC3D,KAAK;AACL;AACA;AACA;AACA;AACA;;;AC1BA;AACA;AACA;AACA;AACA;;AAEyC;;AAEzC;;AAEA;AACA;AACA;AACA;AACA,UAAU,iBAAiB;AAC3B;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA,mBAAmB;AACnB;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEe;AACf;;AAEA;;AAEA,eAAe,YAAY;AAC3B;;AAEA;AACA;AACA,mBAAmB,mBAAmB;AACtC;AACA;AACA;AACA;AACA;AACA;AACA,eAAe,YAAY;AAC3B;AACA,KAAK;AACL;AACA;AACA,mBAAmB,sBAAsB;AACzC;AACA;AACA,uBAAuB,2BAA2B;AAClD;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,iBAAiB,mBAAmB;AACpC;AACA;AACA;AACA;AACA,qBAAqB,2BAA2B;AAChD;AACA;AACA,YAAY,uBAAuB;AACnC;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA,qBAAqB,uBAAuB;AAC5C;AACA;AACA,8BAA8B;AAC9B;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,CAAC;;AAED;AACA;;AAEA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,yDAAyD;AACzD;;AAEA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;;;;;;;;AC7NA,2BAA2B,mBAAO,CAAC,MAA+C;AAClF;;;AAGA;AACA,cAAc,QAAS,wDAAwD,gBAAgB;;AAE/F;;;;;;;;ACPA;;AAEA;AACA,cAAc,mBAAO,CAAC,MAA0X;AAChZ;AACA,4CAA4C,QAAS;AACrD;AACA;AACA,UAAU,mBAAO,CAAC,MAA6D;AAC/E,6CAA6C,qCAAqC,E;;;;;;;;;;;;;;;ACTlF;;AAEA;AACA,MAAM,KAAuC,EAAE,EAE5C;;AAEH;AACA;AACA,IAAI,qBAAuB;AAC3B;AACA;;AAEA;AACe,sDAAI;;;ACdnB,0BAA0B,aAAa,0BAA0B,wBAAwB,iBAAiB,0DAA0D,kBAAkB,OAAO,0IAA0I,KAAK,kCAAkC,yBAAyB;AACvY;;;;;;;;;;;;;;;;;;;;;;;;ACiBe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;;AAGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,EAAC;;;AClgByL,CAAgB,oIAAG,EAAC,C;;;;;ACA/M;;AAEA;AACA;AACA;;AAEe;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;;AC/FiG;AAC3B;AACL;AAC8B;;;AAG/F;AAC0F;AAC1F,gBAAgB,kBAAU;AAC1B,EAAE,qDAAM;AACR,EAAE,MAAM;AACR,EAAE,eAAe;AACjB;AACA;AACA;AACA;;AAEA;;AAEe,yE;;ACnBwD;;AAEvE;AACA,mBAAmB;AACnB,gBAAgB,mBAAmB,OAAO,mBAAmB;AAC7D;;AAEA;AACe,2DAAmB,EAAC;;AAEnC;AACA;AACA,EAAE,mBAAmB;AACrB;;AAEA;;;ACfwB;AACA;AACT,kFAAG;AACI","file":"user-behavior-monitor.common.js","sourcesContent":[" \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = \"fb15\");\n","/*\n\tMIT License http://www.opensource.org/licenses/mit-license.php\n\tAuthor Tobias Koppers @sokra\n*/\n// css base code, injected by the css-loader\nmodule.exports = function(useSourceMap) {\n\tvar list = [];\n\n\t// return the list of modules as css string\n\tlist.toString = function toString() {\n\t\treturn this.map(function (item) {\n\t\t\tvar content = cssWithMappingToString(item, useSourceMap);\n\t\t\tif(item[2]) {\n\t\t\t\treturn \"@media \" + item[2] + \"{\" + content + \"}\";\n\t\t\t} else {\n\t\t\t\treturn content;\n\t\t\t}\n\t\t}).join(\"\");\n\t};\n\n\t// import a list of modules into the list\n\tlist.i = function(modules, mediaQuery) {\n\t\tif(typeof modules === \"string\")\n\t\t\tmodules = [[null, modules, \"\"]];\n\t\tvar alreadyImportedModules = {};\n\t\tfor(var i = 0; i < this.length; i++) {\n\t\t\tvar id = this[i][0];\n\t\t\tif(typeof id === \"number\")\n\t\t\t\talreadyImportedModules[id] = true;\n\t\t}\n\t\tfor(i = 0; i < modules.length; i++) {\n\t\t\tvar item = modules[i];\n\t\t\t// skip already imported module\n\t\t\t// this implementation is not 100% perfect for weird media query combinations\n\t\t\t// when a module is imported multiple times with different media queries.\n\t\t\t// I hope this will never occur (Hey this way we have smaller bundles)\n\t\t\tif(typeof item[0] !== \"number\" || !alreadyImportedModules[item[0]]) {\n\t\t\t\tif(mediaQuery && !item[2]) {\n\t\t\t\t\titem[2] = mediaQuery;\n\t\t\t\t} else if(mediaQuery) {\n\t\t\t\t\titem[2] = \"(\" + item[2] + \") and (\" + mediaQuery + \")\";\n\t\t\t\t}\n\t\t\t\tlist.push(item);\n\t\t\t}\n\t\t}\n\t};\n\treturn list;\n};\n\nfunction cssWithMappingToString(item, useSourceMap) {\n\tvar content = item[1] || '';\n\tvar cssMapping = item[3];\n\tif (!cssMapping) {\n\t\treturn content;\n\t}\n\n\tif (useSourceMap && typeof btoa === 'function') {\n\t\tvar sourceMapping = toComment(cssMapping);\n\t\tvar sourceURLs = cssMapping.sources.map(function (source) {\n\t\t\treturn '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'\n\t\t});\n\n\t\treturn [content].concat(sourceURLs).concat([sourceMapping]).join('\\n');\n\t}\n\n\treturn [content].join('\\n');\n}\n\n// Adapted from convert-source-map (MIT)\nfunction toComment(sourceMap) {\n\t// eslint-disable-next-line no-undef\n\tvar base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));\n\tvar data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;\n\n\treturn '/*# ' + data + ' */';\n}\n","export * from \"-!../../node_modules/vue-style-loader/index.js??ref--6-oneOf-1-0!../../node_modules/css-loader/index.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./UserBehaviorMonitor.vue?vue&type=style&index=0&id=4f34cec0&prod&lang=css\"","/**\n * Translates the list format produced by css-loader into something\n * easier to manipulate.\n */\nexport default function listToStyles (parentId, list) {\n var styles = []\n var newStyles = {}\n for (var i = 0; i < list.length; i++) {\n var item = list[i]\n var id = item[0]\n var css = item[1]\n var media = item[2]\n var sourceMap = item[3]\n var part = {\n id: parentId + ':' + i,\n css: css,\n media: media,\n sourceMap: sourceMap\n }\n if (!newStyles[id]) {\n styles.push(newStyles[id] = { id: id, parts: [part] })\n } else {\n newStyles[id].parts.push(part)\n }\n }\n return styles\n}\n","/*\n MIT License http://www.opensource.org/licenses/mit-license.php\n Author Tobias Koppers @sokra\n Modified by Evan You @yyx990803\n*/\n\nimport listToStyles from './listToStyles'\n\nvar hasDocument = typeof document !== 'undefined'\n\nif (typeof DEBUG !== 'undefined' && DEBUG) {\n if (!hasDocument) {\n throw new Error(\n 'vue-style-loader cannot be used in a non-browser environment. ' +\n \"Use { target: 'node' } in your Webpack config to indicate a server-rendering environment.\"\n ) }\n}\n\n/*\ntype StyleObject = {\n id: number;\n parts: Array<StyleObjectPart>\n}\n\ntype StyleObjectPart = {\n css: string;\n media: string;\n sourceMap: ?string\n}\n*/\n\nvar stylesInDom = {/*\n [id: number]: {\n id: number,\n refs: number,\n parts: Array<(obj?: StyleObjectPart) => void>\n }\n*/}\n\nvar head = hasDocument && (document.head || document.getElementsByTagName('head')[0])\nvar singletonElement = null\nvar singletonCounter = 0\nvar isProduction = false\nvar noop = function () {}\nvar options = null\nvar ssrIdKey = 'data-vue-ssr-id'\n\n// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>\n// tags it will allow on a page\nvar isOldIE = typeof navigator !== 'undefined' && /msie [6-9]\\b/.test(navigator.userAgent.toLowerCase())\n\nexport default function addStylesClient (parentId, list, _isProduction, _options) {\n isProduction = _isProduction\n\n options = _options || {}\n\n var styles = listToStyles(parentId, list)\n addStylesToDom(styles)\n\n return function update (newList) {\n var mayRemove = []\n for (var i = 0; i < styles.length; i++) {\n var item = styles[i]\n var domStyle = stylesInDom[item.id]\n domStyle.refs--\n mayRemove.push(domStyle)\n }\n if (newList) {\n styles = listToStyles(parentId, newList)\n addStylesToDom(styles)\n } else {\n styles = []\n }\n for (var i = 0; i < mayRemove.length; i++) {\n var domStyle = mayRemove[i]\n if (domStyle.refs === 0) {\n for (var j = 0; j < domStyle.parts.length; j++) {\n domStyle.parts[j]()\n }\n delete stylesInDom[domStyle.id]\n }\n }\n }\n}\n\nfunction addStylesToDom (styles /* Array<StyleObject> */) {\n for (var i = 0; i < styles.length; i++) {\n var item = styles[i]\n var domStyle = stylesInDom[item.id]\n if (domStyle) {\n domStyle.refs++\n for (var j = 0; j < domStyle.parts.length; j++) {\n domStyle.parts[j](item.parts[j])\n }\n for (; j < item.parts.length; j++) {\n domStyle.parts.push(addStyle(item.parts[j]))\n }\n if (domStyle.parts.length > item.parts.length) {\n domStyle.parts.length = item.parts.length\n }\n } else {\n var parts = []\n for (var j = 0; j < item.parts.length; j++) {\n parts.push(addStyle(item.parts[j]))\n }\n stylesInDom[item.id] = { id: item.id, refs: 1, parts: parts }\n }\n }\n}\n\nfunction createStyleElement () {\n var styleElement = document.createElement('style')\n styleElement.type = 'text/css'\n head.appendChild(styleElement)\n return styleElement\n}\n\nfunction addStyle (obj /* StyleObjectPart */) {\n var update, remove\n var styleElement = document.querySelector('style[' + ssrIdKey + '~=\"' + obj.id + '\"]')\n\n if (styleElement) {\n if (isProduction) {\n // has SSR styles and in production mode.\n // simply do nothing.\n return noop\n } else {\n // has SSR styles but in dev mode.\n // for some reason Chrome can't handle source map in server-rendered\n // style tags - source maps in <style> only works if the style tag is\n // created and inserted dynamically. So we remove the server rendered\n // styles and inject new ones.\n styleElement.parentNode.removeChild(styleElement)\n }\n }\n\n if (isOldIE) {\n // use singleton mode for IE9.\n var styleIndex = singletonCounter++\n styleElement = singletonElement || (singletonElement = createStyleElement())\n update = applyToSingletonTag.bind(null, styleElement, styleIndex, false)\n remove = applyToSingletonTag.bind(null, styleElement, styleIndex, true)\n } else {\n // use multi-style-tag mode in all other cases\n styleElement = createStyleElement()\n update = applyToTag.bind(null, styleElement)\n remove = function () {\n styleElement.parentNode.removeChild(styleElement)\n }\n }\n\n update(obj)\n\n return function updateStyle (newObj /* StyleObjectPart */) {\n if (newObj) {\n if (newObj.css === obj.css &&\n newObj.media === obj.media &&\n newObj.sourceMap === obj.sourceMap) {\n return\n }\n update(obj = newObj)\n } else {\n remove()\n }\n }\n}\n\nvar replaceText = (function () {\n var textStore = []\n\n return function (index, replacement) {\n textStore[index] = replacement\n return textStore.filter(Boolean).join('\\n')\n }\n})()\n\nfunction applyToSingletonTag (styleElement, index, remove, obj) {\n var css = remove ? '' : obj.css\n\n if (styleElement.styleSheet) {\n styleElement.styleSheet.cssText = replaceText(index, css)\n } else {\n var cssNode = document.createTextNode(css)\n var childNodes = styleElement.childNodes\n if (childNodes[index]) styleElement.removeChild(childNodes[index])\n if (childNodes.length) {\n styleElement.insertBefore(cssNode, childNodes[index])\n } else {\n styleElement.appendChild(cssNode)\n }\n }\n}\n\nfunction applyToTag (styleElement, obj) {\n var css = obj.css\n var media = obj.media\n var sourceMap = obj.sourceMap\n\n if (media) {\n styleElement.setAttribute('media', media)\n }\n if (options.ssrId) {\n styleElement.setAttribute(ssrIdKey, obj.id)\n }\n\n if (sourceMap) {\n // https://developer.chrome.com/devtools/docs/javascript-debugging\n // this makes source maps inside style tags work properly in Chrome\n css += '\\n/*# sourceURL=' + sourceMap.sources[0] + ' */'\n // http://stackoverflow.com/a/26603875\n css += '\\n/*# sourceMappingURL=data:application/json;base64,' + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + ' */'\n }\n\n if (styleElement.styleSheet) {\n styleElement.styleSheet.cssText = css\n } else {\n while (styleElement.firstChild) {\n styleElement.removeChild(styleElement.firstChild)\n }\n styleElement.appendChild(document.createTextNode(css))\n }\n}\n","exports = module.exports = require(\"../../node_modules/css-loader/lib/css-base.js\")(false);\n// imports\n\n\n// module\nexports.push([module.id, \".behavior-warning-dialog.el-dialog.el-dialog--center{text-align:left}\", \"\"]);\n\n// exports\n","// style-loader: Adds some css to the DOM by adding a <style> tag\n\n// load the styles\nvar content = require(\"!!../../node_modules/css-loader/index.js??ref--6-oneOf-1-1!../../node_modules/vue-loader/lib/loaders/stylePostLoader.js!../../node_modules/postcss-loader/src/index.js??ref--6-oneOf-1-2!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./UserBehaviorMonitor.vue?vue&type=style&index=0&id=4f34cec0&prod&lang=css\");\nif(content.__esModule) content = content.default;\nif(typeof content === 'string') content = [[module.id, content, '']];\nif(content.locals) module.exports = content.locals;\n// add the styles to the DOM\nvar add = require(\"!../../node_modules/vue-style-loader/lib/addStylesClient.js\").default\nvar update = add(\"1c569d63\", content, true, {\"sourceMap\":false,\"shadowMode\":false});","// This file is imported into lib/wc client bundles.\n\nif (typeof window !== 'undefined') {\n if (process.env.NEED_CURRENTSCRIPT_POLYFILL) {\n require('current-script-polyfill')\n }\n\n var i\n if ((i = window.document.currentScript) && (i = i.src.match(/(.+\\/)[^/]+\\.js(\\?.*)?$/))) {\n __webpack_public_path__ = i[1] // eslint-disable-line\n }\n}\n\n// Indicate to webpack that this file can be concatenated\nexport default null\n","var render = function () {var _vm=this;var _h=_vm.$createElement;var _c=_vm._self._c||_h;return _c('div',{ref:\"behaviorMonitor\",staticClass:\"user-behavior-monitor\"},[_c('el-dialog',{attrs:{\"title\":\"提示\",\"visible\":_vm.showWarning,\"show-close\":false,\"modal\":true,\"width\":\"30%\",\"center\":\"\",\"custom-class\":\"behavior-warning-dialog\"},on:{\"update:visible\":function($event){_vm.showWarning=$event}}},[_c('span',[_vm._v(_vm._s(_vm.warningMessage))])])],1)}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","<template>\r\n <div ref=\"behaviorMonitor\" class=\"user-behavior-monitor\">\r\n <!-- 提示框 -->\r\n <el-dialog\r\n title=\"提示\"\r\n :visible.sync=\"showWarning\"\r\n :show-close=\"false\"\r\n :modal=\"true\"\r\n width=\"30%\"\r\n center\r\n custom-class=\"behavior-warning-dialog\"\r\n >\r\n <span>{{ warningMessage }}</span>\r\n </el-dialog>\r\n </div>\r\n</template>\r\n\r\n<script>\r\nexport default {\r\n name: 'UserBehaviorMonitor',\r\n props: {\r\n // WebSocket服务器地址\r\n // websocketUrl: {\r\n // type: String,\r\n // required: true\r\n // },\r\n // 倒计时时长(分钟)\r\n timeoutMinutes: {\r\n type: Number,\r\n default: 10\r\n },\r\n // 警告提前时间(分钟)\r\n warningMinutes: {\r\n type: Number,\r\n default: 1\r\n }\r\n },\r\n data() {\r\n return {\r\n mouseMoveThrottled: false,\r\n socket: null,\r\n countdownTimer: null,\r\n warningTimer: null,\r\n showWarning: false,\r\n // warningMinutes\r\n warningMessage: `您已${this.timeoutMinutes}分钟未操作,将在1分钟后自动退出`,\r\n lastActivityTime: null,\r\n isMonitoring: false,\r\n currentTimeoutMinutes: 10,\r\n currentWarningMinutes: 1\r\n };\r\n },\r\n mounted() {\r\n // 检查当前路由是否包含/login,如果不包含则初始化监控\r\n if (!this.isLoginRoute()) {\r\n this.initMonitor();\r\n }\r\n },\r\n beforeDestroy() {\r\n this.destroyMonitor();\r\n },\r\n watch: {\r\n // 监听路由变化\r\n '$route'(to, from) {\r\n // 安全检查 from.href 是否存在\r\n const isFromLogin = from.path.includes('/login') || \r\n (from.href && from.href.includes('/login'));\r\n \r\n // 检查当前是否为登录路由\r\n const isToLogin = this.isLoginRoute();\r\n \r\n // 如果从登录页跳转到非登录页,且监控未启动,则初始化监控\r\n if (isFromLogin && !isToLogin && !this.isMonitoring) {\r\n this.initMonitor();\r\n }\r\n // 如果从非登录页跳转到登录页,且监控正在运行,则销毁监控\r\n else if (!isFromLogin && isToLogin && this.isMonitoring) {\r\n this.destroyMonitor();\r\n }\r\n }\r\n },\r\n methods: {\r\n // 检查当前路由是否为登录页面\r\n isLoginRoute() {\r\n // 优先检查 Vue Router 路由\r\n if (this.$route && this.$route.path) {\r\n if (this.$route.path.includes('/login')) {\r\n return true;\r\n }\r\n }\r\n \r\n // 检查浏览器地址栏作为后备\r\n if (typeof window !== 'undefined') {\r\n return window.location.pathname.includes('/login') || \r\n window.location.href.includes('/login');\r\n }\r\n \r\n return false;\r\n },\r\n \r\n updateTimeoutSettings(timeoutMinutes, warningMinutes) {\r\n if (timeoutMinutes !== undefined) {\r\n this.currentTimeoutMinutes = timeoutMinutes;\r\n localStorage.setItem('userBehavior_timeoutMinutes', timeoutMinutes.toString());\r\n }\r\n \r\n if (warningMinutes !== undefined) {\r\n this.currentWarningMinutes = warningMinutes;\r\n localStorage.setItem('userBehavior_warningMinutes', warningMinutes.toString());\r\n }\r\n \r\n // 更新警告消息\r\n // ${this.currentWarningMinutes}\r\n this.warningMessage = `您已${this.currentTimeoutMinutes}分钟未操作,将在1分钟后自动退出`;\r\n \r\n // 重置计时器\r\n this.resetTimer();\r\n },\r\n // 初始化监控\r\n initMonitor() {\r\n // 再次检查确保不在登录页面\r\n if (this.isLoginRoute()) {\r\n // 如果在登录页面,确保清理所有监控资源\r\n this.destroyMonitor();\r\n return;\r\n }\r\n \r\n if (this.isMonitoring) return;\r\n \r\n this.isMonitoring = true;\r\n this.lastActivityTime = Date.now();\r\n \r\n // 初始化WebSocket连接\r\n this.initWebSocket();\r\n \r\n // 启动倒计时\r\n // this.startCountdown();\r\n \r\n // 绑定事件监听器\r\n this.bindEventListeners();\r\n },\r\n \r\n // 销毁监控\r\n destroyMonitor() {\r\n this.isMonitoring = false;\r\n \r\n // 清除定时器\r\n if (this.countdownTimer) clearInterval(this.countdownTimer);\r\n if (this.warningTimer) clearTimeout(this.warningTimer);\r\n \r\n // 关闭WebSocket连接\r\n if (this.socket) {\r\n this.socket.close();\r\n this.socket = null;\r\n }\r\n \r\n // 解绑事件监听器\r\n this.unbindEventListeners();\r\n },\r\n \r\n // 初始化WebSocket连接\r\n initWebSocket() {\r\n try {\r\n // 使用传入的WebSocket URL\r\n const websocketUrl = `wss://${location.host}/xy-api/auth-server/ws/user-activity`;\r\n \r\n // 获取token(假设存储在localStorage中)\r\n let token = '';\r\n try {\r\n const apiHeader = localStorage.getItem('api_header');\r\n if (apiHeader) {\r\n token = JSON.parse(apiHeader).Authorization || '';\r\n }\r\n } catch (e) {\r\n token = localStorage.getItem('token') || '';\r\n }\r\n \r\n // 创建WebSocket连接\r\n this.socket = new WebSocket(`${websocketUrl}?token=${encodeURIComponent(token)}`);\r\n \r\n // 设置连接成功回调\r\n this.socket.onopen = () => {\r\n console.log('WebSocket连接已建立');\r\n this.sendUserBehavior();\r\n this.$emit('websocket-open');\r\n };\r\n \r\n // 设置接收消息回调\r\n this.socket.onmessage = (event) => {\r\n try {\r\n const data = JSON.parse(event.data);\r\n console.log('收到用户活动状态消息:', data);\r\n this.handleActivityStatus(data);\r\n this.$emit('websocket-message', data);\r\n } catch (e) {\r\n console.error('解析WebSocket消息失败:', e);\r\n }\r\n };\r\n \r\n // 设置连接关闭回调\r\n this.socket.onclose = (event) => {\r\n console.log('WebSocket连接已断开:', event.reason);\r\n this.$emit('websocket-close');\r\n };\r\n \r\n // 设置连接错误回调\r\n this.socket.onerror = (error) => {\r\n console.error('WebSocket错误:', error);\r\n this.$emit('websocket-error', error);\r\n };\r\n \r\n } catch (error) {\r\n console.error('WebSocket初始化失败:', error);\r\n this.$emit('websocket-error', error);\r\n }\r\n },\r\n \r\n // 处理活动状态信息\r\n handleActivityStatus(data) {\r\n if (data.type === 'ACTIVITY_STATUS' && data.data) {\r\n const activityData = data.data;\r\n \r\n // 更新超时和警告时间配置\r\n this.currentTimeoutMinutes = activityData.timeoutMinutes || 10;\r\n this.currentWarningMinutes = activityData.reminderMinutes || 1;\r\n \r\n // 更新警告消息\r\n this.warningMessage = `您已${this.currentTimeoutMinutes}分钟未操作,将在${this.currentWarningMinutes}分钟后自动退出`;\r\n if(activityData.needReminder){\r\n this.showWarningWarning(activityData.remainingMillis);\r\n }\r\n // remainingMillis\r\n\r\n\r\n // 检查是否仍然活跃\r\n //if (!activityData.isActive) {\r\n //this.handleInactiveStatus();\r\n //}\r\n }\r\n // 检查是否需要显示提醒\r\n // let dataReminder=data.data;\r\n // if (dataReminder&&dataReminder.needReminder) {\r\n // this.showWarningWarning(dataReminder.remainingSeconds);\r\n // }\r\n },\r\n \r\n // 处理非活跃状态\r\n handleInactiveStatus() {\r\n // 清空缓存\r\n localStorage.clear();\r\n sessionStorage.clear();\r\n \r\n // 跳转到登录页\r\n //this.$router.push('/login');\r\n \r\n // 或者使用window.location\r\n window.location.href = '/login';\r\n \r\n // 触发登出事件\r\n this.$emit('logout');\r\n },\r\n \r\n // 发送用户行为数据到后端\r\n sendUserBehavior(data) {\r\n console.log('用户行为监测:')\r\n if (this.warningTimer) clearTimeout(this.warningTimer);\r\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\r\n const message = {\r\n \"type\": \"HEARTBEAT\",\r\n \"message\": \"心跳\",\r\n };\r\n console.log('用户行为监测:', JSON.stringify(message));\r\n this.socket.send(JSON.stringify(message));\r\n }\r\n },\r\n \r\n // 绑定事件监听器\r\n bindEventListeners() {\r\n // 鼠标事件\r\n document.addEventListener('click', this.handleUserActivity, true);\r\n document.addEventListener('dblclick', this.handleUserActivity, true);\r\n document.addEventListener('mousedown', this.handleUserActivity, true);\r\n document.addEventListener('mouseup', this.handleUserActivity, true);\r\n document.addEventListener('mousemove', this.handleMouseMove, true);\r\n document.addEventListener('mouseover', this.handleUserActivity, true);\r\n document.addEventListener('mouseout', this.handleUserActivity, true);\r\n \r\n // 键盘事件\r\n document.addEventListener('keydown', this.handleUserActivity, true);\r\n document.addEventListener('keyup', this.handleUserActivity, true);\r\n \r\n // 表单事件\r\n document.addEventListener('input', this.handleUserActivity, true);\r\n document.addEventListener('change', this.handleUserActivity, true);\r\n document.addEventListener('focus', this.handleUserActivity, true);\r\n document.addEventListener('blur', this.handleUserActivity, true);\r\n \r\n // 滚动事件(防抖处理)\r\n document.addEventListener('scroll', this.debounce(this.handleUserActivity, 300), true);\r\n \r\n // 窗口事件\r\n window.addEventListener('resize', this.handleUserActivity, true);\r\n window.addEventListener('beforeunload', this.handleBeforeUnload);\r\n },\r\n \r\n // 解绑事件监听器\r\n unbindEventListeners() {\r\n document.removeEventListener('click', this.handleUserActivity, true);\r\n document.removeEventListener('dblclick', this.handleUserActivity, true);\r\n document.removeEventListener('mousedown', this.handleUserActivity, true);\r\n document.removeEventListener('mouseup', this.handleUserActivity, true);\r\n document.removeEventListener('mousemove', this.handleMouseMove, true);\r\n document.removeEventListener('mouseover', this.handleUserActivity, true);\r\n document.removeEventListener('mouseout', this.handleUserActivity, true);\r\n \r\n document.removeEventListener('keydown', this.handleUserActivity, true);\r\n document.removeEventListener('keyup', this.handleUserActivity, true);\r\n \r\n document.removeEventListener('input', this.handleUserActivity, true);\r\n document.removeEventListener('change', this.handleUserActivity, true);\r\n document.removeEventListener('focus', this.handleUserActivity, true);\r\n document.removeEventListener('blur', this.handleUserActivity, true);\r\n \r\n document.removeEventListener('scroll', this.debounce(this.handleUserActivity, 300), true);\r\n \r\n window.removeEventListener('resize', this.handleUserActivity, true);\r\n window.removeEventListener('beforeunload', this.handleBeforeUnload);\r\n },\r\n \r\n // 处理用户活动\r\n handleUserActivity(event) {\r\n // 过滤掉自动触发的事件\r\n if (this.isAutomaticEvent(event)) return;\r\n \r\n this.resetTimer();\r\n \r\n // 发送用户行为数据\r\n const behaviorData = {\r\n eventType: event.type,\r\n target: event.target.tagName,\r\n timestamp: Date.now(),\r\n url: window.location.href\r\n };\r\n \r\n // 添加特定事件的额外信息\r\n if (event.type === 'click') {\r\n behaviorData.clientX = event.clientX;\r\n behaviorData.clientY = event.clientY;\r\n } else if (event.type === 'keydown') {\r\n behaviorData.key = event.key;\r\n behaviorData.ctrlKey = event.ctrlKey;\r\n behaviorData.altKey = event.altKey;\r\n behaviorData.shiftKey = event.shiftKey;\r\n }\r\n \r\n this.sendUserBehavior(behaviorData);\r\n },\r\n handleMouseMove(event) {\r\n if (!this.mouseMoveThrottled) {\r\n this.handleUserActivity(event);\r\n this.mouseMoveThrottled = true;\r\n setTimeout(() => {\r\n this.mouseMoveThrottled = false;\r\n }, 500);\r\n }\r\n },\r\n \r\n // 判断是否为自动触发事件\r\n isAutomaticEvent(event) {\r\n // 自动刷新等非用户主动触发的事件\r\n if (event.type === 'scroll' && !this.isUserScrolling) {\r\n return true;\r\n }\r\n return false;\r\n },\r\n \r\n // 防抖函数\r\n debounce(func, wait) {\r\n let timeout;\r\n return function executedFunction(...args) {\r\n const later = () => {\r\n clearTimeout(timeout);\r\n func.apply(this, args);\r\n };\r\n clearTimeout(timeout);\r\n timeout = setTimeout(later, wait);\r\n };\r\n },\r\n \r\n // 重置计时器\r\n resetTimer() {\r\n this.lastActivityTime = Date.now();\r\n this.showWarning = false;\r\n \r\n // 清除警告定时器\r\n if (this.warningTimer) {\r\n clearTimeout(this.warningTimer);\r\n this.warningTimer = null;\r\n }\r\n \r\n // 重新启动倒计时\r\n // this.startCountdown();\r\n \r\n this.$emit('user-active');\r\n },\r\n \r\n // 启动倒计时\r\n startCountdown() {\r\n // 清除现有定时器\r\n if (this.countdownTimer) clearInterval(this.countdownTimer);\r\n \r\n // 设置新的倒计时\r\n this.countdownTimer = setInterval(() => {\r\n const now = Date.now();\r\n const elapsedMinutes = (now - this.lastActivityTime) / (1000 * 50);\r\n \r\n // 检查是否需要显示警告\r\n if (elapsedMinutes >= (this.currentTimeoutMinutes - this.currentWarningMinutes) && !this.warningTimer) {\r\n this.showWarningWarning();\r\n }\r\n \r\n // 检查是否超时\r\n if (elapsedMinutes >= this.currentTimeoutMinutes) {\r\n this.handleTimeout();\r\n }\r\n }, 1000);\r\n },\r\n \r\n // 显示超时警告\r\n showWarningWarning(timeoutMinutes) {\r\n let time=timeoutMinutes||50;\r\n console.log('Setting showWarning to true');\r\n this.showWarning = true;\r\n this.$emit('timeout-warning');\r\n if (this.warningTimer) clearTimeout(this.warningTimer);\r\n // 设置超时处理\r\n this.warningTimer = setTimeout(() => {\r\n this.handleTimeout();\r\n }, 1 * time * 1000);\r\n },\r\n \r\n // 处理超时\r\n handleTimeout() {\r\n this.showWarning = false;\r\n this.$emit('timeout');\r\n \r\n // 执行登出逻辑\r\n this.logout();\r\n },\r\n \r\n // 登出操作\r\n logout() {\r\n localStorage.clear();\r\n sessionStorage.clear();\r\n location.reload();\r\n // 跳转到登录页\r\n //this.$router.push('/login');\r\n \r\n // 或者使用window.location\r\n // window.location.href = '/login';\r\n // 发送登出消息到后端\r\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\r\n const message = {\r\n type: 'logout',\r\n timestamp: Date.now()\r\n };\r\n this.socket.send(JSON.stringify(message));\r\n }\r\n \r\n // 触发登出事件\r\n this.$emit('logout');\r\n \r\n // 清除定时器\r\n if (this.countdownTimer) clearInterval(this.countdownTimer);\r\n if (this.warningTimer) clearTimeout(this.warningTimer);\r\n },\r\n \r\n // 处理页面卸载前的操作\r\n handleBeforeUnload(event) {\r\n // 发送页面关闭消息\r\n if (this.socket && this.socket.readyState === WebSocket.OPEN) {\r\n const message = {\r\n type: 'page_unload',\r\n timestamp: Date.now()\r\n };\r\n this.socket.send(JSON.stringify(message));\r\n }\r\n },\r\n \r\n // 手动重置监控\r\n reset() {\r\n // 检查当前路由,如果在登录页面则销毁监控\r\n if (this.isLoginRoute()) {\r\n this.destroyMonitor();\r\n return;\r\n }\r\n \r\n this.resetTimer();\r\n },\r\n \r\n // 重新连接WebSocket\r\n reconnect() {\r\n // 检查当前路由,如果在登录页面则销毁监控\r\n if (this.isLoginRoute()) {\r\n this.destroyMonitor();\r\n return;\r\n }\r\n \r\n if (this.socket) {\r\n this.socket.close();\r\n }\r\n this.initWebSocket();\r\n }\r\n }\r\n};\r\n</script>\r\n<style>\r\n.behavior-warning-dialog.el-dialog.el-dialog--center{\r\n text-align: left;\r\n}\r\n</style>","import mod from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./UserBehaviorMonitor.vue?vue&type=script&lang=js\"; export default mod; export * from \"-!../../node_modules/cache-loader/dist/cjs.js??ref--0-0!../../node_modules/vue-loader/lib/index.js??vue-loader-options!./UserBehaviorMonitor.vue?vue&type=script&lang=js\"","/* globals __VUE_SSR_CONTEXT__ */\n\n// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).\n// This module is a runtime utility for cleaner component module output and will\n// be included in the final webpack user bundle.\n\nexport default function normalizeComponent(\n scriptExports,\n render,\n staticRenderFns,\n functionalTemplate,\n injectStyles,\n scopeId,\n moduleIdentifier /* server only */,\n shadowMode /* vue-cli only */\n) {\n // Vue.extend constructor export interop\n var options =\n typeof scriptExports === 'function' ? scriptExports.options : scriptExports\n\n // render functions\n if (render) {\n options.render = render\n options.staticRenderFns = staticRenderFns\n options._compiled = true\n }\n\n // functional template\n if (functionalTemplate) {\n options.functional = true\n }\n\n // scopedId\n if (scopeId) {\n options._scopeId = 'data-v-' + scopeId\n }\n\n var hook\n if (moduleIdentifier) {\n // server build\n hook = function (context) {\n // 2.3 injection\n context =\n context || // cached call\n (this.$vnode && this.$vnode.ssrContext) || // stateful\n (this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional\n // 2.2 with runInNewContext: true\n if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {\n context = __VUE_SSR_CONTEXT__\n }\n // inject component styles\n if (injectStyles) {\n injectStyles.call(this, context)\n }\n // register component module identifier for async chunk inferrence\n if (context && context._registeredComponents) {\n context._registeredComponents.add(moduleIdentifier)\n }\n }\n // used by ssr in case component is cached and beforeCreate\n // never gets called\n options._ssrRegister = hook\n } else if (injectStyles) {\n hook = shadowMode\n ? function () {\n injectStyles.call(\n this,\n (options.functional ? this.parent : this).$root.$options.shadowRoot\n )\n }\n : injectStyles\n }\n\n if (hook) {\n if (options.functional) {\n // for template-only hot-reload because in that case the render fn doesn't\n // go through the normalizer\n options._injectStyles = hook\n // register for functional component in vue file\n var originalRender = options.render\n options.render = function renderWithStyleInjection(h, context) {\n hook.call(context)\n return originalRender(h, context)\n }\n } else {\n // inject component registration as beforeCreate hook\n var existing = options.beforeCreate\n options.beforeCreate = existing ? [].concat(existing, hook) : [hook]\n }\n }\n\n return {\n exports: scriptExports,\n options: options\n }\n}\n","import { render, staticRenderFns } from \"./UserBehaviorMonitor.vue?vue&type=template&id=4f34cec0\"\nimport script from \"./UserBehaviorMonitor.vue?vue&type=script&lang=js\"\nexport * from \"./UserBehaviorMonitor.vue?vue&type=script&lang=js\"\nimport style0 from \"./UserBehaviorMonitor.vue?vue&type=style&index=0&id=4f34cec0&prod&lang=css\"\n\n\n/* normalize component */\nimport normalizer from \"!../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import UserBehaviorMonitor from './components/UserBehaviorMonitor.vue';\r\n\r\n// 为使用 CDN 方式引入的用户提供组件注册方法\r\nUserBehaviorMonitor.install = function(Vue) {\r\n Vue.component(UserBehaviorMonitor.name, UserBehaviorMonitor);\r\n};\r\n\r\n// 导出组件\r\nexport default UserBehaviorMonitor;\r\n\r\n// 如果是直接引入文件,则自动注册组件\r\nif (typeof window !== 'undefined' && window.Vue) {\r\n UserBehaviorMonitor.install(window.Vue);\r\n}\r\n\r\n// 同时支持按需导入\r\nexport { UserBehaviorMonitor };","import './setPublicPath'\nimport mod from '~entry'\nexport default mod\nexport * from '~entry'\n"],"sourceRoot":""}