qb-pc-sdk 1.0.8 → 1.1.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.
package/README.md CHANGED
@@ -139,8 +139,33 @@ new AdSDK(config)
139
139
  - `onAdExpose` (function, 可选): 广告曝光回调
140
140
  - `onAdClick` (function, 可选): 广告点击回调
141
141
 
142
+ ### 实例方法
143
+
144
+ #### destroy()
145
+
146
+ 销毁广告实例,清理 DOM、解绑事件、释放资源。
147
+
148
+ ```javascript
149
+ const adSDK = new AdSDK({
150
+ appId: 'your-app-id',
151
+ placementId: 'your-placement-id',
152
+ container: '#ad-container'
153
+ });
154
+
155
+ // 销毁广告
156
+ adSDK.destroy();
157
+ ```
158
+
159
+ **说明**:
160
+ - 调用后会清空广告容器内容
161
+ - 会调用底层 SDK 的 `destroy()` 方法释放资源
162
+ - 广告会自动显示关闭按钮(右上角 ×),点击即可关闭
163
+ - 也可以手动调用 `destroy()` 方法销毁广告
164
+
142
165
  ### 示例
143
166
 
167
+ #### 基本使用
168
+
144
169
  ```javascript
145
170
  const AdSDK = require('qb-pc-sdk');
146
171
 
@@ -163,6 +188,41 @@ const adSDK = new AdSDK({
163
188
  });
164
189
  ```
165
190
 
191
+ #### 销毁广告示例
192
+
193
+ ```javascript
194
+ const adSDK = new AdSDK({
195
+ appId: 'your-app-id',
196
+ placementId: 'your-placement-id',
197
+ container: '#ad-container'
198
+ });
199
+
200
+ // 方式1:点击广告右上角的关闭按钮(自动销毁)
201
+ // 广告渲染时会自动显示关闭按钮
202
+
203
+ // 方式2:手动调用 destroy 方法
204
+ document.getElementById('close-ad-btn').addEventListener('click', () => {
205
+ adSDK.destroy();
206
+ });
207
+
208
+ // 方式3:在组件销毁时调用(Vue/React)
209
+ // Vue
210
+ onBeforeUnmount(() => {
211
+ if (adSDK) {
212
+ adSDK.destroy();
213
+ }
214
+ });
215
+
216
+ // React
217
+ useEffect(() => {
218
+ return () => {
219
+ if (adSDK) {
220
+ adSDK.destroy();
221
+ }
222
+ };
223
+ }, []);
224
+ ```
225
+
166
226
  ## 依赖
167
227
 
168
228
  - `qb-pc-ad-sdk-origin`: 底层广告 SDK(已自动在浏览器环境加载)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "qb-pc-sdk",
3
- "version": "1.0.8",
4
- "description": "趣变广告 SDK 封装器 - 隐藏自动请求微信sdk",
3
+ "version": "1.1.0",
4
+ "description": "趣变广告 SDK 封装器 - 添加关闭按钮增加销毁广告选项",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
@@ -285,8 +285,8 @@
285
285
 
286
286
  // 内部常量配置
287
287
  const API_CONFIG = {
288
- INIT: 'http://test.qubiankeji.com:8084/pc/init',
289
- POSITION: 'http://test.qubiankeji.com:8084/pc/position',
288
+ INIT: 'http://app.qubiankeji.com:8084/pc/init',
289
+ POSITION: 'http://app.qubiankeji.com:8084/pc/position',
290
290
  GDT_SDK_ID: 2 // 标识
291
291
  };
292
292
 
@@ -300,6 +300,10 @@
300
300
  .q-ad-desc { font-size: 12px; color: #666; margin: 0 0 10px 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
301
301
  .q-cta-btn { background: #007bff; color: #fff; border: none; padding: 6px 15px; border-radius: 4px; font-size: 12px; cursor: pointer; align-self: flex-start; }
302
302
  .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; }
303
+ .q-ad-close { position: absolute; top: 5px; right: 5px; width: 24px; height: 24px; 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: 16px; line-height: 1; z-index: 10; transition: background 0.2s; }
304
+ .q-ad-close:hover { background: rgba(0,0,0,0.7); }
305
+ .q-ad-close:active { background: rgba(0,0,0,0.9); }
306
+ .q-ad-destroyed { display: none !important; }
303
307
  `;
304
308
 
305
309
  class AdSDKWrapper {
@@ -316,6 +320,9 @@
316
320
  this.gdtPlacementId = null;
317
321
  this.currentAd = null;
318
322
 
323
+ // 检测移动端环境并提示
324
+ this._checkMobileEnvironment();
325
+
319
326
  this._injectStyles();
320
327
 
321
328
  this.container = typeof this.config.container === 'string'
@@ -329,6 +336,27 @@
329
336
  }
330
337
  }
331
338
 
339
+ // 检测移动端环境
340
+ _checkMobileEnvironment() {
341
+ if (typeof window !== 'undefined' && window.navigator) {
342
+ const userAgent = window.navigator.userAgent || '';
343
+ const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
344
+
345
+ if (isMobile) {
346
+ console.warn('[AdSDK] ⚠️ 检测到移动端环境,这是 PC 端 SDK,可能在移动端无法正常工作。建议使用移动端专用 SDK。');
347
+ if (this.config.onAdError) {
348
+ // 不立即触发错误,让用户决定是否继续
349
+ setTimeout(() => {
350
+ this.config.onAdError(
351
+ new Error('移动端环境警告'),
352
+ '这是 PC 端 SDK,移动端可能无法正常加载广告'
353
+ );
354
+ }, 100);
355
+ }
356
+ }
357
+ }
358
+ }
359
+
332
360
  // 注入默认 CSS
333
361
  _injectStyles() {
334
362
  if (!document.getElementById('q-ad-styles')) {
@@ -347,14 +375,33 @@
347
375
  fetch(`${API_CONFIG.POSITION}?positionId=${this.config.placementId}`).then(r => r.json())
348
376
  ]);
349
377
 
378
+ // 处理返回数据结构:支持 { data: {...} } 或直接返回数据
379
+ const initData = initRes.data || initRes;
380
+ const posData = posRes.data || posRes;
381
+
350
382
  // 2. 提取 AppID
351
- this.gdtAppId = initRes.thirdIdMap ? initRes.thirdIdMap[String(API_CONFIG.GDT_SDK_ID)] : null;
383
+ this.gdtAppId = initData.thirdIdMap ? initData.thirdIdMap[String(API_CONFIG.GDT_SDK_ID)] : null;
352
384
 
353
385
  // 3. 执行广告位筛选逻辑
354
- this.gdtPlacementId = this._selectPlacementId(posRes);
386
+ this.gdtPlacementId = this._selectPlacementId(posData);
387
+
388
+ // 添加调试日志(始终输出,便于排查问题)
389
+ console.log('[AdSDK] 初始化数据:', {
390
+ initRes: initRes,
391
+ posRes: posRes,
392
+ initData: initData,
393
+ posData: posData,
394
+ gdtAppId: this.gdtAppId,
395
+ gdtPlacementId: this.gdtPlacementId
396
+ });
355
397
 
356
398
  if (!this.gdtAppId || !this.gdtPlacementId) {
357
- throw new Error('未找到匹配的(sdkId:2)配置信息');
399
+ const errorMsg = `未找到匹配的(sdkId:2)配置信息 - AppId: ${this.gdtAppId}, PlacementId: ${this.gdtPlacementId}`;
400
+ console.error('[AdSDK]', errorMsg, {
401
+ initData: initData,
402
+ posData: posData
403
+ });
404
+ throw new Error(errorMsg);
358
405
  }
359
406
 
360
407
  this._checkAndRun();
@@ -368,21 +415,62 @@
368
415
  * 规则:headSetList优先;否则选positionSetList中优先级最高的
369
416
  */
370
417
  _selectPlacementId(data) {
418
+ if (!data) {
419
+ console.error('[AdSDK] _selectPlacementId: data 为空');
420
+ return null;
421
+ }
422
+
423
+ // 添加调试信息
424
+ console.log('[AdSDK] _selectPlacementId 输入数据:', data);
425
+ const targetSdkId = API_CONFIG.GDT_SDK_ID;
426
+ console.log('[AdSDK] 查找 sdkId (类型:', typeof targetSdkId, '值:', targetSdkId, ')');
427
+
371
428
  // A. 优先检查 headSetList
372
- if (data.headSetList && data.headSetList.length > 0) {
373
- const headAd = data.headSetList.find(item => item.sdkId === API_CONFIG.GDT_SDK_ID);
374
- if (headAd) return headAd.positionId;
429
+ if (data.headSetList && Array.isArray(data.headSetList) && data.headSetList.length > 0) {
430
+ console.log('[AdSDK] 检查 headSetList:', data.headSetList);
431
+ const headAd = data.headSetList.find(item => {
432
+ // 支持数字和字符串类型的比较
433
+ const itemSdkId = Number(item.sdkId);
434
+ const match = itemSdkId === targetSdkId || String(item.sdkId) === String(targetSdkId);
435
+ if (!match) {
436
+ console.log('[AdSDK] headSetList 项不匹配:', item, 'sdkId:', item.sdkId, '类型:', typeof item.sdkId);
437
+ }
438
+ return match;
439
+ });
440
+ if (headAd) {
441
+ console.log('[AdSDK] ✅ 在 headSetList 中找到匹配项:', headAd);
442
+ return headAd.positionId;
443
+ }
375
444
  }
376
445
 
377
446
  // B. 检查 positionSetList
378
- if (data.positionSetList && data.positionSetList.length > 0) {
447
+ if (data.positionSetList && Array.isArray(data.positionSetList) && data.positionSetList.length > 0) {
448
+ console.log('[AdSDK] 检查 positionSetList:', data.positionSetList);
379
449
  const sortedList = data.positionSetList
380
- .filter(item => item.sdkId === API_CONFIG.GDT_SDK_ID)
381
- .sort((a, b) => b.callbackPriority - a.callbackPriority); // 按优先级从大到小排序
382
-
383
- return sortedList.length > 0 ? sortedList[0].positionId : null;
450
+ .filter(item => {
451
+ // 支持数字和字符串类型的比较
452
+ const itemSdkId = Number(item.sdkId);
453
+ const match = itemSdkId === targetSdkId || String(item.sdkId) === String(targetSdkId);
454
+ console.log('[AdSDK] positionSetList 项检查:', {
455
+ item: item,
456
+ itemSdkId: item.sdkId,
457
+ itemSdkIdType: typeof item.sdkId,
458
+ targetSdkId: targetSdkId,
459
+ targetSdkIdType: typeof targetSdkId,
460
+ match: match
461
+ });
462
+ return match;
463
+ })
464
+ .sort((a, b) => (b.callbackPriority || 0) - (a.callbackPriority || 0)); // 按优先级从大到小排序
465
+
466
+ console.log('[AdSDK] 筛选后的列表:', sortedList);
467
+ if (sortedList.length > 0) {
468
+ console.log('[AdSDK] ✅ 在 positionSetList 中找到匹配项:', sortedList[0]);
469
+ return sortedList[0].positionId;
470
+ }
384
471
  }
385
472
 
473
+ console.error('[AdSDK] ❌ 未找到匹配的 sdkId:', targetSdkId, '数据:', data);
386
474
  return null;
387
475
  }
388
476
 
@@ -453,6 +541,7 @@
453
541
  // 构建 HTML 结构
454
542
  this.container.innerHTML = `
455
543
  <div class="q-ad-wrapper">
544
+ <button class="q-ad-close" title="关闭广告">×</button>
456
545
  <div class="q-media-container">
457
546
  <div class="q-ad-video" style="display:none"></div>
458
547
  <img class="q-ad-img" style="display:none">
@@ -466,6 +555,15 @@
466
555
  </div>
467
556
  `;
468
557
 
558
+ // 绑定关闭按钮事件
559
+ const closeBtn = this.container.querySelector('.q-ad-close');
560
+ if (closeBtn) {
561
+ closeBtn.addEventListener('click', (e) => {
562
+ e.stopPropagation(); // 阻止事件冒泡,避免触发广告点击
563
+ this.destroy();
564
+ });
565
+ }
566
+
469
567
  const videoEl = this.container.querySelector('.q-ad-video');
470
568
  const imgEl = this.container.querySelector('.q-ad-img');
471
569
  const wrapper = this.container.querySelector('.q-ad-wrapper');
@@ -505,6 +603,40 @@
505
603
  if (this.config.onAdLoaded) this.config.onAdLoaded(ad);
506
604
  }
507
605
 
606
+ /**
607
+ * 销毁广告实例
608
+ * 清理 DOM、解绑事件、释放资源,并隐藏容器元素
609
+ */
610
+ destroy() {
611
+ try {
612
+ // 1. 销毁底层广告实例
613
+ if (this.currentAd && typeof this.currentAd.destroy === 'function') {
614
+ this.currentAd.destroy();
615
+ this.currentAd = null;
616
+ }
617
+
618
+ // 2. 清空容器内容
619
+ if (this.container) {
620
+ this.container.innerHTML = '';
621
+ // 3. 隐藏容器元素
622
+ if (this.container.style) {
623
+ this.container.style.display = 'none';
624
+ } else {
625
+ // 如果 style 不可用,使用 class
626
+ this.container.classList.add('q-ad-destroyed');
627
+ }
628
+ }
629
+
630
+ // 4. 清理引用
631
+ this.gdtAppId = null;
632
+ this.gdtPlacementId = null;
633
+
634
+ console.log('[AdSDK] 广告已销毁');
635
+ } catch (err) {
636
+ console.error('[AdSDK] 销毁广告时出错:', err);
637
+ }
638
+ }
639
+
508
640
  _handleError(error, message) {
509
641
  // 内部错误也进行过滤,防止将过滤掉的错误再次打印
510
642
  const errStr = String(error).toLowerCase();
@@ -38,15 +38,19 @@
38
38
  console.log('✅ 封装 SDK 加载成功');
39
39
 
40
40
  // 初始化广告
41
- new window.AdSDK({
42
- appId: '1999336062823956569',
43
- placementId: '1999381081819709520',
41
+ const adSDK = new window.AdSDK({
42
+ appId: '1999364640831725629',
43
+ placementId: '2003009002186760245',
44
44
  container: '#ad-slot-1',
45
45
  onAdLoaded: () => console.log('✅ 广告加载完成'),
46
46
  onAdError: (err, msg) => console.log('❌ 广告报错:', err, msg),
47
47
  onAdExpose: () => console.log('👀 广告曝光'),
48
48
  onAdClick: () => console.log('🖱️ 用户点击了广告')
49
49
  });
50
+
51
+ // 示例:可以通过 destroy 方法销毁广告
52
+ // 广告右上角会自动显示关闭按钮,点击即可关闭
53
+ // 也可以手动调用:adSDK.destroy();
50
54
  });
51
55
  </script>
52
56
  </body>