@uxda/appkit 4.2.92 → 4.2.94
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/appkit.css +2 -2
- package/dist/index.js +1085 -24
- package/package.json +2 -1
- package/src/balance/components/AccountView.vue +1 -1
- package/src/payment/components/RechargeView.vue +2 -1
- package/src/payment/types.ts +1 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/tracking/README.md +509 -0
- package/src/shared/tracking/directives/index.ts +40 -0
- package/src/shared/tracking/directives/track-click.ts +142 -0
- package/src/shared/tracking/directives/track-page.ts +50 -0
- package/src/shared/tracking/directives/track-scroll.ts +116 -0
- package/src/shared/tracking/directives/track-search.ts +179 -0
- package/src/shared/tracking/examples/data-structure-example.ts +239 -0
- package/src/shared/tracking/examples/directive-tracking-example.vue +202 -0
- package/src/shared/tracking/examples/global-tracking-example.vue +25 -0
- package/src/shared/tracking/examples/page-tracking-template.vue +27 -0
- package/src/shared/tracking/examples/sdk-tracking-example.vue +33 -0
- package/src/shared/tracking/index.ts +5 -0
- package/src/shared/tracking/tracking-init.ts +214 -0
- package/src/shared/tracking/tracking-sdk.ts +986 -0
- package/src/shared/tracking/useAutoTracking.ts +108 -0
- package/src/user/components/UserBinding.vue +2 -2
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全埋点SDK - 精简版
|
|
3
|
+
* 集成核心埋点功能,支持自动埋点和手动埋点
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { onAppHide, setStorageSync, getSystemInfo, getNetworkType, getStorageSync, getEnv, getAccountInfoSync, getPerformance, request } from '@tarojs/taro'
|
|
7
|
+
import pako from 'pako'
|
|
8
|
+
import { isApp } from '../../utils/utils'
|
|
9
|
+
|
|
10
|
+
// 埋点事件类型枚举
|
|
11
|
+
export enum TrackingEventType {
|
|
12
|
+
PAGE_VIEW = 'page_view', // 页面访问
|
|
13
|
+
PAGE_LEAVE = 'page_leave', // 页面离开
|
|
14
|
+
APP_DEVICE_INFO = 'app_device_info', // app和device信息
|
|
15
|
+
CLICK = 'click', // 点击事件
|
|
16
|
+
CUSTOM = 'custom', // 自定义事件
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 导出枚举值供外部使用
|
|
20
|
+
export const { PAGE_VIEW, PAGE_LEAVE, CLICK, CUSTOM, APP_DEVICE_INFO } = TrackingEventType
|
|
21
|
+
|
|
22
|
+
// 日志类型(支持组合类型,如 "page/event/error/device/app")
|
|
23
|
+
export type LogType = 'page' | 'event' | 'error' | 'device' | 'app' | 'performance' | string
|
|
24
|
+
|
|
25
|
+
// 来源类型
|
|
26
|
+
export type SourceType = 'app' | 'H5' | 'PC' | string
|
|
27
|
+
|
|
28
|
+
// 页面信息接口
|
|
29
|
+
export interface PageData {
|
|
30
|
+
page_name?: string // 页面名称
|
|
31
|
+
page_url?: string // 页面URL
|
|
32
|
+
page_referrer?: string // 页面来源
|
|
33
|
+
previous_page_name?: string // 上一页名称
|
|
34
|
+
page_start_time?: string // 页面开始时间 (ISO 8601格式)
|
|
35
|
+
page_duration?: number // 页面停留时长(毫秒)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 事件信息接口
|
|
39
|
+
export interface EventData {
|
|
40
|
+
event_type?: string // 事件类型
|
|
41
|
+
event_name?: string // 事件名称
|
|
42
|
+
element_id?: string // 元素ID
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 应用信息接口
|
|
46
|
+
export interface AppData {
|
|
47
|
+
app_version?: string // 应用版本
|
|
48
|
+
package_name?: string // 包名
|
|
49
|
+
app_channel?: string // 应用渠道
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 设备信息接口
|
|
53
|
+
export interface DeviceData {
|
|
54
|
+
device_type?: string // 设备类型 (mobile/tablet/desktop)
|
|
55
|
+
os?: string // 操作系统
|
|
56
|
+
os_version?: string // 操作系统版本
|
|
57
|
+
brand?: string // 品牌
|
|
58
|
+
model?: string // 型号
|
|
59
|
+
network_type?: string // 网络类型
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 性能信息接口
|
|
63
|
+
export interface PerformanceData {
|
|
64
|
+
page_load_time?: number // 页面加载时间(毫秒)
|
|
65
|
+
response_time?: number // 响应时间(毫秒)
|
|
66
|
+
fps?: number // 帧率
|
|
67
|
+
memory_usage?: number // 内存使用率(%)
|
|
68
|
+
battery_usage?: number // 电池使用率(%)
|
|
69
|
+
network_latency?: number // 网络延迟(毫秒)
|
|
70
|
+
crash_rate?: number // 崩溃率(%)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 错误信息接口
|
|
74
|
+
export interface ErrorData {
|
|
75
|
+
error_code?: string // 错误代码
|
|
76
|
+
error_message?: string // 错误消息
|
|
77
|
+
stack_trace?: string // 堆栈跟踪
|
|
78
|
+
error_level?: string // 错误级别 (info/warning/error)
|
|
79
|
+
error_context?: string // 错误上下文
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 日志数据接口
|
|
83
|
+
export interface LogData {
|
|
84
|
+
page?: PageData
|
|
85
|
+
event?: EventData
|
|
86
|
+
app?: AppData
|
|
87
|
+
device?: DeviceData
|
|
88
|
+
performance?: PerformanceData
|
|
89
|
+
error?: ErrorData
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 埋点主数据接口(符合新的数据结构)
|
|
93
|
+
export interface TrackingMainData {
|
|
94
|
+
log_id?: string // 日志ID
|
|
95
|
+
log_time: number // 日志时间戳
|
|
96
|
+
log_type: LogType // 日志类型(支持组合类型,如 "page/event/error/device/app")
|
|
97
|
+
tenant_id?: string // 租户ID
|
|
98
|
+
user_id?: string // 用户ID
|
|
99
|
+
source: SourceType // 来源 (app/H5/PC)
|
|
100
|
+
session_id: string // 会话ID
|
|
101
|
+
log_data: LogData // 日志数据
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 内部使用的数据结构(用于方法参数)
|
|
105
|
+
interface TrackingData {
|
|
106
|
+
logTime?: number // 时间戳
|
|
107
|
+
sessionId?: string // 会话ID
|
|
108
|
+
userId?: string // 用户ID
|
|
109
|
+
tenantId?: string // 租户ID
|
|
110
|
+
pagePath?: string // 页面路径
|
|
111
|
+
pageTitle?: string // 页面标题
|
|
112
|
+
lastPagePath?: string // 上一页路径
|
|
113
|
+
lastPageTitle?: string // 上一页标题
|
|
114
|
+
elementId?: string // 元素ID
|
|
115
|
+
elementText?: string // 元素文本
|
|
116
|
+
businessData?: Record<string, unknown> // 业务数据
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 用户信息接口
|
|
120
|
+
export interface TrackingUserInfo {
|
|
121
|
+
userId?: string // 用户ID
|
|
122
|
+
tenantId?: string // 租户ID
|
|
123
|
+
sessionId?: string // 会话ID
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 埋点配置接口
|
|
127
|
+
export interface TrackingConfig {
|
|
128
|
+
enabled: boolean // 是否启用埋点
|
|
129
|
+
debug: boolean // 是否开启调试模式
|
|
130
|
+
batchSize: number // 批量发送大小
|
|
131
|
+
flushInterval: number // 刷新间隔(ms)
|
|
132
|
+
apiEndpoint: string // API端点
|
|
133
|
+
userInfo?: TrackingUserInfo // 用户信息
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 默认配置
|
|
137
|
+
const DEFAULT_CONFIG: TrackingConfig = {
|
|
138
|
+
enabled: true,
|
|
139
|
+
debug: false,
|
|
140
|
+
batchSize: 30,
|
|
141
|
+
flushInterval: 100000,
|
|
142
|
+
apiEndpoint: '',
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 全埋点SDK类
|
|
147
|
+
*/
|
|
148
|
+
// 页面历史记录接口
|
|
149
|
+
interface PageHistory {
|
|
150
|
+
route: string
|
|
151
|
+
title: string
|
|
152
|
+
timestamp: number
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
class TrackingSDK {
|
|
156
|
+
private config: TrackingConfig
|
|
157
|
+
private eventQueue: TrackingMainData[] = []
|
|
158
|
+
private currentPage: string = ''
|
|
159
|
+
private pageStartTime: number = 0
|
|
160
|
+
private flushTimer: ReturnType<typeof setInterval> | null = null
|
|
161
|
+
private isInitialized: boolean = false
|
|
162
|
+
private pageHistory: PageHistory[] = [] // 页面历史记录
|
|
163
|
+
private readonly PAGE_HISTORY_KEY = 'tracking_page_history' // 存储键
|
|
164
|
+
private readonly MAX_HISTORY_LENGTH = 10 // 最大历史记录数
|
|
165
|
+
private userInfo: TrackingUserInfo | undefined = undefined // 用户信息(优先使用)
|
|
166
|
+
|
|
167
|
+
constructor(config: Partial<TrackingConfig> = {}) {
|
|
168
|
+
this.config = { ...DEFAULT_CONFIG, ...config }
|
|
169
|
+
// 保存传入的用户信息
|
|
170
|
+
if (config.userInfo) {
|
|
171
|
+
this.userInfo = config.userInfo
|
|
172
|
+
}
|
|
173
|
+
this.init()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 初始化SDK
|
|
178
|
+
*/
|
|
179
|
+
private init(): void {
|
|
180
|
+
if (this.isInitialized) return
|
|
181
|
+
|
|
182
|
+
this.isInitialized = true
|
|
183
|
+
this.loadPageHistory() // 加载页面历史记录
|
|
184
|
+
this.setupAutoTracking()
|
|
185
|
+
this.startFlushTimer()
|
|
186
|
+
|
|
187
|
+
if (this.config.debug) {
|
|
188
|
+
console.log('[TrackingSDK] 初始化完成', this.config)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 加载页面历史记录
|
|
194
|
+
*/
|
|
195
|
+
private loadPageHistory(): void {
|
|
196
|
+
try {
|
|
197
|
+
const historyStr = getStorageSync(this.PAGE_HISTORY_KEY)
|
|
198
|
+
if (historyStr) {
|
|
199
|
+
this.pageHistory = JSON.parse(historyStr)
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (this.config.debug) {
|
|
203
|
+
console.log('[TrackingSDK] 加载页面历史失败:', error)
|
|
204
|
+
}
|
|
205
|
+
this.pageHistory = []
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* 简单的Token加密 (Base64 + 反转)
|
|
211
|
+
* @returns 加密后的token字符串
|
|
212
|
+
*/
|
|
213
|
+
private encryptToken(): string {
|
|
214
|
+
const raw = `Ddjf@Log1125|${Date.now()}`
|
|
215
|
+
|
|
216
|
+
// Base64 编码(兼容 H5 和小程序环境)
|
|
217
|
+
let base64: string
|
|
218
|
+
if (typeof btoa !== 'undefined') {
|
|
219
|
+
// H5 环境使用 btoa
|
|
220
|
+
base64 = btoa(unescape(encodeURIComponent(raw)))
|
|
221
|
+
} else {
|
|
222
|
+
// 小程序环境使用手动实现的 Base64 编码
|
|
223
|
+
base64 = this.base64Encode(raw)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 反转字符串
|
|
227
|
+
return base64.split('').reverse().join('')
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Base64 编码(降级方案)
|
|
231
|
+
*/
|
|
232
|
+
private base64Encode(str: string): string {
|
|
233
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
|
|
234
|
+
let output = ''
|
|
235
|
+
let i = 0
|
|
236
|
+
|
|
237
|
+
while (i < str.length) {
|
|
238
|
+
const a = str.charCodeAt(i++)
|
|
239
|
+
const b = i < str.length ? str.charCodeAt(i++) : 0
|
|
240
|
+
const c = i < str.length ? str.charCodeAt(i++) : 0
|
|
241
|
+
|
|
242
|
+
const bitmap = (a << 16) | (b << 8) | c
|
|
243
|
+
|
|
244
|
+
output += chars.charAt((bitmap >> 18) & 63)
|
|
245
|
+
output += chars.charAt((bitmap >> 12) & 63)
|
|
246
|
+
output += i - 2 < str.length ? chars.charAt((bitmap >> 6) & 63) : '='
|
|
247
|
+
output += i - 1 < str.length ? chars.charAt(bitmap & 63) : '='
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return output
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* 保存页面历史记录
|
|
255
|
+
*/
|
|
256
|
+
private savePageHistory(): void {
|
|
257
|
+
try {
|
|
258
|
+
setStorageSync(this.PAGE_HISTORY_KEY, JSON.stringify(this.pageHistory))
|
|
259
|
+
} catch (error) {
|
|
260
|
+
if (this.config.debug) {
|
|
261
|
+
console.log('[TrackingSDK] 保存页面历史失败:', error)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 添加页面到历史记录
|
|
268
|
+
*/
|
|
269
|
+
private addPageToHistory(route: string, title: string): void {
|
|
270
|
+
const history: PageHistory = {
|
|
271
|
+
route,
|
|
272
|
+
title,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// 添加到历史记录
|
|
277
|
+
this.pageHistory.push(history)
|
|
278
|
+
|
|
279
|
+
// 保持历史记录在最大长度内
|
|
280
|
+
if (this.pageHistory.length > this.MAX_HISTORY_LENGTH) {
|
|
281
|
+
this.pageHistory.shift()
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 保存到本地存储
|
|
285
|
+
this.savePageHistory()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 获取上一页信息(从历史记录中)
|
|
290
|
+
*/
|
|
291
|
+
private getLastPageFromHistory(): PageHistory | null {
|
|
292
|
+
if (this.pageHistory.length >= 2) {
|
|
293
|
+
// 返回倒数第二个页面(最后一个是当前页面)
|
|
294
|
+
return this.pageHistory[this.pageHistory.length - 2]
|
|
295
|
+
}
|
|
296
|
+
return null
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 清空页面历史记录
|
|
301
|
+
*/
|
|
302
|
+
public clearPageHistory(): void {
|
|
303
|
+
this.pageHistory = []
|
|
304
|
+
this.savePageHistory()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 获取用户信息
|
|
309
|
+
* 优先级:1. 初始化时传入的userInfo 2. Storage中的用户信息 3. 生成新的sessionId
|
|
310
|
+
*/
|
|
311
|
+
private getUserInfo(): { userId?: string; tenantId?: string; sessionId?: string } {
|
|
312
|
+
if (this.userInfo) {
|
|
313
|
+
return {
|
|
314
|
+
userId: this.userInfo.userId,
|
|
315
|
+
tenantId: this.userInfo.tenantId,
|
|
316
|
+
sessionId: this.userInfo.sessionId || this.generateSessionId(),
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return { sessionId: this.generateSessionId() }
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 生成会话ID
|
|
325
|
+
*/
|
|
326
|
+
private generateSessionId(): string {
|
|
327
|
+
return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 获取设备信息
|
|
332
|
+
*/
|
|
333
|
+
private async getDeviceInfo(): Promise<DeviceData> {
|
|
334
|
+
try {
|
|
335
|
+
const systemInfo = await getSystemInfo()
|
|
336
|
+
|
|
337
|
+
// 获取网络类型
|
|
338
|
+
let networkType = ''
|
|
339
|
+
try {
|
|
340
|
+
const networkInfo = await getNetworkType()
|
|
341
|
+
networkType = networkInfo.networkType || ''
|
|
342
|
+
} catch {
|
|
343
|
+
// 网络类型获取失败,使用空字符串
|
|
344
|
+
networkType = ''
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
device_type:
|
|
349
|
+
systemInfo.platform === 'ios' || systemInfo.platform === 'android' ? 'mobile' : 'desktop',
|
|
350
|
+
os: systemInfo.platform,
|
|
351
|
+
os_version: systemInfo.system,
|
|
352
|
+
brand: systemInfo.brand || '',
|
|
353
|
+
model: systemInfo.model || '',
|
|
354
|
+
network_type: networkType,
|
|
355
|
+
}
|
|
356
|
+
} catch {
|
|
357
|
+
return {}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 获取应用信息
|
|
363
|
+
*/
|
|
364
|
+
private getAppInfo(): AppData {
|
|
365
|
+
try {
|
|
366
|
+
const env = getEnv()
|
|
367
|
+
if (env === 'WEAPP') {
|
|
368
|
+
const accountInfo = getAccountInfoSync?.()
|
|
369
|
+
return {
|
|
370
|
+
app_version: accountInfo?.miniProgram?.version || '1.0.0',
|
|
371
|
+
package_name: accountInfo?.miniProgram?.appId || '',
|
|
372
|
+
app_channel: 'WeChat',
|
|
373
|
+
}
|
|
374
|
+
} else {
|
|
375
|
+
return {
|
|
376
|
+
app_version: '1.0.0',
|
|
377
|
+
package_name: '',
|
|
378
|
+
app_channel: env === 'WEB' ? 'H5' : 'App Store',
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
} catch {
|
|
382
|
+
return {
|
|
383
|
+
app_version: '1.0.0',
|
|
384
|
+
package_name: '',
|
|
385
|
+
app_channel: getEnv() === 'WEB' ? 'H5' : 'Unknown',
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* 获取来源类型
|
|
392
|
+
*/
|
|
393
|
+
private getSourceType(): SourceType {
|
|
394
|
+
const env = getEnv()
|
|
395
|
+
if (isApp()) return 'app'
|
|
396
|
+
if (env === 'WEB') return 'H5'
|
|
397
|
+
if (env === 'WEAPP') return 'app'
|
|
398
|
+
return 'PC'
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 获取页面性能数据
|
|
403
|
+
*/
|
|
404
|
+
private async getPerformanceData(): Promise<PerformanceData> {
|
|
405
|
+
try {
|
|
406
|
+
const performanceData: PerformanceData = {}
|
|
407
|
+
const env = getEnv()
|
|
408
|
+
|
|
409
|
+
if (env === 'WEB') {
|
|
410
|
+
// H5 环境:使用浏览器原生 Performance API
|
|
411
|
+
if (typeof window !== 'undefined' && window.performance) {
|
|
412
|
+
const perfEntries = window.performance.getEntriesByType('navigation')
|
|
413
|
+
if (perfEntries && perfEntries.length > 0) {
|
|
414
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
415
|
+
const navigationTiming = perfEntries[0] as any
|
|
416
|
+
// 页面加载完成时间
|
|
417
|
+
performanceData.page_load_time = Math.round(navigationTiming.duration || 0)
|
|
418
|
+
// 响应时间
|
|
419
|
+
performanceData.response_time = Math.round(
|
|
420
|
+
(navigationTiming.responseEnd || 0) - (navigationTiming.requestStart || 0)
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// 获取内存信息(部分浏览器支持)
|
|
425
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
426
|
+
const memory = (window.performance as any).memory
|
|
427
|
+
if (memory) {
|
|
428
|
+
const usedMemory = memory.usedJSHeapSize || 0
|
|
429
|
+
const totalMemory = memory.jsHeapSizeLimit || 0
|
|
430
|
+
if (totalMemory > 0) {
|
|
431
|
+
performanceData.memory_usage = Number(((usedMemory / totalMemory) * 100).toFixed(2))
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
// 小程序环境:使用 Taro.getPerformance()
|
|
437
|
+
const performance = await getPerformance()
|
|
438
|
+
if (performance) {
|
|
439
|
+
// 页面加载时间
|
|
440
|
+
const entryList = performance.getEntriesByType?.('navigation')
|
|
441
|
+
if (entryList && entryList.length > 0) {
|
|
442
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
443
|
+
const navigationTiming = entryList[0] as any
|
|
444
|
+
// 页面加载完成时间
|
|
445
|
+
performanceData.page_load_time = Math.round(navigationTiming.duration || 0)
|
|
446
|
+
// 响应时间
|
|
447
|
+
performanceData.response_time = Math.round(
|
|
448
|
+
(navigationTiming.responseEnd || 0) - (navigationTiming.requestStart || 0)
|
|
449
|
+
)
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// 获取内存信息(仅小程序支持)
|
|
453
|
+
if (env === 'WEAPP') {
|
|
454
|
+
try {
|
|
455
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
456
|
+
const performanceObj = performance as any
|
|
457
|
+
const memoryInfo = performanceObj?.getMemoryInfo?.()
|
|
458
|
+
if (memoryInfo) {
|
|
459
|
+
// 计算内存使用率
|
|
460
|
+
const usedMemory = memoryInfo.usedJSHeapSize || 0
|
|
461
|
+
const totalMemory = memoryInfo.totalJSHeapSize || memoryInfo.jsHeapSizeLimit || 0
|
|
462
|
+
if (totalMemory > 0) {
|
|
463
|
+
performanceData.memory_usage = Number(
|
|
464
|
+
((usedMemory / totalMemory) * 100).toFixed(2)
|
|
465
|
+
)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} catch {
|
|
469
|
+
// 内存信息获取失败,忽略
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return performanceData
|
|
476
|
+
} catch (error) {
|
|
477
|
+
if (this.config.debug) {
|
|
478
|
+
console.log('[TrackingSDK] 获取性能数据失败:', error)
|
|
479
|
+
}
|
|
480
|
+
return {}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* 创建基础埋点数据(新版数据结构)
|
|
486
|
+
*/
|
|
487
|
+
private async createBaseTrackingData(
|
|
488
|
+
eventType: TrackingEventType,
|
|
489
|
+
customData?: Partial<TrackingData>
|
|
490
|
+
): Promise<TrackingMainData> {
|
|
491
|
+
const userInfo = this.getUserInfo()
|
|
492
|
+
const pagePath = customData?.pagePath || this.currentPage || ''
|
|
493
|
+
const now = Date.now()
|
|
494
|
+
const pageData: PageData = {}
|
|
495
|
+
const eventData: EventData = {}
|
|
496
|
+
let logType: string = ''
|
|
497
|
+
|
|
498
|
+
// 判断是否需要包含app和device信息
|
|
499
|
+
let appInfo: AppData = {}
|
|
500
|
+
let deviceInfo: DeviceData = {}
|
|
501
|
+
let performanceData: PerformanceData = {}
|
|
502
|
+
|
|
503
|
+
if (eventType === TrackingEventType.APP_DEVICE_INFO) {
|
|
504
|
+
deviceInfo = await this.getDeviceInfo()
|
|
505
|
+
appInfo = this.getAppInfo()
|
|
506
|
+
} else {
|
|
507
|
+
// 构建页面数据
|
|
508
|
+
if (pagePath) {
|
|
509
|
+
pageData.page_url = pagePath
|
|
510
|
+
pageData.page_name = customData?.pageTitle || this.getPageTitle(pagePath)
|
|
511
|
+
pageData.page_referrer = customData?.lastPagePath || ''
|
|
512
|
+
pageData.previous_page_name = customData?.lastPageTitle || ''
|
|
513
|
+
if (this.pageStartTime) {
|
|
514
|
+
pageData.page_start_time = new Date(this.pageStartTime).toISOString()
|
|
515
|
+
pageData.page_duration = now - this.pageStartTime
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// 构建事件数据
|
|
520
|
+
if (eventType === TrackingEventType.CLICK) {
|
|
521
|
+
eventData.event_type = 'click'
|
|
522
|
+
eventData.event_name =
|
|
523
|
+
customData?.elementText || (customData?.businessData?.elementText as string) || '点击事件'
|
|
524
|
+
eventData.element_id = customData?.elementId || ''
|
|
525
|
+
} else if (eventType === TrackingEventType.CUSTOM) {
|
|
526
|
+
eventData.event_type = 'custom'
|
|
527
|
+
eventData.event_name = (customData?.businessData?.eventName as string) || '自定义事件'
|
|
528
|
+
} else {
|
|
529
|
+
eventData.event_type = eventType
|
|
530
|
+
eventData.event_name = eventType
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// 如果是页面访问事件,自动采集性能数据
|
|
534
|
+
if (eventType === TrackingEventType.PAGE_VIEW || customData?.businessData?.performance) {
|
|
535
|
+
performanceData = await this.getPerformanceData()
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 构建日志类型
|
|
540
|
+
if ([TrackingEventType.CLICK, TrackingEventType.CUSTOM].includes(eventType)) {
|
|
541
|
+
logType = 'event'
|
|
542
|
+
} else if ([TrackingEventType.PAGE_VIEW, TrackingEventType.PAGE_LEAVE].includes(eventType)) {
|
|
543
|
+
logType = 'page'
|
|
544
|
+
} else if ([TrackingEventType.APP_DEVICE_INFO].includes(eventType)) {
|
|
545
|
+
logType = 'device'
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// 合并性能数据:优先使用自动采集的,其次使用手动传入的
|
|
549
|
+
const finalPerformanceData: PerformanceData = {
|
|
550
|
+
...performanceData,
|
|
551
|
+
...(customData?.businessData?.performance as PerformanceData),
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// 构建 log_data,过滤掉空值
|
|
555
|
+
const logData: LogData = {}
|
|
556
|
+
if (Object.keys(pageData).length > 0) {
|
|
557
|
+
logData.page = pageData
|
|
558
|
+
}
|
|
559
|
+
if (Object.keys(eventData).length > 0) {
|
|
560
|
+
logData.event = eventData
|
|
561
|
+
}
|
|
562
|
+
if (Object.keys(appInfo).length > 0) {
|
|
563
|
+
logData.app = appInfo
|
|
564
|
+
}
|
|
565
|
+
if (Object.keys(deviceInfo).length > 0) {
|
|
566
|
+
logData.device = deviceInfo
|
|
567
|
+
}
|
|
568
|
+
if (Object.keys(finalPerformanceData).length > 0) {
|
|
569
|
+
logData.performance = finalPerformanceData
|
|
570
|
+
}
|
|
571
|
+
const errorData = customData?.businessData?.error as ErrorData
|
|
572
|
+
if (errorData && Object.keys(errorData).length > 0) {
|
|
573
|
+
logData.error = errorData
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
const trackingData: TrackingMainData = {
|
|
577
|
+
log_id: `${now}_${Math.random().toString(36).substr(2, 9)}`,
|
|
578
|
+
log_time: now,
|
|
579
|
+
log_type: logType || 'event',
|
|
580
|
+
tenant_id: customData?.tenantId || userInfo.tenantId || '',
|
|
581
|
+
user_id: customData?.userId || userInfo.userId || '',
|
|
582
|
+
source: this.getSourceType(),
|
|
583
|
+
session_id: userInfo.sessionId || '',
|
|
584
|
+
log_data: logData,
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return trackingData
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* 根据页面路径获取页面标题
|
|
592
|
+
*/
|
|
593
|
+
private getPageTitle(pagePath: string): string {
|
|
594
|
+
const titleMap: Record<string, string> = {
|
|
595
|
+
'/aiapprove/pages': 'AI审批',
|
|
596
|
+
'/aiapprove/views': '企明星',
|
|
597
|
+
'/pages/beidou': '北斗星',
|
|
598
|
+
'/pages/contracts': '蜂鸟签约',
|
|
599
|
+
'/pages/user': '我的',
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
for (const [path, title] of Object.entries(titleMap)) {
|
|
603
|
+
if (pagePath.includes(path)) {
|
|
604
|
+
return title
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
return pagePath
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* 添加埋点数据到队列
|
|
613
|
+
*/
|
|
614
|
+
private addToQueue(data: TrackingMainData): void {
|
|
615
|
+
if (!this.config.enabled) return
|
|
616
|
+
|
|
617
|
+
this.eventQueue.push(data)
|
|
618
|
+
|
|
619
|
+
if (this.config.debug) {
|
|
620
|
+
console.log('[TrackingSDK] 添加埋点数据:', data)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// 检查是否需要立即发送
|
|
624
|
+
if (this.eventQueue.length >= this.config.batchSize) {
|
|
625
|
+
this.flush()
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* 发送埋点数据
|
|
631
|
+
*/
|
|
632
|
+
private async flush(): Promise<void> {
|
|
633
|
+
if (this.eventQueue.length === 0) return
|
|
634
|
+
|
|
635
|
+
const events = [...this.eventQueue]
|
|
636
|
+
this.eventQueue = []
|
|
637
|
+
|
|
638
|
+
try {
|
|
639
|
+
// 将对象数组转为 JSON 字符串
|
|
640
|
+
const jsonStr = JSON.stringify(events)
|
|
641
|
+
|
|
642
|
+
// 使用 pako 压缩
|
|
643
|
+
const compressed = pako.deflate(jsonStr)
|
|
644
|
+
|
|
645
|
+
// if (this.config.debug) {
|
|
646
|
+
// console.log('[TrackingSDK] 原始数据大小:', new Blob([jsonStr]).size, 'bytes')
|
|
647
|
+
// console.log('[TrackingSDK] 压缩后大小:', compressed.length, 'bytes')
|
|
648
|
+
// console.log(
|
|
649
|
+
// '[TrackingSDK] 压缩率:',
|
|
650
|
+
// ((compressed.length / new Blob([jsonStr]).size) * 100).toFixed(2) + '%'
|
|
651
|
+
// )
|
|
652
|
+
// }
|
|
653
|
+
|
|
654
|
+
// 批量发送埋点数据
|
|
655
|
+
const encryptedToken = this.encryptToken()
|
|
656
|
+
const { success } = await this.http({
|
|
657
|
+
url: `${this.config.apiEndpoint}?contentId=${encryptedToken}`,
|
|
658
|
+
method: 'POST',
|
|
659
|
+
data: compressed,
|
|
660
|
+
header: {
|
|
661
|
+
'Content-Encoding': 'deflate',
|
|
662
|
+
},
|
|
663
|
+
})
|
|
664
|
+
|
|
665
|
+
if (success && this.config.debug) {
|
|
666
|
+
console.log('[TrackingSDK] 埋点数据发送成功:', events.length, '条')
|
|
667
|
+
}
|
|
668
|
+
} catch (error) {
|
|
669
|
+
console.error('[TrackingSDK] 埋点数据发送失败:', error)
|
|
670
|
+
|
|
671
|
+
// 重试逻辑 - 将失败的数据放回队列
|
|
672
|
+
if (events.length > 0) {
|
|
673
|
+
this.eventQueue.unshift(...events)
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* 请求拦截
|
|
680
|
+
*/
|
|
681
|
+
private http(params: {
|
|
682
|
+
url: string
|
|
683
|
+
method: 'GET' | 'POST'
|
|
684
|
+
data: any
|
|
685
|
+
header: any
|
|
686
|
+
}): Promise<any> {
|
|
687
|
+
return new Promise((resolve, reject) => {
|
|
688
|
+
request({
|
|
689
|
+
url: params.url,
|
|
690
|
+
method: params.method || 'GET',
|
|
691
|
+
data: params.data,
|
|
692
|
+
header: params.header,
|
|
693
|
+
})
|
|
694
|
+
.then((res) => {
|
|
695
|
+
console.log('===res', res)
|
|
696
|
+
resolve(res.data)
|
|
697
|
+
})
|
|
698
|
+
.catch((err) => {
|
|
699
|
+
reject(err)
|
|
700
|
+
})
|
|
701
|
+
})
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 启动定时刷新
|
|
706
|
+
*/
|
|
707
|
+
private startFlushTimer(): void {
|
|
708
|
+
if (this.flushTimer) {
|
|
709
|
+
clearInterval(this.flushTimer)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.flushTimer = setInterval(() => {
|
|
713
|
+
this.flush()
|
|
714
|
+
}, this.config.flushInterval)
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* 设置自动埋点
|
|
719
|
+
*/
|
|
720
|
+
private setupAutoTracking(): void {
|
|
721
|
+
// 监听页面隐藏 - 立即发送剩余数据
|
|
722
|
+
onAppHide(() => {
|
|
723
|
+
this.trackPageLeave()
|
|
724
|
+
// 立即发送剩余的埋点数据
|
|
725
|
+
this.flushNow()
|
|
726
|
+
})
|
|
727
|
+
|
|
728
|
+
// H5 环境:监听页面卸载事件
|
|
729
|
+
if (getEnv() === 'WEB' && typeof window !== 'undefined') {
|
|
730
|
+
// 使用 beforeunload 事件
|
|
731
|
+
window.addEventListener('beforeunload', () => {
|
|
732
|
+
this.flushNow()
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
// 使用 visibilitychange 事件(页面隐藏时发送)
|
|
736
|
+
document.addEventListener('visibilitychange', () => {
|
|
737
|
+
if (document.visibilityState === 'hidden') {
|
|
738
|
+
this.flushNow()
|
|
739
|
+
}
|
|
740
|
+
})
|
|
741
|
+
|
|
742
|
+
// 使用 pagehide 事件(更可靠)
|
|
743
|
+
window.addEventListener('pagehide', () => {
|
|
744
|
+
this.flushNow()
|
|
745
|
+
})
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* 手动埋点 - 页面访问
|
|
751
|
+
*/
|
|
752
|
+
public async trackPageView(pageTitle?: string): Promise<void> {
|
|
753
|
+
const currentPage = this.getPageInfoByIndex()
|
|
754
|
+
const currentPath = (currentPage.route as string) || ''
|
|
755
|
+
const currentTitle =
|
|
756
|
+
pageTitle ||
|
|
757
|
+
(currentPage.options?.title as string) ||
|
|
758
|
+
currentPage.config?.navigationBarTitleText ||
|
|
759
|
+
''
|
|
760
|
+
|
|
761
|
+
// 添加当前页到历史记录
|
|
762
|
+
if (currentPath) {
|
|
763
|
+
this.addPageToHistory(currentPath, currentTitle)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// 从历史记录获取上一页信息
|
|
767
|
+
let lastPath = ''
|
|
768
|
+
let lastTitle = ''
|
|
769
|
+
const lastPageHistory = this.getLastPageFromHistory()
|
|
770
|
+
if (lastPageHistory) {
|
|
771
|
+
lastPath = lastPageHistory.route
|
|
772
|
+
lastTitle = lastPageHistory.title
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
this.currentPage = currentPath
|
|
776
|
+
this.pageStartTime = Date.now()
|
|
777
|
+
|
|
778
|
+
const data = await this.createBaseTrackingData(TrackingEventType.PAGE_VIEW, {
|
|
779
|
+
pagePath: currentPath,
|
|
780
|
+
pageTitle: currentTitle,
|
|
781
|
+
lastPagePath: lastPath,
|
|
782
|
+
lastPageTitle: lastTitle,
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
// if (this.config.debug) {
|
|
786
|
+
// console.log('[TrackingSDK] 页面访问埋点:', {
|
|
787
|
+
// current: { path: currentPath, title: currentTitle },
|
|
788
|
+
// last: { path: lastPath, title: lastTitle },
|
|
789
|
+
// historyLength: this.pageHistory.length,
|
|
790
|
+
// })
|
|
791
|
+
// }
|
|
792
|
+
|
|
793
|
+
this.addToQueue(data)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* 手动埋点 - 页面离开
|
|
798
|
+
*/
|
|
799
|
+
public async trackPageLeave(pagePath?: string): Promise<void> {
|
|
800
|
+
const path = pagePath || this.currentPage
|
|
801
|
+
if (!path) return
|
|
802
|
+
|
|
803
|
+
const data = await this.createBaseTrackingData(TrackingEventType.PAGE_LEAVE, {
|
|
804
|
+
pagePath: path,
|
|
805
|
+
})
|
|
806
|
+
|
|
807
|
+
this.addToQueue(data)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* 手动埋点 - 点击事件
|
|
812
|
+
*/
|
|
813
|
+
public async trackClick(elementId?: string, elementText?: string): Promise<void> {
|
|
814
|
+
const data = await this.createBaseTrackingData(TrackingEventType.CLICK, {
|
|
815
|
+
elementId,
|
|
816
|
+
elementText,
|
|
817
|
+
})
|
|
818
|
+
|
|
819
|
+
this.addToQueue(data)
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* 手动埋点 - 自定义事件
|
|
824
|
+
*/
|
|
825
|
+
public async trackCustom(eventName: string, customData?: Record<string, unknown>): Promise<void> {
|
|
826
|
+
const data = await this.createBaseTrackingData(TrackingEventType.CUSTOM, {
|
|
827
|
+
businessData: {
|
|
828
|
+
eventName,
|
|
829
|
+
...customData,
|
|
830
|
+
},
|
|
831
|
+
})
|
|
832
|
+
|
|
833
|
+
this.addToQueue(data)
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* 手动埋点 - 业务事件
|
|
838
|
+
*/
|
|
839
|
+
public async trackBusiness(
|
|
840
|
+
event: TrackingEventType,
|
|
841
|
+
businessData?: Record<string, unknown>
|
|
842
|
+
): Promise<void> {
|
|
843
|
+
const data = await this.createBaseTrackingData(event, {
|
|
844
|
+
businessData,
|
|
845
|
+
})
|
|
846
|
+
|
|
847
|
+
this.addToQueue(data)
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* 手动埋点 - app 和 device 信息
|
|
852
|
+
*/
|
|
853
|
+
public async trackAppDeviceInfo(): Promise<void> {
|
|
854
|
+
const data = await this.createBaseTrackingData(TrackingEventType.APP_DEVICE_INFO, {})
|
|
855
|
+
|
|
856
|
+
this.addToQueue(data)
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/**
|
|
860
|
+
* 获取指定页面信息
|
|
861
|
+
*/
|
|
862
|
+
public getPageInfoByIndex(index = 1): {
|
|
863
|
+
route?: string
|
|
864
|
+
options?: Record<string, unknown>
|
|
865
|
+
config?: { navigationBarTitleText?: string }
|
|
866
|
+
[key: string]: unknown
|
|
867
|
+
} {
|
|
868
|
+
try {
|
|
869
|
+
const pages = getCurrentPages()
|
|
870
|
+
const currentPage = pages[pages.length - index]
|
|
871
|
+
return currentPage || {}
|
|
872
|
+
} catch {
|
|
873
|
+
return {}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* 更新配置
|
|
879
|
+
*/
|
|
880
|
+
public updateConfig(newConfig: Partial<TrackingConfig>): void {
|
|
881
|
+
this.config = {
|
|
882
|
+
enabled: newConfig.enabled ?? this.config.enabled,
|
|
883
|
+
debug: newConfig.debug ?? this.config.debug,
|
|
884
|
+
batchSize: newConfig.batchSize ?? this.config.batchSize,
|
|
885
|
+
flushInterval: newConfig.flushInterval ?? this.config.flushInterval,
|
|
886
|
+
apiEndpoint: newConfig.apiEndpoint ?? this.config.apiEndpoint,
|
|
887
|
+
userInfo: newConfig.userInfo ?? this.userInfo,
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
// 更新用户信息
|
|
891
|
+
if (newConfig.userInfo !== undefined) {
|
|
892
|
+
this.userInfo = newConfig.userInfo
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
if (newConfig.flushInterval) {
|
|
896
|
+
this.startFlushTimer()
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* 立即发送所有待发送的数据(同步方式)
|
|
902
|
+
*/
|
|
903
|
+
public flushNow(): void {
|
|
904
|
+
if (this.eventQueue.length === 0) return
|
|
905
|
+
|
|
906
|
+
const events = [...this.eventQueue]
|
|
907
|
+
this.eventQueue = []
|
|
908
|
+
|
|
909
|
+
try {
|
|
910
|
+
// 使用同步方式发送(适用于页面关闭场景)
|
|
911
|
+
const env = getEnv()
|
|
912
|
+
|
|
913
|
+
if (env === 'WEB' && typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
914
|
+
// H5 环境:使用 sendBeacon API(专为页面卸载场景设计)(sendBeacon无法指定header头, 无法deflate压缩)
|
|
915
|
+
const blob = new Blob([JSON.stringify(events)], {
|
|
916
|
+
type: 'application/json',
|
|
917
|
+
})
|
|
918
|
+
const encryptedToken = this.encryptToken()
|
|
919
|
+
const sent = navigator.sendBeacon(
|
|
920
|
+
`${this.config.apiEndpoint}?contentId=${encryptedToken}`,
|
|
921
|
+
blob
|
|
922
|
+
)
|
|
923
|
+
|
|
924
|
+
if (this.config.debug) {
|
|
925
|
+
console.log(
|
|
926
|
+
'[TrackingSDK] 使用 sendBeacon 发送:',
|
|
927
|
+
sent ? '成功' : '失败',
|
|
928
|
+
events.length,
|
|
929
|
+
'条'
|
|
930
|
+
)
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// 如果 sendBeacon 失败,降级使用普通请求
|
|
934
|
+
if (!sent) {
|
|
935
|
+
this.eventQueue.unshift(...events)
|
|
936
|
+
this.flush().catch(console.error)
|
|
937
|
+
}
|
|
938
|
+
} else {
|
|
939
|
+
// 小程序环境:使用普通异步请求
|
|
940
|
+
this.flush().catch((error) => {
|
|
941
|
+
console.error('[TrackingSDK] 立即发送失败:', error)
|
|
942
|
+
// 发送失败,重新加入队列
|
|
943
|
+
this.eventQueue.unshift(...events)
|
|
944
|
+
})
|
|
945
|
+
}
|
|
946
|
+
} catch (error) {
|
|
947
|
+
console.error('[TrackingSDK] flushNow 执行失败:', error)
|
|
948
|
+
// 发送失败,重新加入队列
|
|
949
|
+
this.eventQueue.unshift(...events)
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* 销毁SDK
|
|
955
|
+
*/
|
|
956
|
+
public destroy(): void {
|
|
957
|
+
if (this.flushTimer) {
|
|
958
|
+
clearInterval(this.flushTimer)
|
|
959
|
+
this.flushTimer = null
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// 立即发送剩余数据
|
|
963
|
+
this.flushNow()
|
|
964
|
+
|
|
965
|
+
this.isInitialized = false
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// 创建全局实例
|
|
970
|
+
export const trackingSDK = new TrackingSDK()
|
|
971
|
+
|
|
972
|
+
// 导出便捷方法(异步版本)
|
|
973
|
+
export const trackPageView = async (pageTitle?: string): Promise<void> =>
|
|
974
|
+
await trackingSDK.trackPageView(pageTitle)
|
|
975
|
+
export const trackClick = async (elementId?: string, elementText?: string): Promise<void> =>
|
|
976
|
+
await trackingSDK.trackClick(elementId, elementText)
|
|
977
|
+
export const trackCustom = async (
|
|
978
|
+
eventName: string,
|
|
979
|
+
customData?: Record<string, unknown>
|
|
980
|
+
): Promise<void> => await trackingSDK.trackCustom(eventName, customData)
|
|
981
|
+
export const trackBusiness = async (
|
|
982
|
+
event: TrackingEventType,
|
|
983
|
+
businessData?: Record<string, unknown>
|
|
984
|
+
): Promise<void> => await trackingSDK.trackBusiness(event, businessData)
|
|
985
|
+
|
|
986
|
+
export default trackingSDK
|