@bi-nova/ui-weapp 1.1.1-beta.202605161224

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,782 @@
1
+ /**
2
+ * @Description 开屏广告组件
3
+ * @Author wutao
4
+ * @Date 2026-04-10
5
+ * @LastEditors wutao
6
+ * @LastEditTime 2026-05-13
7
+ */
8
+ const {
9
+ navigateCustomAd
10
+ } = require('../utils')
11
+
12
+ Component({
13
+ properties: {
14
+ unitId: { type: String, value: '' },
15
+ customAd: { type: Object, value: null },
16
+ show: { type: Boolean, value: true },
17
+ zIndex: { type: Number, value: 999 },
18
+ pictureInPicture: { type: Boolean, value: false },
19
+ checkNativeSplash: { type: Boolean, value: true },
20
+ jumpSeconds: { type: Number, value: 5 }
21
+ },
22
+ data: {
23
+ isWechatAd: false,
24
+ visible: false,
25
+ showPiP: false,
26
+ remainingSeconds: 0,
27
+ mediaUrl: '',
28
+ posterUrl: '',
29
+ isImage: true,
30
+ adSpec: 2,
31
+ hasCustomMedia: false,
32
+ customLoaded: false,
33
+ skipStyle: '',
34
+ adBadgeText: '广告',
35
+ contentStyle: '',
36
+ badgeStyle: '',
37
+ pipAnimating: false,
38
+ nativeSplashActive: false
39
+ },
40
+ lifetimes: {
41
+ attached() {
42
+ /** 系统开屏状态重试次数 */
43
+ this.checkSplashNum = 0
44
+ this.initSkipStyle()
45
+ this.initSafeAreaStyle()
46
+ this.syncAdSource(this.properties.unitId, this.properties.customAd)
47
+ this.syncVisible(this.properties.show)
48
+ },
49
+ detached() {
50
+ this.clearAllTimers()
51
+ }
52
+ },
53
+ observers: {
54
+ 'unitId, customAd'(nextUnitId, nextCustomAd) {
55
+ this.syncAdSource(nextUnitId, nextCustomAd)
56
+ this.syncVisible(this.properties.show)
57
+ },
58
+ show(nextShow) {
59
+ this.syncVisible(nextShow)
60
+ },
61
+ checkNativeSplash() {
62
+ this.restartNativeSplashCheck()
63
+ }
64
+ },
65
+ methods: {
66
+ /**
67
+ * 微信系统开屏检测开关变更后重新同步展示状态
68
+ * @returns {void}
69
+ */
70
+ restartNativeSplashCheck() {
71
+ this.clearAllTimers()
72
+ this.checkSplashNum = 0
73
+ this.setData({
74
+ nativeSplashActive: false
75
+ })
76
+ this.syncVisible(this.properties.show)
77
+ },
78
+
79
+ /**
80
+ * 创建开屏广告事件基础数据
81
+ * @param {'wechat' | 'custom'} provider 广告提供方
82
+ * @returns {{provider: 'wechat' | 'custom', type: 'splash'}}
83
+ */
84
+ createEventDetail(provider) {
85
+ return {
86
+ provider,
87
+ type: 'splash'
88
+ }
89
+ },
90
+
91
+ /**
92
+ * 合并广告事件数据
93
+ * @param {'wechat' | 'custom'} provider 广告提供方
94
+ * @param {Record<string, any>} [event] 原始事件对象
95
+ * @param {Record<string, any>} [extraDetail] 附加事件数据
96
+ * @returns {Record<string, any>}
97
+ */
98
+ resolveEventDetail(provider, event, extraDetail) {
99
+ const baseDetail = this.createEventDetail(provider)
100
+ const detail = event && event.detail && typeof event.detail === 'object' ? event.detail : null
101
+
102
+ if (detail) {
103
+ return {
104
+ ...baseDetail,
105
+ ...detail,
106
+ ...extraDetail
107
+ }
108
+ }
109
+
110
+ if (event && typeof event === 'object') {
111
+ return {
112
+ ...baseDetail,
113
+ ...event,
114
+ ...extraDetail
115
+ }
116
+ }
117
+
118
+ return {
119
+ ...baseDetail,
120
+ ...extraDetail
121
+ }
122
+ },
123
+
124
+ /**
125
+ * 同步广告来源与媒体数据
126
+ * @param {string} unitId 微信广告位 ID
127
+ * @param {Record<string, any> | null} customAd 自定义广告数据
128
+ * @returns {void}
129
+ */
130
+ syncAdSource(unitId, customAd) {
131
+ /** 是否为微信原生广告 */
132
+ const isWechatAd = Boolean(unitId)
133
+ /** 主媒体地址 */
134
+ const mediaUrl = this.getMediaUrl(customAd)
135
+ /** 视频封面地址 */
136
+ const posterUrl = this.getPosterUrl(customAd, mediaUrl)
137
+ /** 是否存在自定义媒体 */
138
+ const hasCustomMedia = Boolean(mediaUrl)
139
+ /** 是否为图片媒体 */
140
+ const isImage = this.getIsImage(customAd, mediaUrl)
141
+ /** 广告规格,1 横版,2 竖版 */
142
+ const adSpec = this.getAdSpec(customAd)
143
+ /** 广告角标文案 */
144
+ const adBadgeText = this.getAdBadgeText(customAd)
145
+
146
+ this.setData({
147
+ isWechatAd,
148
+ mediaUrl,
149
+ posterUrl,
150
+ hasCustomMedia,
151
+ isImage,
152
+ adSpec,
153
+ adBadgeText
154
+ })
155
+ },
156
+
157
+ /**
158
+ * 同步组件显隐状态
159
+ * @param {boolean} nextShow 是否展示
160
+ * @returns {void}
161
+ */
162
+ syncVisible(nextShow) {
163
+ if (!nextShow) {
164
+ this.clearAllTimers()
165
+ this.setData({
166
+ visible: false,
167
+ showPiP: false,
168
+ remainingSeconds: 0,
169
+ customLoaded: false,
170
+ pipAnimating: false,
171
+ nativeSplashActive: false
172
+ })
173
+ return
174
+ }
175
+
176
+ this.checkSplashNum = 0
177
+ this.resetVisibleState()
178
+ this.checkSplashAdStatus()
179
+ },
180
+
181
+ /**
182
+ * 重置组件可见状态
183
+ * @returns {void}
184
+ */
185
+ resetVisibleState() {
186
+ /** 当前倒计时秒数 */
187
+ const remainingSeconds = this.getJumpSeconds()
188
+ /** 当前展示周期是否已由微信系统开屏广告承接 */
189
+ const nativeSplashActive = Boolean(this.properties.checkNativeSplash && this.data.nativeSplashActive)
190
+ /** 是否允许展示组件 */
191
+ const visible = !nativeSplashActive && Boolean(this.properties.unitId || this.data.hasCustomMedia)
192
+
193
+ this.clearAllTimers()
194
+ this.setData({
195
+ visible,
196
+ showPiP: false,
197
+ remainingSeconds,
198
+ customLoaded: false,
199
+ pipAnimating: false
200
+ }, () => {
201
+ this.startWechatCountDownIfNeeded()
202
+ })
203
+
204
+ if (visible && !this.properties.unitId && this.data.hasCustomMedia) {
205
+ this.startCustomLoadTimeout()
206
+ }
207
+ },
208
+
209
+ /**
210
+ * 获取跳过倒计时秒数
211
+ * @returns {number}
212
+ */
213
+ getJumpSeconds() {
214
+ /** 自定义广告跳过秒数 */
215
+ const customSeconds = this.properties.customAd && this.properties.customAd.jumpSeconds
216
+ /** 最终生效跳过秒数 */
217
+ const fallbackSeconds = customSeconds != null ? customSeconds : this.properties.jumpSeconds
218
+ return Math.min(99, Math.max(0, Number(fallbackSeconds) || 0))
219
+ },
220
+
221
+ /**
222
+ * 获取广告媒体地址
223
+ * @param {Record<string, any> | null} customAd 自定义广告数据
224
+ * @returns {string}
225
+ */
226
+ getMediaUrl(customAd) {
227
+ if (!customAd) return ''
228
+ return customAd.image || customAd.video || ''
229
+ },
230
+
231
+ /**
232
+ * 获取广告封面地址
233
+ * @param {Record<string, any> | null} customAd 自定义广告数据
234
+ * @param {string} mediaUrl 主媒体地址
235
+ * @returns {string}
236
+ */
237
+ getPosterUrl(customAd, mediaUrl) {
238
+ if (!customAd) return ''
239
+ return customAd.poster || customAd.image || mediaUrl || ''
240
+ },
241
+
242
+ /**
243
+ * 判断媒体是否为图片
244
+ * @param {Record<string, any> | null} customAd 自定义广告数据
245
+ * @param {string} mediaUrl 主媒体地址
246
+ * @returns {boolean}
247
+ */
248
+ getIsImage(customAd, mediaUrl) {
249
+ if (!customAd) return true
250
+ if (customAd.video) return false
251
+ if (customAd.image) return true
252
+ return /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico)$/i.test(String(mediaUrl || '').trim())
253
+ },
254
+
255
+ /**
256
+ * 获取广告规格
257
+ * @param {Record<string, any> | null} customAd 自定义广告数据
258
+ * @returns {number}
259
+ */
260
+ getAdSpec(customAd) {
261
+ if (!customAd) return 2
262
+
263
+ /** 显式传入的广告规格 */
264
+ const adSpec = Number(customAd.adSpec || customAd.spec || 0)
265
+ if (adSpec === 1 || adSpec === 2) return adSpec
266
+
267
+ /** 媒体宽度 */
268
+ const width = Number(customAd.videoWidth || customAd.width || 0)
269
+ /** 媒体高度 */
270
+ const height = Number(customAd.videoHeight || customAd.height || 0)
271
+
272
+ if (width > 0 && height > 0) {
273
+ return width > height ? 1 : 2
274
+ }
275
+
276
+ return customAd.video ? 1 : 2
277
+ },
278
+
279
+ /**
280
+ * 获取广告角标文案
281
+ * @param {Record<string, any> | null} customAd 自定义广告数据
282
+ * @returns {string}
283
+ */
284
+ getAdBadgeText(customAd) {
285
+ if (!customAd) return '广告'
286
+ return customAd.badgeText || customAd.adLabel || '广告'
287
+ },
288
+
289
+ /**
290
+ * 初始化跳过按钮位置样式
291
+ * @returns {void}
292
+ */
293
+ initSkipStyle() {
294
+ /** @type {{top?: number, right?: number, height?: number, left?: number}} */
295
+ const menuButtonRect =
296
+ typeof wx.getMenuButtonBoundingClientRect === 'function'
297
+ ? wx.getMenuButtonBoundingClientRect()
298
+ : {}
299
+ /** @type {{windowWidth?: number, windowHeight?: number, screenHeight?: number}} */
300
+ const windowInfo =
301
+ typeof wx.getWindowInfo === 'function'
302
+ ? wx.getWindowInfo()
303
+ : typeof wx.getSystemInfoSync === 'function'
304
+ ? wx.getSystemInfoSync()
305
+ : {}
306
+
307
+ /** 是否可按原生菜单胶囊对齐 */
308
+ const isFullScreen = windowInfo.windowHeight === windowInfo.screenHeight
309
+ /** 默认顶部距离 */
310
+ const defaultTop = 16
311
+ /** 默认右侧距离 */
312
+ const defaultRight = 16
313
+ /** 默认左侧距离 */
314
+ const defaultLeft = 16
315
+ /** 默认高度 */
316
+ const defaultHeight = 32
317
+ /** 跳过按钮顶部偏移 */
318
+ const top = isFullScreen && menuButtonRect.top ? menuButtonRect.top : defaultTop
319
+ /** 跳过按钮高度 */
320
+ const height = isFullScreen && menuButtonRect.height ? menuButtonRect.height : defaultHeight
321
+ /** 跳过按钮水平定位样式 */
322
+ const horizontalStyle = isFullScreen ? `left:${defaultLeft}px` : `right:${defaultRight}px`
323
+
324
+ this.setData({
325
+ skipStyle: [
326
+ `top:${top}px`,
327
+ horizontalStyle,
328
+ `height:${height}px`,
329
+ `line-height:${height}px`,
330
+ `border-radius:${Math.round(height / 2)}px`
331
+ ].join(';')
332
+ })
333
+ },
334
+
335
+ /**
336
+ * 初始化安全区相关样式
337
+ * @returns {void}
338
+ */
339
+ initSafeAreaStyle() {
340
+ /** @type {{safeArea?: {bottom?: number}, screenHeight?: number, windowHeight?: number}} */
341
+ const systemInfo =
342
+ typeof wx.getWindowInfo === 'function'
343
+ ? wx.getWindowInfo()
344
+ : typeof wx.getSystemInfoSync === 'function'
345
+ ? wx.getSystemInfoSync()
346
+ : {}
347
+ /** 底部安全区高度 */
348
+ const safeBottom = Math.max(
349
+ 0,
350
+ Number(systemInfo.screenHeight || 0) - Number(systemInfo.safeArea && systemInfo.safeArea.bottom || systemInfo.windowHeight || 0)
351
+ )
352
+ /** 内容区底部间距 */
353
+ const contentBottom = safeBottom > 0 ? safeBottom + 42 : 84
354
+ /** 角标底部间距 */
355
+ const badgeBottom = safeBottom > 0 ? safeBottom + 88 : 130
356
+
357
+ this.setData({
358
+ contentStyle: `bottom:${contentBottom}px;`,
359
+ badgeStyle: `bottom:${badgeBottom}px;`
360
+ })
361
+ },
362
+
363
+ /**
364
+ * 启动广告倒计时
365
+ * @returns {void}
366
+ */
367
+ startCountDown() {
368
+ // 倒计时可能由容器展示兜底、微信广告 load 回调等多个入口触发;已启动、不可见或画中画状态下不重复处理。
369
+ if (this.countDownTimer || !this.data.visible || this.data.showPiP) return
370
+
371
+ if (this.data.remainingSeconds <= 0) {
372
+ this.handleSkip()
373
+ return
374
+ }
375
+
376
+ this.countDownTimer = setInterval(() => {
377
+ /** 下一秒剩余时间 */
378
+ const nextSeconds = this.data.remainingSeconds - 1
379
+
380
+ if (nextSeconds <= 0) {
381
+ this.clearCountDown()
382
+ this.setData({ remainingSeconds: 0 })
383
+ this.handleSkip()
384
+ return
385
+ }
386
+
387
+ this.setData({ remainingSeconds: nextSeconds })
388
+ }, 1000)
389
+ },
390
+
391
+ /**
392
+ * 清理倒计时实例
393
+ * @returns {void}
394
+ */
395
+ clearCountDown() {
396
+ if (this.countDownTimer) {
397
+ clearInterval(this.countDownTimer)
398
+ this.countDownTimer = null
399
+ }
400
+ },
401
+
402
+ /**
403
+ * 获取自定义广告素材加载超时时间
404
+ * @returns {number}
405
+ */
406
+ getCustomLoadTimeout() {
407
+ /** @type {Record<string, any> | null} 自定义广告数据 */
408
+ const customAd = this.properties.customAd
409
+ /** 自定义超时时间,单位毫秒 */
410
+ const timeout = customAd && (customAd.loadTimeout || customAd.timeout)
411
+
412
+ return Math.max(1000, Number(timeout) || 5000)
413
+ },
414
+
415
+ /**
416
+ * 启动自定义广告素材加载超时检测
417
+ * @returns {void}
418
+ */
419
+ startCustomLoadTimeout() {
420
+ this.clearCustomLoadTimeout()
421
+ this.customLoadTimer = setTimeout(() => {
422
+ this.handleCustomLoadTimeout()
423
+ }, this.getCustomLoadTimeout())
424
+ },
425
+
426
+ /**
427
+ * 清理自定义广告素材加载超时定时器
428
+ * @returns {void}
429
+ */
430
+ clearCustomLoadTimeout() {
431
+ if (this.customLoadTimer) {
432
+ clearTimeout(this.customLoadTimer)
433
+ this.customLoadTimer = null
434
+ }
435
+ },
436
+
437
+ /**
438
+ * 清理系统开屏状态重试定时器
439
+ * @returns {void}
440
+ */
441
+ clearSplashStatusRetry() {
442
+ if (this.splashStatusTimer) {
443
+ clearTimeout(this.splashStatusTimer)
444
+ this.splashStatusTimer = null
445
+ }
446
+ },
447
+
448
+ /**
449
+ * 清理画中画动画延迟定时器
450
+ * @returns {void}
451
+ */
452
+ clearPiPTransitionTimer() {
453
+ if (this.pipTransitionTimer) {
454
+ clearTimeout(this.pipTransitionTimer)
455
+ this.pipTransitionTimer = null
456
+ }
457
+ },
458
+
459
+ /**
460
+ * 清理组件内全部定时器
461
+ * @returns {void}
462
+ */
463
+ clearAllTimers() {
464
+ this.clearCountDown()
465
+ this.clearCustomLoadTimeout()
466
+ this.clearSplashStatusRetry()
467
+ this.clearPiPTransitionTimer()
468
+ },
469
+
470
+ /**
471
+ * 微信原生广告确认展示后启动倒计时兜底,避免 ad-custom load 回调偶发未触发时卡在初始秒数
472
+ * @returns {void}
473
+ */
474
+ startWechatCountDownIfNeeded() {
475
+ // 微信原生广告 load 回调在重新编译、缓存恢复等场景下可能不稳定;只要原生广告容器已展示,就允许兜底启动倒计时。
476
+ if (!this.properties.show || !this.properties.unitId || !this.data.visible || this.data.showPiP) return
477
+
478
+ this.startCountDown()
479
+ },
480
+
481
+ /**
482
+ * 设置开屏广告可见状态并按需启动微信原生广告倒计时
483
+ * @param {boolean} visible 是否展示开屏广告
484
+ * @returns {void}
485
+ */
486
+ setVisibleAndStartWechatCountDownIfNeeded(visible) {
487
+ this.setData({
488
+ visible: Boolean(visible)
489
+ }, () => {
490
+ this.startWechatCountDownIfNeeded()
491
+ })
492
+ },
493
+
494
+ /**
495
+ * 检查系统开屏广告状态
496
+ * @returns {void}
497
+ */
498
+ checkSplashAdStatus() {
499
+ if (!this.properties.show) return
500
+
501
+ if (!this.properties.checkNativeSplash) {
502
+ this.setData({
503
+ visible: Boolean(this.properties.unitId || this.data.hasCustomMedia),
504
+ nativeSplashActive: false
505
+ }, () => {
506
+ this.startWechatCountDownIfNeeded()
507
+ })
508
+ return
509
+ }
510
+
511
+ if (this.data.nativeSplashActive) {
512
+ this.setData({ visible: false })
513
+ return
514
+ }
515
+
516
+ if (!wx.getShowSplashAdStatus || this.checkSplashNum >= 3) {
517
+ this.setVisibleAndStartWechatCountDownIfNeeded(Boolean(this.properties.unitId || this.data.hasCustomMedia))
518
+ return
519
+ }
520
+
521
+ wx.getShowSplashAdStatus({
522
+ success: (result) => {
523
+ if (result.code === -1) {
524
+ this.checkSplashNum += 1
525
+ this.clearSplashStatusRetry()
526
+ this.splashStatusTimer = setTimeout(() => {
527
+ this.splashStatusTimer = null
528
+ this.checkSplashAdStatus()
529
+ }, 300)
530
+ return
531
+ }
532
+
533
+ if (result.code === 1) {
534
+ this.clearAllTimers()
535
+ this.setData({
536
+ visible: false,
537
+ nativeSplashActive: true
538
+ })
539
+ this.triggerEvent('load', this.resolveEventDetail('wechat', null, { nativeSplash: true }))
540
+ return
541
+ }
542
+
543
+ this.setVisibleAndStartWechatCountDownIfNeeded(Boolean(this.properties.unitId || this.data.hasCustomMedia))
544
+ },
545
+ fail: () => {
546
+ this.setVisibleAndStartWechatCountDownIfNeeded(Boolean(this.properties.unitId || this.data.hasCustomMedia))
547
+ }
548
+ })
549
+ },
550
+
551
+ /**
552
+ * 处理微信广告加载完成事件
553
+ * @param {Record<string, any>} event 广告加载事件
554
+ * @returns {void}
555
+ */
556
+ handleLoad(event) {
557
+ this.startCountDown()
558
+ this.triggerEvent('load', this.resolveEventDetail('wechat', event))
559
+ },
560
+
561
+ /**
562
+ * 处理广告错误事件
563
+ * @param {Record<string, any>} event 广告错误事件
564
+ * @returns {void}
565
+ */
566
+ handleError(event) {
567
+ const rawDetail = event && event.detail && typeof event.detail === 'object' ? event.detail : {}
568
+ const errorDetail = this.resolveEventDetail('wechat', event, {
569
+ errCode: 'BI_SPLASH_WECHAT_AD_LOAD_FAIL',
570
+ errMsg: rawDetail.errMsg || '微信开屏广告加载失败',
571
+ rawErrCode: rawDetail.errCode,
572
+ rawErrMsg: rawDetail.errMsg
573
+ })
574
+
575
+ this.triggerEvent('error', errorDetail)
576
+ this.fallbackFromWechatAd(errorDetail)
577
+ },
578
+
579
+ /**
580
+ * 微信广告加载失败后的降级处理
581
+ * @param {Record<string, any>} errorDetail 微信广告错误详情
582
+ * @returns {void}
583
+ */
584
+ fallbackFromWechatAd(errorDetail) {
585
+ if (!this.data.isWechatAd) return
586
+
587
+ this.clearAllTimers()
588
+
589
+ if (this.data.hasCustomMedia) {
590
+ this.setData({
591
+ isWechatAd: false,
592
+ visible: true,
593
+ showPiP: false,
594
+ remainingSeconds: this.getJumpSeconds(),
595
+ customLoaded: false,
596
+ pipAnimating: false
597
+ })
598
+ this.startCustomLoadTimeout()
599
+ return
600
+ }
601
+
602
+ this.setData({
603
+ visible: false,
604
+ showPiP: false,
605
+ remainingSeconds: 0,
606
+ customLoaded: false,
607
+ pipAnimating: false
608
+ })
609
+ this.triggerEvent('close', {
610
+ provider: 'wechat',
611
+ type: 'splash',
612
+ reason: 'wechatAdLoadFail',
613
+ error: errorDetail
614
+ })
615
+ },
616
+
617
+ /**
618
+ * 处理自定义广告加载完成事件
619
+ * @returns {void}
620
+ */
621
+ handleCustomLoad() {
622
+ if (this.data.customLoaded) return
623
+
624
+ this.clearCustomLoadTimeout()
625
+ this.setData({ customLoaded: true })
626
+ this.startCountDown()
627
+ this.triggerEvent('load', this.createEventDetail('custom'))
628
+ },
629
+
630
+ /**
631
+ * 处理自定义广告素材加载超时事件
632
+ * @returns {void}
633
+ */
634
+ handleCustomLoadTimeout() {
635
+ if (this.data.customLoaded) return
636
+
637
+ this.handleCustomMediaFail(null, {
638
+ errCode: 'BI_SPLASH_CUSTOM_MEDIA_LOAD_TIMEOUT',
639
+ errMsg: '开屏自定义广告素材加载超时'
640
+ })
641
+ },
642
+
643
+ /**
644
+ * 统一处理自定义广告素材失败事件
645
+ * @param {Record<string, any> | null} event 素材错误事件
646
+ * @param {Record<string, any>} detail 错误详情
647
+ * @returns {void}
648
+ */
649
+ handleCustomMediaFail(event, detail) {
650
+ this.clearAllTimers()
651
+ this.setData({
652
+ visible: false,
653
+ showPiP: false,
654
+ remainingSeconds: 0,
655
+ customLoaded: false,
656
+ pipAnimating: false
657
+ })
658
+ this.triggerEvent('error', this.resolveEventDetail('custom', event, detail))
659
+ },
660
+
661
+ /**
662
+ * 处理自定义广告素材加载失败事件
663
+ * @param {Record<string, any>} event 素材错误事件
664
+ * @returns {void}
665
+ */
666
+ handleCustomMediaError(event) {
667
+ this.handleCustomMediaFail(event, {
668
+ errCode: 'BI_SPLASH_CUSTOM_MEDIA_LOAD_FAIL',
669
+ errMsg: '开屏自定义广告素材加载失败'
670
+ })
671
+ },
672
+
673
+ /**
674
+ * 处理广告点击事件
675
+ * @returns {void}
676
+ */
677
+ handleAction() {
678
+ /** @type {'wechat' | 'custom'} 当前广告来源 */
679
+ const provider = this.data.isWechatAd ? 'wechat' : 'custom'
680
+ /** @type {Record<string, any>} 点击事件载荷 */
681
+ const actionDetail = this.createEventDetail(provider)
682
+
683
+ this.triggerEvent('action', actionDetail)
684
+
685
+ if (provider === 'custom') {
686
+ this.handleCustomNavigation(actionDetail)
687
+ }
688
+ },
689
+
690
+ /**
691
+ * 处理自定义广告跳转逻辑
692
+ * @param {Record<string, any>} actionDetail 点击事件载荷
693
+ * @returns {void}
694
+ */
695
+ handleCustomNavigation(actionDetail) {
696
+ /** @type {Record<string, any> | null} 自定义广告数据 */
697
+ const customAd = this.properties.customAd
698
+
699
+ if (!customAd) return
700
+
701
+ navigateCustomAd({
702
+ customAd: this.properties.customAd,
703
+ actionDetail,
704
+ onOpenWebview: (detail) => {
705
+ this.triggerEvent('openWebview', detail)
706
+ },
707
+ onError: (detail) => {
708
+ this.triggerEvent('error', detail)
709
+ },
710
+ resolveEventDetail: (provider, event, extraDetail) => this.resolveEventDetail(provider, event, extraDetail),
711
+ miniProgramErrorCode: 'BI_SPLASH_NAVIGATE_MINI_PROGRAM_FAIL',
712
+ navigateErrorCode: 'BI_SPLASH_NAVIGATE_FAIL'
713
+ })
714
+ },
715
+
716
+ /**
717
+ * 统一处理广告关闭逻辑
718
+ * @param {Record<string, any>} detail 关闭事件数据
719
+ * @returns {void}
720
+ */
721
+ handleClose(detail) {
722
+ this.clearAllTimers()
723
+ this.setData({
724
+ visible: false,
725
+ showPiP: false,
726
+ remainingSeconds: 0,
727
+ customLoaded: false,
728
+ pipAnimating: false
729
+ })
730
+ this.triggerEvent('close', detail || { type: 'splash' })
731
+ },
732
+
733
+ /**
734
+ * 处理微信广告关闭事件
735
+ * @param {Record<string, any>} event 微信广告关闭事件
736
+ * @returns {void}
737
+ */
738
+ handleWechatClose(event) {
739
+ this.handleClose(this.resolveEventDetail('wechat', event))
740
+ },
741
+
742
+ /**
743
+ * 处理跳过与画中画逻辑
744
+ * @returns {void}
745
+ */
746
+ handleSkip() {
747
+ /** @type {'wechat' | 'custom'} 当前广告来源 */
748
+ const provider = this.data.isWechatAd ? 'wechat' : 'custom'
749
+
750
+ if (this.properties.pictureInPicture) {
751
+ this.clearCountDown()
752
+ this.clearCustomLoadTimeout()
753
+ this.clearPiPTransitionTimer()
754
+ this.setData({ pipAnimating: true })
755
+ this.pipTransitionTimer = setTimeout(() => {
756
+ this.pipTransitionTimer = null
757
+ this.setData({
758
+ showPiP: true,
759
+ pipAnimating: false
760
+ })
761
+ this.triggerEvent('hide', this.createEventDetail(provider))
762
+ }, 280)
763
+ return
764
+ }
765
+
766
+ this.handleClose({ type: 'splash', provider })
767
+ },
768
+
769
+ /**
770
+ * 关闭画中画广告
771
+ * @returns {void}
772
+ */
773
+ handlePiPClose() {
774
+ this.handleClose({
775
+ type: 'splash',
776
+ provider: this.data.isWechatAd ? 'wechat' : 'custom'
777
+ })
778
+ }
779
+ }
780
+ })
781
+
782
+