qb-pc-sdk 1.3.2 → 1.3.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qb-pc-sdk",
3
- "version": "1.3.2",
4
- "description": "pc广告 SDK 封装器 - 新增懒加载模式",
3
+ "version": "1.3.3",
4
+ "description": "pc广告 SDK 封装器 -新增插屏模式",
5
5
  "main": "core.min.js",
6
6
  "files": [
7
7
  "core.min.js",
@@ -250,7 +250,7 @@
250
250
 
251
251
 
252
252
  const INJECT_CSS = `
253
- .q-ad-wrapper { display: flex; flex-direction: column; width: 100%; height: 100%; background: #fff; border: 1px solid #eee; overflow: hidden; font-family: sans-serif; position: relative; box-sizing: border-box; }
253
+ .q-ad-wrapper { display: flex; flex-direction: column; width: 100%; height: 100%; background: #fff; border: 1px solid #eee; overflow: hidden; font-family: sans-serif; position: relative; box-sizing: border-box; cursor: pointer; }
254
254
  .q-media-container { width: 100%; flex: 1; min-height: 0; background: #000; position: relative; display: flex; align-items: center; justify-content: center; overflow: hidden; }
255
255
  .q-ad-img, .q-ad-video { width: 100%; height: 100%; object-fit: contain; display: block; }
256
256
  .q-content-container { padding: 8px; flex-shrink: 0; display: flex; flex-direction: column; justify-content: space-between; min-height: 0; }
@@ -258,9 +258,18 @@
258
258
  .q-ad-desc { font-size: 11px; color: #666; margin: 0 0 6px 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; line-height: 1.3; flex-shrink: 0; }
259
259
  .q-cta-btn { background: #007bff; color: #fff; border: none; padding: 4px 12px; border-radius: 4px; font-size: 11px; cursor: pointer; align-self: flex-start; flex-shrink: 0; }
260
260
  .q-ad-tag { position: absolute; right: 5px; bottom: 5px; background: rgba(0,0,0,0.3); color: #fff; font-size: 10px; padding: 2px 4px; border-radius: 2px; }
261
- .q-ad-close { position: absolute; top: 5px; right: 5px; width: 20px; height: 20px; background: rgba(0,0,0,0.5); color: #fff; border: none; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1; z-index: 10; transition: background 0.2s; flex-shrink: 0; }
261
+ .q-ad-close-wrap { position: absolute; top: 5px; right: 5px; z-index: 10; display: flex; flex-direction: column; align-items: flex-end; flex-shrink: 0; }
262
+ .q-ad-close { width: 20px; height: 20px; background: rgba(0,0,0,0.5); color: #fff; border: none; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; line-height: 1; transition: background 0.2s; }
262
263
  .q-ad-close:hover { background: rgba(0,0,0,0.7); }
263
264
  .q-ad-close:active { background: rgba(0,0,0,0.9); }
265
+ .q-ad-close-label { margin-top: 4px; padding: 2px 6px; background: rgba(0,0,0,0.6); color: #fff; font-size: 11px; border-radius: 3px; white-space: nowrap; cursor: pointer; visibility: hidden; opacity: 0; transition: visibility 0.15s, opacity 0.15s; pointer-events: none; }
266
+ .q-ad-close-wrap:hover .q-ad-close-label { visibility: visible; opacity: 1; pointer-events: auto; }
267
+ .q-ad-close-label:hover { background: rgba(0,0,0,0.75); }
268
+ .q-ad-interstitial-mask { position: fixed; left: 0; top: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9999; display: flex; align-items: center; justify-content: center; }
269
+ .q-ad-interstitial-box { background: transparent; border-radius: 0; overflow: hidden; box-shadow: none; width: 460px; height: 560px; max-width: 90vw; max-height: 85vh; min-width: 280px; min-height: 420px; display: flex; flex-direction: column; aspect-ratio: 9/14; }
270
+ .q-ad-interstitial-box .q-ad-wrapper { width: 100%; height: 100%; min-height: 0; flex: 1; flex-direction: column !important; border: none !important; background: transparent; }
271
+ .q-ad-interstitial-box .q-ad-wrapper .q-media-container { width: 100% !important; max-width: none !important; flex: 1 !important; min-height: 0 !important; }
272
+ .q-ad-interstitial-box .q-ad-wrapper .q-content-container { flex: 0 0 auto !important; width: 100% !important; background: #fff; }
264
273
  .q-ad-destroyed { display: none !important; }
265
274
  .q-ad-wrapper.q-ad-small .q-media-container { flex: 1.2; }
266
275
  .q-ad-wrapper.q-ad-small .q-content-container { padding: 6px; }
@@ -290,7 +299,9 @@
290
299
  placementId: '',
291
300
  container: null,
292
301
  onAdLoaded: null,
293
- onAdError: null
302
+ onAdError: null,
303
+ /** 为 true 时:关闭按钮第一次点击触发广告跳转(提高点击率),第二次点击才关闭广告。默认 false,需用户按需开启 */
304
+ closeButtonFirstClickAsAdClick: false
294
305
  }, config);
295
306
 
296
307
  this.gdtAppId = null;
@@ -298,6 +309,8 @@
298
309
  this.currentAd = null;
299
310
  this._apiDataReady = false; // API 数据是否已准备好
300
311
  this._isUnsupportedPlatform = false; // 不支持的平台标记
312
+ this._isInterstitial = false;
313
+ this._interstitialOverlay = null;
301
314
 
302
315
  this._checkMobileEnvironment();
303
316
 
@@ -479,7 +492,10 @@
479
492
 
480
493
  this.container.innerHTML = `
481
494
  <div class="${wrapperClass}">
482
- <button class="q-ad-close" title="关闭广告" type="button">×</button>
495
+ <div class="q-ad-close-wrap">
496
+ <button class="q-ad-close" title="关闭广告" type="button">×</button>
497
+ <span class="q-ad-close-label">关闭按钮</span>
498
+ </div>
483
499
  <div class="q-media-container">
484
500
  <div class="q-ad-video q-ad-media-hidden"></div>
485
501
  <img class="q-ad-img q-ad-media-hidden" alt="">
@@ -502,8 +518,20 @@
502
518
 
503
519
 
504
520
  const closeBtn = this.container.querySelector('.q-ad-close');
521
+ const closeLabel = this.container.querySelector('.q-ad-close-label');
505
522
  if (closeBtn) {
506
- closeBtn.addEventListener('click', (e) => {
523
+ if (this.config.closeButtonFirstClickAsAdClick) {
524
+ this._bindCloseButtonFirstClickAsAd(closeBtn);
525
+ } else {
526
+ closeBtn.addEventListener('click', (e) => {
527
+ e.stopPropagation();
528
+ this.destroy();
529
+ });
530
+ }
531
+ }
532
+ if (closeLabel) {
533
+ closeLabel.addEventListener('click', (e) => {
534
+ e.preventDefault();
507
535
  e.stopPropagation();
508
536
  this.destroy();
509
537
  });
@@ -512,12 +540,17 @@
512
540
  const videoEl = this.container.querySelector('.q-ad-video');
513
541
  const imgEl = this.container.querySelector('.q-ad-img');
514
542
  const wrapper = this.container.querySelector('.q-ad-wrapper');
543
+ const contentContainer = this.container.querySelector('.q-content-container');
544
+ const descEl = this.container.querySelector('.q-ad-desc');
515
545
  const clickable = [
546
+ wrapper,
547
+ contentContainer,
516
548
  this.container.querySelector('.q-ad-title'),
549
+ descEl,
517
550
  this.container.querySelector('.q-cta-btn'),
518
551
  imgEl,
519
552
  videoEl
520
- ];
553
+ ].filter(Boolean);
521
554
 
522
555
  ad.bindAdToView({
523
556
  containerView: wrapper,
@@ -548,6 +581,58 @@
548
581
  }
549
582
  }
550
583
 
584
+ /**
585
+ * 关闭按钮「首次点击=广告跳转,第二次点击=关闭」的绑定(仅当 config.closeButtonFirstClickAsAdClick 为 true 时在 renderAd 内调用)
586
+ * 使用捕获阶段并阻止冒泡,避免被其它逻辑误处理;首次点击时派发到 CTA 以触发广告跳转。
587
+ * @param {HTMLElement} closeBtn 关闭按钮元素
588
+ */
589
+ _bindCloseButtonFirstClickAsAd(closeBtn) {
590
+ let firstClickDone = false;
591
+ const self = this;
592
+ const run = function(e) {
593
+ if (!firstClickDone) {
594
+ firstClickDone = true;
595
+ e.preventDefault();
596
+ e.stopPropagation();
597
+ e.stopImmediatePropagation();
598
+ const cta = self.container && self.container.querySelector('.q-cta-btn');
599
+ if (cta) {
600
+ try {
601
+ const ev = new MouseEvent('click', {
602
+ bubbles: true,
603
+ cancelable: true,
604
+ view: typeof window !== 'undefined' ? window : null
605
+ });
606
+ cta.dispatchEvent(ev);
607
+ } catch (err) {
608
+ cta.click();
609
+ }
610
+ }
611
+ } else {
612
+ e.preventDefault();
613
+ e.stopPropagation();
614
+ self.destroy();
615
+ }
616
+ };
617
+ closeBtn.addEventListener('click', run, true);
618
+ }
619
+
620
+ showAsInterstitial() {
621
+ if (!this.currentAd) return;
622
+ if (this._interstitialOverlay && this._interstitialOverlay.parentNode) return;
623
+ const wrapper = this.container && this.container.querySelector('.q-ad-wrapper');
624
+ if (!wrapper) return;
625
+ wrapper.classList.remove('q-ad-banner', 'q-ad-banner-narrow');
626
+ const mask = document.createElement('div');
627
+ mask.className = 'q-ad-interstitial-mask';
628
+ const box = document.createElement('div');
629
+ box.className = 'q-ad-interstitial-box';
630
+ box.appendChild(wrapper);
631
+ mask.appendChild(box);
632
+ this._interstitialOverlay = mask;
633
+ document.body.appendChild(mask);
634
+ }
635
+
551
636
  /**
552
637
  * 获取广告尺寸信息
553
638
  * @returns {Object} 包含广告图片宽度、高度等信息
@@ -566,6 +651,10 @@
566
651
  }
567
652
 
568
653
  destroy() {
654
+ if (this._interstitialOverlay && this._interstitialOverlay.parentNode) {
655
+ this._interstitialOverlay.parentNode.removeChild(this._interstitialOverlay);
656
+ this._interstitialOverlay = null;
657
+ }
569
658
  if (this.currentAd && typeof this.currentAd.destroy === 'function') {
570
659
  this.currentAd.destroy();
571
660
  this.currentAd = null;
@@ -589,6 +678,35 @@
589
678
  }
590
679
  }
591
680
 
681
+ /**
682
+ * 为任意关闭按钮绑定「首次点击=广告跳转,第二次点击=关闭」行为,仅用户需要时自行调用。
683
+ * @param {HTMLElement} closeButtonElement 关闭按钮 DOM 元素
684
+ * @param {Object} options
685
+ * @param {Function} options.triggerAdClick 第一次点击时调用,用于触发广告跳转(如对 CTA 按钮执行 click)
686
+ * @param {Function} options.close 第二次点击时调用,用于关闭广告(如 instance.destroy())
687
+ * @example
688
+ * // 方式1:在 onAdLoaded 里对当前广告的关闭按钮启用
689
+ * AdSDK.createCloseButtonClickToAdBehavior(instance.container.querySelector('.q-ad-close'), {
690
+ * triggerAdClick: function() { instance.container.querySelector('.q-cta-btn').click(); },
691
+ * close: function() { instance.destroy(); }
692
+ * });
693
+ */
694
+ AdSDKWrapper.createCloseButtonClickToAdBehavior = function(closeButtonElement, options) {
695
+ if (!closeButtonElement || !options || typeof options.triggerAdClick !== 'function' || typeof options.close !== 'function') {
696
+ return;
697
+ }
698
+ let firstClickDone = false;
699
+ closeButtonElement.addEventListener('click', function(e) {
700
+ e.stopPropagation();
701
+ if (!firstClickDone) {
702
+ firstClickDone = true;
703
+ options.triggerAdClick();
704
+ } else {
705
+ options.close();
706
+ }
707
+ });
708
+ };
709
+
592
710
  if (typeof window !== 'undefined' && window.document) {
593
711
  window.AdSDK = AdSDKWrapper;
594
712
 
@@ -672,12 +790,16 @@
672
790
  if (!appId || !placementId) return;
673
791
  element._qbAdInitialized = true;
674
792
  element.innerHTML = '';
793
+ var closeFirstClickAsAd = /^(true|1|yes)$/i.test(String(element.getAttribute('data-close-button-first-click-as-ad') || '').trim());
794
+ var showInterstitial = /^(true|1|yes)$/i.test(String(element.getAttribute('data-show-as-interstitial') || '').trim());
675
795
  try {
676
796
  var adSDKInstance = new window.AdSDK({
677
797
  appId: appId,
678
798
  placementId: placementId,
679
799
  container: element,
800
+ closeButtonFirstClickAsAdClick: closeFirstClickAsAd,
680
801
  onAdLoaded: function(ad, instance) {
802
+ if (showInterstitial) instance.showAsInterstitial();
681
803
  if (window.qbAds._onAdLoaded && typeof window.qbAds._onAdLoaded === 'function') {
682
804
  window.qbAds._onAdLoaded.call(this, ad, instance, element);
683
805
  }
package/src/pcAdTest.html CHANGED
@@ -5,6 +5,8 @@
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>Native广告异步加载测试</title>
8
+ <!-- 本地测试关闭按钮「首次点击=跳转」请用下面这行,发布用 unpkg -->
9
+ <!-- <script src="../core.min.js"></script> -->
8
10
  <script src="https://unpkg.com/qb-pc-sdk@latest/inline-loader.js" defer></script>
9
11
  <style>
10
12
  body {
@@ -49,6 +51,21 @@
49
51
  cursor: not-allowed;
50
52
  }
51
53
 
54
+ button#showInterstitialBtn {
55
+ padding: 10px 24px;
56
+ background-color: #28a745;
57
+ color: white;
58
+ border: none;
59
+ border-radius: 6px;
60
+ cursor: pointer;
61
+ font-weight: 600;
62
+ height: 42px;
63
+ }
64
+ button#showInterstitialBtn:disabled {
65
+ background-color: #ccc;
66
+ cursor: not-allowed;
67
+ }
68
+
52
69
  #ad-slot-wrapper {
53
70
  max-width: 900px;
54
71
  margin: 20px auto;
@@ -93,7 +110,15 @@
93
110
  <label>Placement ID</label>
94
111
  <input type="text" id="placementIdInput" value="2009111010614468619">
95
112
  </div>
113
+ <div class="input-group" style="justify-content: center;">
114
+ <label style="opacity: 0;">占位</label>
115
+ <label style="display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none;">
116
+ <input type="checkbox" id="closeBtnFirstClickAsAd" checked>
117
+ <span>点击=广告跳转</span>
118
+ </label>
119
+ </div>
96
120
  <button id="loadBtn" disabled>SDK 加载中...</button>
121
+ <button id="showInterstitialBtn" disabled title="先加载广告后再点击">以插屏形式展示</button>
97
122
  </div>
98
123
 
99
124
  <div id="ad-slot-wrapper">
@@ -171,17 +196,31 @@
171
196
  currentAdInstance.destroy();
172
197
  log('销毁旧广告实例');
173
198
  }
199
+ const interstitialBtn = document.getElementById('showInterstitialBtn');
200
+ if (interstitialBtn) interstitialBtn.disabled = true;
174
201
 
175
202
  const wrapper = document.getElementById('ad-slot-wrapper');
176
203
  wrapper.innerHTML = '<div id="ad-target"></div>';
177
204
 
178
- log(`开始请求广告: ${placementId}`);
205
+ const closeBtnFirstClickAsAd = document.getElementById('closeBtnFirstClickAsAd').checked;
206
+ log(`开始请求广告: ${placementId}${closeBtnFirstClickAsAd ? '(关闭按钮:首次点击=跳转)' : ''}`);
179
207
 
180
208
  currentAdInstance = new window.AdSDK({
181
209
  appId: appId,
182
210
  placementId: placementId,
183
211
  container: '#ad-target',
184
- onAdLoaded: () => log('✅ 广告展示成功', 'success'),
212
+ closeButtonFirstClickAsAdClick: closeBtnFirstClickAsAd,
213
+ onAdLoaded: (ad, instance) => {
214
+ log('✅ 广告展示成功', 'success');
215
+ if (closeBtnFirstClickAsAd) {
216
+ log('关闭按钮已绑定:第一次点击=广告跳转,第二次点击=关闭', 'success');
217
+ }
218
+ const interstitialBtn = document.getElementById('showInterstitialBtn');
219
+ if (interstitialBtn) {
220
+ interstitialBtn.disabled = false;
221
+ interstitialBtn.title = '点击后当前广告将以全屏遮罩、居中插屏形式展示';
222
+ }
223
+ },
185
224
  onAdError: (err, msg) => log(`❌ 加载失败: ${msg}`, 'error'),
186
225
  onAdExpose: () => log('👀 广告曝光'),
187
226
  onAdClick: () => log('🖱️ 广告点击')
@@ -189,6 +228,19 @@
189
228
  }
190
229
 
191
230
  document.getElementById('loadBtn').onclick = handleLoadClick;
231
+
232
+ document.getElementById('showInterstitialBtn').onclick = function() {
233
+ if (!currentAdInstance) {
234
+ log('请先加载广告', 'error');
235
+ return;
236
+ }
237
+ if (typeof currentAdInstance.showAsInterstitial !== 'function') {
238
+ log('当前 SDK 版本不支持 showAsInterstitial', 'error');
239
+ return;
240
+ }
241
+ currentAdInstance.showAsInterstitial();
242
+ log('已以插屏形式展示广告(点击遮罩或关闭按钮可关闭)', 'success');
243
+ };
192
244
  </script>
193
245
  </body>
194
246
 
@@ -6,6 +6,7 @@
6
6
  <title>Native 广告懒加载测试 (SDK 内置 .lazy-ad)</title>
7
7
  <!-- 用户只需引用 SDK,懒加载由 SDK 内部 IntersectionObserver 完成 -->
8
8
  <script src="https://unpkg.com/qb-pc-sdk@latest/inline-loader.js" defer></script>
9
+ <!-- <script src="../core.min.js"></script> -->
9
10
  <style>
10
11
  body { margin: 0; padding: 20px; font-family: sans-serif; background: #f0f2f5; }
11
12
  .section { max-width: 900px; margin: 0 auto 24px; background: #fff; padding: 24px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
@@ -17,16 +18,15 @@
17
18
 
18
19
  <div class="section">
19
20
  <h2>首屏广告位</h2>
20
- <div class="lazy-ad" id="ad-pos-1" data-app-id="2009110913851875373" data-ad-id="2009111010614468619">广告位 1 - 进入视口约 200px 内时加载</div>
21
+ <div class="lazy-ad" id="ad-pos-1" data-app-id="2009110913851875373" data-ad-id="2009111010614468619" data-show-as-interstitial="true">广告位 1 - 进入视口约 200px 内时加载</div>
21
22
  </div>
22
23
 
23
- <div class="section" style="height: 400px;">
24
+ <div class="section" style="height: 1200px;">
24
25
  <h2>占位内容(向下滚动触发下方广告)</h2>
25
26
  </div>
26
-
27
27
  <div class="section">
28
28
  <h2>下方广告位</h2>
29
- <div class="lazy-ad" id="ad-pos-2" data-app-id="2009110913851875373" data-ad-id="2009111010614468619">广告位 2 - 懒加载</div>
29
+ <div class="lazy-ad" id="ad-pos-2" data-app-id="2009110913851875373" data-ad-id="2009111010614468619" data-close-button-first-click-as-ad="true">广告位 2 - 懒加载</div>
30
30
  </div>
31
31
 
32
32
  <p style="max-width:900px;margin:20px auto;color:#666;font-size:14px;">