@rk911/tracker-sdk 1.6.4
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/core/compat.js +344 -0
- package/core/config.js +132 -0
- package/core/queue.js +461 -0
- package/core/session.js +128 -0
- package/core/tracker.js +287 -0
- package/customize/index.js +92 -0
- package/customize/modules/common.js +43 -0
- package/customize/modules/store-manager.js +56 -0
- package/index.js +403 -0
- package/package.json +21 -0
- package/platforms/app.js +36 -0
- package/platforms/weapp.js +218 -0
- package/platforms/web.js +483 -0
- package/plugins/api-watch.js +240 -0
- package/plugins/app-lifecycle.js +90 -0
- package/plugins/click.js +557 -0
- package/plugins/exposure.js +376 -0
- package/plugins/js-error.js +232 -0
- package/plugins/page-stay.js +44 -0
- package/plugins/page-view.js +172 -0
- package/plugins/performance.js +157 -0
- package/plugins/scroll-depth.js +256 -0
package/platforms/web.js
ADDED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
export class WebAdapter {
|
|
2
|
+
constructor(config) {
|
|
3
|
+
this.config = config
|
|
4
|
+
this._listeners = []
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
getPlatformName() {
|
|
8
|
+
return 'web'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// [PROJECT-ADAPT] 存储实现
|
|
12
|
+
// 当前依赖 uni.getStorageSync(uni-app 运行时)
|
|
13
|
+
// 非 uni-app 项目需替换为 localStorage.getItem
|
|
14
|
+
|
|
15
|
+
getStorage(key) {
|
|
16
|
+
try {
|
|
17
|
+
return uni.getStorageSync(key)
|
|
18
|
+
} catch (e) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setStorage(key, value) {
|
|
24
|
+
try {
|
|
25
|
+
uni.setStorageSync(key, value)
|
|
26
|
+
} catch (e) {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
removeStorage(key) {
|
|
30
|
+
try {
|
|
31
|
+
uni.removeStorageSync(key)
|
|
32
|
+
} catch (e) {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
getSessionStorage(key) {
|
|
36
|
+
try {
|
|
37
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
38
|
+
const val = sessionStorage.getItem(key)
|
|
39
|
+
return val ? JSON.parse(val) : null
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
setSessionStorage(key, value) {
|
|
46
|
+
try {
|
|
47
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
48
|
+
sessionStorage.setItem(key, JSON.stringify(value))
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
removeSessionStorage(key) {
|
|
54
|
+
try {
|
|
55
|
+
if (typeof sessionStorage !== 'undefined') {
|
|
56
|
+
sessionStorage.removeItem(key)
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---- 多源路由检测(交叉验证,投票取准)----
|
|
62
|
+
|
|
63
|
+
getCurrentPagePath() {
|
|
64
|
+
var sources = []
|
|
65
|
+
|
|
66
|
+
// 来源 1: window.location.hash(浏览器地址栏,导航时同步更新,最可靠)
|
|
67
|
+
var hashRoute = this._getRouteFromHash()
|
|
68
|
+
if (hashRoute) sources.push(hashRoute)
|
|
69
|
+
|
|
70
|
+
// 来源 2: getCurrentPages() 栈顶(uni-app 内部,导航过程中可能延迟更新)
|
|
71
|
+
var stackRoute = this._getRouteFromPageStack()
|
|
72
|
+
if (stackRoute) sources.push(stackRoute)
|
|
73
|
+
|
|
74
|
+
// 来源 3: history.state(部分 uni-app 版本会写入路由信息)
|
|
75
|
+
var stateRoute = this._getRouteFromHistoryState()
|
|
76
|
+
if (stateRoute) sources.push(stateRoute)
|
|
77
|
+
|
|
78
|
+
if (sources.length === 0) return '/'
|
|
79
|
+
|
|
80
|
+
// 投票:多数一致取多数,全不一致取地址栏(地址栏是用户可见的 ground truth)
|
|
81
|
+
if (sources.length === 1) return sources[0]
|
|
82
|
+
|
|
83
|
+
var voteMap = {}
|
|
84
|
+
for (var i = 0; i < sources.length; i++) {
|
|
85
|
+
voteMap[sources[i]] = (voteMap[sources[i]] || 0) + 1
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 找出票数最多的
|
|
89
|
+
var maxVotes = 0
|
|
90
|
+
var winner = sources[0]
|
|
91
|
+
for (var route in voteMap) {
|
|
92
|
+
if (voteMap[route] > maxVotes) {
|
|
93
|
+
maxVotes = voteMap[route]
|
|
94
|
+
winner = route
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 地址栏是用户可见的 ground truth:导航过程中 pageStack/historyState 可能滞后,
|
|
99
|
+
// 只要 hash 有值且与投票结果不一致,以 hash 为准
|
|
100
|
+
if (hashRoute && winner !== hashRoute) {
|
|
101
|
+
return hashRoute
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return winner
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_getRouteFromHash() {
|
|
108
|
+
try {
|
|
109
|
+
if (typeof window === 'undefined') return null
|
|
110
|
+
var hash = window.location.hash || ''
|
|
111
|
+
if (!hash || hash === '#' || hash === '#/') return null
|
|
112
|
+
// uni-app H5 hash 格式: #/pages/index/index?query=xxx
|
|
113
|
+
var path = hash.replace(/^#/, '').split('?')[0]
|
|
114
|
+
if (path && path !== '/') return path
|
|
115
|
+
} catch (e) {}
|
|
116
|
+
return null
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_getRouteFromPageStack() {
|
|
120
|
+
try {
|
|
121
|
+
var pages = getCurrentPages()
|
|
122
|
+
if (pages.length > 0) {
|
|
123
|
+
var route = pages[pages.length - 1].route || ''
|
|
124
|
+
if (route) {
|
|
125
|
+
return route.charAt(0) === '/' ? route : '/' + route
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {}
|
|
129
|
+
return null
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
_getRouteFromHistoryState() {
|
|
133
|
+
try {
|
|
134
|
+
if (typeof window === 'undefined' || !window.history || !window.history.state) return null
|
|
135
|
+
var state = window.history.state
|
|
136
|
+
// uni-app 某些版本的 history.state 结构
|
|
137
|
+
if (state && state.current) {
|
|
138
|
+
var path = state.current.split('?')[0]
|
|
139
|
+
if (path && path !== '/') {
|
|
140
|
+
return path.charAt(0) === '/' ? path : '/' + path
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} catch (e) {}
|
|
144
|
+
return null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getCurrentPageRoute() {
|
|
148
|
+
var path = this.getCurrentPagePath()
|
|
149
|
+
return path === '/' ? '' : path.replace(/^\//, '')
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
getPageTitle() {
|
|
153
|
+
try {
|
|
154
|
+
if (typeof document !== 'undefined' && document.title) {
|
|
155
|
+
return document.title
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {}
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getToken() {
|
|
162
|
+
return this.getStorage(this.config.tokenKey || 'token')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// [PROJECT-ADAPT] 网络请求实现
|
|
166
|
+
// 当前依赖 uni.request(uni-app 运行时)
|
|
167
|
+
// 非 uni-app 项目需替换为 fetch/axios
|
|
168
|
+
|
|
169
|
+
request(options) {
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
uni.request({
|
|
172
|
+
...options,
|
|
173
|
+
success: (res) => resolve(res),
|
|
174
|
+
fail: (err) => reject(err),
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sendBeacon(url, data) {
|
|
180
|
+
try {
|
|
181
|
+
if (typeof navigator !== 'undefined' && navigator.sendBeacon) {
|
|
182
|
+
return navigator.sendBeacon(
|
|
183
|
+
url,
|
|
184
|
+
typeof data === 'string' ? data : JSON.stringify(data)
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
} catch (e) {}
|
|
188
|
+
// 降级为同步请求
|
|
189
|
+
try {
|
|
190
|
+
uni.request({
|
|
191
|
+
method: 'POST',
|
|
192
|
+
url: url,
|
|
193
|
+
data: data,
|
|
194
|
+
header: { 'content-type': 'application/json' },
|
|
195
|
+
})
|
|
196
|
+
} catch (e) {}
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- 网络状态 ----
|
|
201
|
+
|
|
202
|
+
getNetworkType() {
|
|
203
|
+
try {
|
|
204
|
+
if (typeof navigator !== 'undefined' && navigator.connection) {
|
|
205
|
+
return navigator.connection.effectiveType || 'unknown'
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {}
|
|
208
|
+
return typeof navigator !== 'undefined' && navigator.onLine ? 'wifi' : 'offline'
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ---- 设备信息 ----
|
|
212
|
+
|
|
213
|
+
getDeviceInfo() {
|
|
214
|
+
try {
|
|
215
|
+
const info = uni.getSystemInfoSync()
|
|
216
|
+
var ua = (typeof navigator !== 'undefined' ? navigator.userAgent : '').toLowerCase()
|
|
217
|
+
return {
|
|
218
|
+
brand: info.brand || this._judgeBrand(ua, info.model),
|
|
219
|
+
model: info.model || '',
|
|
220
|
+
platform: info.platform || '',
|
|
221
|
+
system: info.system || '',
|
|
222
|
+
screenWidth: info.screenWidth || 0,
|
|
223
|
+
screenHeight: info.screenHeight || 0,
|
|
224
|
+
userAgent:
|
|
225
|
+
typeof navigator !== 'undefined' ? navigator.userAgent : '',
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
return {}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ---- 生命周期事件 ----
|
|
233
|
+
|
|
234
|
+
onPageShow(callback) {
|
|
235
|
+
if (typeof window !== 'undefined') {
|
|
236
|
+
const handler = () => callback()
|
|
237
|
+
window.addEventListener('pageshow', handler)
|
|
238
|
+
this._listeners.push({ target: window, event: 'pageshow', handler })
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
onPageHide(callback) {
|
|
243
|
+
if (typeof document !== 'undefined') {
|
|
244
|
+
const handler = () => {
|
|
245
|
+
if (document.visibilityState === 'hidden') {
|
|
246
|
+
callback()
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
document.addEventListener('visibilitychange', handler)
|
|
250
|
+
this._listeners.push({
|
|
251
|
+
target: document,
|
|
252
|
+
event: 'visibilitychange',
|
|
253
|
+
handler,
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
onPageClose(callback) {
|
|
259
|
+
if (typeof window !== 'undefined') {
|
|
260
|
+
const handler = () => callback()
|
|
261
|
+
window.addEventListener('beforeunload', handler)
|
|
262
|
+
this._listeners.push({ target: window, event: 'beforeunload', handler })
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// ---- DOM 能力 ----
|
|
267
|
+
|
|
268
|
+
supportsDOM() {
|
|
269
|
+
return typeof document !== 'undefined'
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
supportsIntersectionObserver() {
|
|
273
|
+
return typeof IntersectionObserver !== 'undefined'
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
addClickListener(handler) {
|
|
277
|
+
if (typeof document !== 'undefined') {
|
|
278
|
+
document.addEventListener('click', handler, true)
|
|
279
|
+
this._listeners.push({ target: document, event: 'click', handler, capture: true })
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
removeClickListener(handler) {
|
|
284
|
+
if (typeof document !== 'undefined') {
|
|
285
|
+
document.removeEventListener('click', handler, true)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
addErrorListener(handler) {
|
|
290
|
+
if (typeof window !== 'undefined') {
|
|
291
|
+
window.addEventListener('error', handler, true)
|
|
292
|
+
this._listeners.push({ target: window, event: 'error', handler, capture: true })
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
addUnhandledRejectionListener(handler) {
|
|
297
|
+
if (typeof window !== 'undefined') {
|
|
298
|
+
window.addEventListener('unhandledrejection', handler)
|
|
299
|
+
this._listeners.push({
|
|
300
|
+
target: window,
|
|
301
|
+
event: 'unhandledrejection',
|
|
302
|
+
handler,
|
|
303
|
+
})
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ---- 路由监听 ----
|
|
308
|
+
|
|
309
|
+
onRouteChange(callback) {
|
|
310
|
+
if (typeof window === 'undefined' || typeof history === 'undefined') return
|
|
311
|
+
|
|
312
|
+
const originalPushState = history.pushState
|
|
313
|
+
const originalReplaceState = history.replaceState
|
|
314
|
+
|
|
315
|
+
history.pushState = function (...args) {
|
|
316
|
+
originalPushState.apply(this, args)
|
|
317
|
+
callback({ type: 'pushState' })
|
|
318
|
+
}
|
|
319
|
+
history.replaceState = function (...args) {
|
|
320
|
+
originalReplaceState.apply(this, args)
|
|
321
|
+
callback({ type: 'replaceState' })
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const popstateHandler = () => callback({ type: 'popstate' })
|
|
325
|
+
window.addEventListener('popstate', popstateHandler)
|
|
326
|
+
this._listeners.push({
|
|
327
|
+
target: window,
|
|
328
|
+
event: 'popstate',
|
|
329
|
+
handler: popstateHandler,
|
|
330
|
+
})
|
|
331
|
+
|
|
332
|
+
// uni-app H5 使用 hash 路由,navigateTo/reLaunch 可能只触发 hashchange
|
|
333
|
+
const hashchangeHandler = () => callback({ type: 'hashchange' })
|
|
334
|
+
window.addEventListener('hashchange', hashchangeHandler)
|
|
335
|
+
this._listeners.push({
|
|
336
|
+
target: window,
|
|
337
|
+
event: 'hashchange',
|
|
338
|
+
handler: hashchangeHandler,
|
|
339
|
+
})
|
|
340
|
+
|
|
341
|
+
this._originalPushState = originalPushState
|
|
342
|
+
this._originalReplaceState = originalReplaceState
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ---- 设备注册信息 ----
|
|
346
|
+
|
|
347
|
+
getDetailedDeviceInfo() {
|
|
348
|
+
var info = {}
|
|
349
|
+
try {
|
|
350
|
+
info = uni.getSystemInfoSync() || {}
|
|
351
|
+
} catch (e) {}
|
|
352
|
+
|
|
353
|
+
var ua = (typeof navigator !== 'undefined' ? navigator.userAgent : '').toLowerCase()
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
appVersion: '1.0',
|
|
357
|
+
browserType: info.browserName || '',
|
|
358
|
+
browserVersion: info.browserVersion || '',
|
|
359
|
+
deviceBrand: this._judgeBrand(ua, info.deviceModel || info.model),
|
|
360
|
+
deviceCode: info.deviceId || '',
|
|
361
|
+
deviceModel: info.deviceModel || '',
|
|
362
|
+
deviceType: this._getDeviceType(ua),
|
|
363
|
+
deviceVersion: info.osVersion || '',
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// [PROJECT-ADAPT] 设备品牌/类型识别
|
|
368
|
+
// 当前偏向中国市场手机品牌,国际化项目需扩展
|
|
369
|
+
// 返回的 deviceType 数值需与后台枚举对齐
|
|
370
|
+
_judgeBrand(ua, model) {
|
|
371
|
+
if (ua.indexOf('iphone') > -1) return 'iPhone'
|
|
372
|
+
if (ua.indexOf('huawei') > -1 || ua.indexOf('honor') > -1) return 'Huawei'
|
|
373
|
+
if (ua.indexOf('oppo') > -1 || ua.indexOf('pacm00') > -1) return 'Oppo'
|
|
374
|
+
if (ua.indexOf('vivo') > -1) return 'vivo'
|
|
375
|
+
// 精确匹配小米,避免裸子串 'mi' 命中微信/企业微信 UA 里的 "MicroMessenger"
|
|
376
|
+
// (曾把微信内所有非小米安卓机误判成小米)。/\bmi[\s-]?\d/ 匹配 "Mi 11"/"MI9",不匹配 "micromessenger"
|
|
377
|
+
if (ua.indexOf('xiaomi') > -1 || ua.indexOf('redmi') > -1 ||
|
|
378
|
+
ua.indexOf('miui') > -1 || /\bmi[\s-]?\d/.test(ua)) return 'mi'
|
|
379
|
+
if (ua.indexOf('sm-') > -1) return 'Samsung'
|
|
380
|
+
if (ua.indexOf('lg-') > -1 || ua.indexOf('lm-') > -1) return 'LG'
|
|
381
|
+
|
|
382
|
+
// UA 未命中时按设备型号前缀兜底识别(微信 webview 常拿不到 UA 品牌,但 uni 能给型号)
|
|
383
|
+
var m = (model || '').toString().toUpperCase()
|
|
384
|
+
if (m) {
|
|
385
|
+
if (/^V\d{3,}/.test(m)) return 'vivo' // vivo: V2283A / V2055A ...
|
|
386
|
+
if (/^(PA|PB|PC|PD|PE|CPH)/.test(m)) return 'Oppo' // oppo/一加型号前缀
|
|
387
|
+
if (/^(MI |MI-|M\d{4}|2\d{6})/.test(m) || m.indexOf('REDMI') > -1) return 'mi'
|
|
388
|
+
if (/^(ELS|NOH|LIO|ANA|TAS|VOG|BRQ)/.test(m)) return 'Huawei'
|
|
389
|
+
if (m.indexOf('SM-') === 0) return 'Samsung'
|
|
390
|
+
}
|
|
391
|
+
return '其他型号手机'
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
_getDeviceType(ua) {
|
|
395
|
+
if (ua.indexOf('ipad') > -1 && ua.indexOf('mac') > -1) return 3
|
|
396
|
+
if (ua.indexOf('iphone') > -1 && ua.indexOf('mac os') > -1) return 6
|
|
397
|
+
if (ua.indexOf('iphone') > -1) return 2
|
|
398
|
+
if (ua.indexOf('android') > -1 && ua.indexOf('ipad') > -1) return 4
|
|
399
|
+
if (ua.indexOf('android') > -1) return 1
|
|
400
|
+
if (ua.indexOf('linux') > -1) return 7
|
|
401
|
+
if (ua.indexOf('windows') > -1) return 5
|
|
402
|
+
return 1
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ---- 工具 ----
|
|
406
|
+
|
|
407
|
+
generateId(prefix) {
|
|
408
|
+
return (
|
|
409
|
+
(prefix || '') +
|
|
410
|
+
'_' +
|
|
411
|
+
Date.now() +
|
|
412
|
+
'_' +
|
|
413
|
+
Math.random().toString(36).substr(2, 9)
|
|
414
|
+
)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
getTimestamp() {
|
|
418
|
+
return Date.now()
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
getXPath(el) {
|
|
422
|
+
if (!el || !el.tagName) return ''
|
|
423
|
+
const parts = []
|
|
424
|
+
let current = el
|
|
425
|
+
while (current && current.nodeType === 1 && current !== document.body) {
|
|
426
|
+
let index = 1
|
|
427
|
+
let sibling = current.previousElementSibling
|
|
428
|
+
while (sibling) {
|
|
429
|
+
if (sibling.tagName === current.tagName) index++
|
|
430
|
+
sibling = sibling.previousElementSibling
|
|
431
|
+
}
|
|
432
|
+
parts.unshift(current.tagName.toLowerCase() + '[' + index + ']')
|
|
433
|
+
current = current.parentElement
|
|
434
|
+
}
|
|
435
|
+
return '/' + parts.join('/')
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// [PROJECT-ADAPT] 同 click.js 的 __vue__ 检测依赖
|
|
439
|
+
findTrackableElement(el) {
|
|
440
|
+
const trackable = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG']
|
|
441
|
+
let current = el
|
|
442
|
+
let depth = 0
|
|
443
|
+
let firstClickable = null
|
|
444
|
+
|
|
445
|
+
while (current && current !== document.body && depth < 10) {
|
|
446
|
+
// 优先:有 data-track-id 标记的
|
|
447
|
+
if (current.dataset && current.dataset.trackId) return current
|
|
448
|
+
// 标准可交互元素
|
|
449
|
+
if (trackable.indexOf(current.tagName) >= 0) return current
|
|
450
|
+
// uni-app H5: Vue 实例上有 click 事件监听
|
|
451
|
+
if (!firstClickable && current.__vue__) {
|
|
452
|
+
var vm = current.__vue__
|
|
453
|
+
if ((vm.$listeners && vm.$listeners.click) || (vm._events && vm._events.click)) {
|
|
454
|
+
firstClickable = current
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// 有 onclick 属性
|
|
458
|
+
if (!firstClickable && current.onclick) {
|
|
459
|
+
firstClickable = current
|
|
460
|
+
}
|
|
461
|
+
current = current.parentElement
|
|
462
|
+
depth++
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return firstClickable || el
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ---- 清理 ----
|
|
469
|
+
|
|
470
|
+
destroy() {
|
|
471
|
+
this._listeners.forEach(({ target, event, handler, capture }) => {
|
|
472
|
+
target.removeEventListener(event, handler, !!capture)
|
|
473
|
+
})
|
|
474
|
+
this._listeners = []
|
|
475
|
+
|
|
476
|
+
if (this._originalPushState) {
|
|
477
|
+
history.pushState = this._originalPushState
|
|
478
|
+
}
|
|
479
|
+
if (this._originalReplaceState) {
|
|
480
|
+
history.replaceState = this._originalReplaceState
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|