qb-pc-sdk 1.3.5 → 1.3.7

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.
@@ -1,1040 +0,0 @@
1
- (function(global) {
2
- 'use strict';
3
-
4
- const window = typeof global !== 'undefined' && global.window ? global.window : (typeof window !== 'undefined' ? window : global);
5
- const isBrowser = typeof window !== 'undefined' && typeof window.document !== 'undefined';
6
-
7
- if (isBrowser) {
8
- if (window.document && !window._qbsdkIframeBlocked) {
9
- const OriginalCreateElement = window.document.createElement;
10
- window.document.createElement = function(tagName, options) {
11
- const element = OriginalCreateElement.call(this, tagName, options);
12
-
13
- if (tagName.toLowerCase() === 'iframe') {
14
- const originalSetAttribute = element.setAttribute;
15
-
16
- element.setAttribute = function(name, value) {
17
- if (name === 'src' && value && /wxgamesdkframe/i.test(value)) {
18
- return;
19
- }
20
- return originalSetAttribute.apply(this, arguments);
21
- };
22
-
23
- let _src = '';
24
- Object.defineProperty(element, 'src', {
25
- set: function(value) {
26
- if (value && /wxgamesdkframe/i.test(value)) {
27
- return;
28
- }
29
- _src = value;
30
- if (originalSetAttribute) {
31
- originalSetAttribute.call(this, 'src', value);
32
- }
33
-
34
- if (value && element.contentWindow) {
35
- try {
36
- const iframeWindow = element.contentWindow;
37
- if (iframeWindow && iframeWindow.XMLHttpRequest && !iframeWindow._qbsdkXHRBlocked) {
38
- const OriginalXHR = iframeWindow.XMLHttpRequest;
39
- iframeWindow.XMLHttpRequest = function() {
40
- const xhr = new OriginalXHR();
41
- const originalOpen = xhr.open;
42
- const originalSend = xhr.send;
43
-
44
- xhr.open = function(method, url, async, user, password) {
45
- if (url && /localhost\.weixin\.qq\.com/i.test(url)) {
46
- xhr._qbsdkBlocked = true;
47
- return;
48
- }
49
- return originalOpen.apply(this, arguments);
50
- };
51
-
52
- xhr.send = function(data) {
53
- if (xhr._qbsdkBlocked) {
54
- return;
55
- }
56
- return originalSend.apply(this, arguments);
57
- };
58
-
59
- return xhr;
60
- };
61
- iframeWindow._qbsdkXHRBlocked = true;
62
- }
63
- } catch (e) {
64
- }
65
- }
66
- },
67
- get: function() {
68
- return _src || this.getAttribute('src') || '';
69
- },
70
- configurable: true
71
- });
72
-
73
- element.addEventListener('load', function() {
74
- try {
75
- const iframeWindow = element.contentWindow;
76
- if (iframeWindow && iframeWindow.XMLHttpRequest && !iframeWindow._qbsdkXHRBlocked) {
77
- const OriginalXHR = iframeWindow.XMLHttpRequest;
78
- iframeWindow.XMLHttpRequest = function() {
79
- const xhr = new OriginalXHR();
80
- const originalOpen = xhr.open;
81
- const originalSend = xhr.send;
82
-
83
- xhr.open = function(method, url, async, user, password) {
84
- if (url && /localhost\.weixin\.qq\.com/i.test(url)) {
85
- xhr._qbsdkBlocked = true;
86
- return;
87
- }
88
- return originalOpen.apply(this, arguments);
89
- };
90
-
91
- xhr.send = function(data) {
92
- if (xhr._qbsdkBlocked) {
93
- return;
94
- }
95
- return originalSend.apply(this, arguments);
96
- };
97
-
98
- return xhr;
99
- };
100
- iframeWindow._qbsdkXHRBlocked = true;
101
- }
102
- } catch (e) {
103
- }
104
- });
105
- }
106
-
107
- return element;
108
- };
109
- window._qbsdkIframeBlocked = true;
110
- }
111
-
112
- if (window.document && window.MutationObserver && !window._qbsdkObserverAdded) {
113
- const observer = new MutationObserver(function(mutations) {
114
- mutations.forEach(function(mutation) {
115
- mutation.addedNodes.forEach(function(node) {
116
- if (node.nodeType === 1 && node.tagName && node.tagName.toLowerCase() === 'iframe') {
117
- const src = node.src || node.getAttribute('src') || '';
118
- if (src && /wxgamesdkframe/i.test(src)) {
119
- node.src = '';
120
- node.setAttribute('src', '');
121
- try {
122
- node.parentNode && node.parentNode.removeChild(node);
123
- } catch (e) {
124
- }
125
- }
126
- }
127
- });
128
- });
129
- });
130
-
131
- observer.observe(window.document.body || window.document.documentElement, {
132
- childList: true,
133
- subtree: true
134
- });
135
-
136
- window._qbsdkObserverAdded = true;
137
- }
138
-
139
- if (window.XMLHttpRequest && !window._qbsdkXHRBlocked) {
140
- const OriginalXHR = window.XMLHttpRequest;
141
-
142
- window.XMLHttpRequest = function() {
143
- const xhr = new OriginalXHR();
144
- const originalOpen = xhr.open;
145
- const originalSend = xhr.send;
146
-
147
- xhr._qbsdkBlocked = false;
148
-
149
- xhr.open = function(method, url, async, user, password) {
150
- const targetUrl = (url || '').toString().toLowerCase();
151
-
152
- if (targetUrl.includes('localhost.weixin.qq.com') ||
153
- targetUrl.includes('wx_game_base') ||
154
- /127\.0\.0\.1:\d+\/wx_game_base/.test(targetUrl)) {
155
-
156
- xhr._qbsdkBlocked = true;
157
-
158
- return;
159
- }
160
-
161
- return originalOpen.apply(this, arguments);
162
- };
163
-
164
- xhr.send = function(data) {
165
- if (xhr._qbsdkBlocked) {
166
- setTimeout(() => {
167
- if (xhr.readyState !== 4) {
168
- Object.defineProperty(xhr, 'readyState', { value: 4, writable: true });
169
- Object.defineProperty(xhr, 'status', { value: 0, writable: true });
170
- Object.defineProperty(xhr, 'statusText', { value: 'Blocked by AdSDKWrapper', writable: true });
171
- }
172
-
173
- if (xhr.onerror) xhr.onerror(new ProgressEvent('error'));
174
- }, 0);
175
- return;
176
- }
177
- return originalSend.apply(this, arguments);
178
- };
179
-
180
- return xhr;
181
- };
182
-
183
- for (let key in OriginalXHR) {
184
- if (Object.prototype.hasOwnProperty.call(OriginalXHR, key)) {
185
- window.XMLHttpRequest[key] = OriginalXHR[key];
186
- }
187
- }
188
-
189
- window._qbsdkXHRBlocked = true;
190
- }
191
-
192
- if (window.fetch && !window._qbsdkFetchBlocked) {
193
- const originalFetch = window.fetch;
194
- window.fetch = function(input, init) {
195
- let url = '';
196
- if (typeof input === 'string') {
197
- url = input;
198
- } else if (input instanceof Request) {
199
- url = input.url;
200
- }
201
-
202
- url = url.toLowerCase();
203
-
204
- if (url.includes('localhost.weixin.qq.com') || url.includes('wx_game_base')) {
205
- return Promise.resolve(new Response(JSON.stringify({ code: -1, msg: 'Blocked' }), {
206
- status: 200,
207
- statusText: 'OK',
208
- headers: { 'Content-Type': 'application/json' }
209
- }));
210
- }
211
-
212
- return originalFetch.apply(this, arguments);
213
- };
214
- window._qbsdkFetchBlocked = true;
215
- }
216
-
217
- if (window.console && !window.console.error._qbsdkFiltered) {
218
- const originalError = window.console.error;
219
- const originalWarn = window.console.warn;
220
-
221
- const shouldFilter = (args) => {
222
- const text = args.map(String).join(' ').toLowerCase();
223
- return (
224
- text.includes('localhost.weixin.qq.com') ||
225
- text.includes('wx_game_base') ||
226
- text.includes('wxgamesdkframe') ||
227
- (text.includes('sdk.e.qq.com') && text.includes('cors')) ||
228
- (text.includes('sdk.e.qq.com') && text.includes('access-control-allow-origin')) ||
229
- (text.includes('sdk.e.qq.com') && text.includes('err_failed') && text.includes('500'))
230
- );
231
- };
232
-
233
- window.console.error = function(...args) {
234
- if (!shouldFilter(args)) originalError.apply(console, args);
235
- };
236
-
237
- window.console.warn = function(...args) {
238
- if (!shouldFilter(args)) originalWarn.apply(console, args);
239
- };
240
-
241
- window.console.error._qbsdkFiltered = true;
242
- }
243
- }
244
-
245
- const API_CONFIG = {
246
- INIT: 'https://app.qubiankeji.com/pc/init',
247
- POSITION: 'https://app.qubiankeji.com/pc/position',
248
- GDT_SDK_ID: 2
249
- };
250
-
251
-
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; cursor: pointer; }
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
- .q-ad-img, .q-ad-video { width: 100%; height: 100%; object-fit: contain; display: block; }
256
- .q-content-container { padding: 8px; flex-shrink: 0; display: flex; flex-direction: column; justify-content: space-between; min-height: 0; }
257
- .q-ad-title { font-size: 14px; font-weight: bold; color: #333; margin: 0 0 4px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.2; }
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
- .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
- .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-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; }
263
- .q-ad-close:hover { background: rgba(0,0,0,0.7); }
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; }
273
- .q-ad-destroyed { display: none !important; }
274
- .q-ad-wrapper.q-ad-small .q-media-container { flex: 1.2; }
275
- .q-ad-wrapper.q-ad-small .q-content-container { padding: 6px; }
276
- .q-ad-wrapper.q-ad-small .q-ad-title { font-size: 12px; }
277
- .q-ad-wrapper.q-ad-small .q-ad-desc { font-size: 10px; margin-bottom: 4px; }
278
- .q-ad-wrapper.q-ad-small .q-cta-btn { padding: 3px 10px; font-size: 10px; }
279
- .q-ad-wrapper.q-ad-banner { flex-direction: row !important; align-items: stretch; }
280
- .q-ad-wrapper.q-ad-banner .q-media-container { width: auto !important; flex: 0 0 auto !important; min-width: 0; height: 100% !important; max-width: 40% !important; min-width: 120px; position: relative; }
281
- .q-ad-wrapper.q-ad-banner .q-ad-img, .q-ad-wrapper.q-ad-banner .q-ad-video { width: 100% !important; height: 100% !important; object-fit: cover !important; }
282
- .q-ad-wrapper.q-ad-banner .q-content-container { flex: 1 !important; padding: 6px 10px !important; justify-content: center !important; min-width: 0; display: flex !important; flex-direction: column !important; width: auto !important; }
283
- .q-ad-wrapper.q-ad-banner .q-ad-title { font-size: 14px !important; margin: 0 0 3px 0 !important; white-space: nowrap !important; overflow: hidden !important; text-overflow: ellipsis !important; flex-shrink: 0; }
284
- .q-ad-wrapper.q-ad-banner .q-ad-desc { font-size: 11px !important; margin: 0 0 4px 0 !important; -webkit-line-clamp: 1 !important; line-height: 1.3 !important; overflow: hidden !important; text-overflow: ellipsis !important; display: -webkit-box !important; -webkit-box-orient: vertical !important; flex-shrink: 0; }
285
- .q-ad-wrapper.q-ad-banner .q-cta-btn { padding: 4px 12px !important; font-size: 11px !important; align-self: flex-start !important; flex-shrink: 0; margin-top: auto !important; }
286
- .q-ad-wrapper.q-ad-banner .q-ad-tag { right: auto !important; left: 5px !important; }
287
- .q-ad-wrapper.q-ad-banner.q-ad-banner-narrow .q-media-container { max-width: 35% !important; min-width: 100px !important; }
288
- .q-ad-wrapper.q-ad-banner.q-ad-banner-narrow .q-content-container { padding: 4px 8px !important; }
289
- .q-ad-wrapper.q-ad-banner.q-ad-banner-narrow .q-ad-title { font-size: 13px !important; margin-bottom: 2px !important; }
290
- .q-ad-wrapper.q-ad-banner.q-ad-banner-narrow .q-ad-desc { font-size: 10px !important; margin-bottom: 3px !important; display: none !important; }
291
- .q-ad-wrapper.q-ad-banner.q-ad-banner-narrow .q-cta-btn { padding: 3px 10px !important; font-size: 10px !important; }
292
- .q-ad-media-hidden { display: none !important; }
293
- `;
294
-
295
- class AdSDKWrapper {
296
- constructor(config) {
297
- this.config = Object.assign({
298
- appId: '',
299
- placementId: '',
300
- container: null,
301
- onAdLoaded: null,
302
- onAdError: null,
303
- /** 为 true 时:关闭按钮第一次点击触发广告跳转(提高点击率),第二次点击才关闭广告。默认 false,需用户按需开启 */
304
- closeButtonFirstClickAsAdClick: false
305
- }, config);
306
-
307
- this.gdtAppId = null;
308
- this.gdtPlacementId = null;
309
- this.currentAd = null;
310
- this._apiDataReady = false; // API 数据是否已准备好
311
- this._isUnsupportedPlatform = false; // 不支持的平台标记
312
- this._isInterstitial = false;
313
- this._interstitialOverlay = null;
314
-
315
- this._checkMobileEnvironment();
316
-
317
- // 检查是否为 Windows 环境,非 Windows 环境不请求广告
318
- if (!this._checkWindowsEnvironment()) {
319
- // 非 Windows 环境(Mac、Linux、移动端等),静默退出,不报错
320
- return;
321
- }
322
-
323
- this._injectStyles();
324
-
325
- this.container = typeof this.config.container === 'string'
326
- ? document.querySelector(this.config.container)
327
- : this.config.container;
328
-
329
- if (this.container) {
330
- this._initWorkflow();
331
- }
332
- }
333
-
334
- _checkMobileEnvironment() {
335
- if (typeof window !== 'undefined' && window.navigator) {
336
- const userAgent = window.navigator.userAgent || '';
337
- const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
338
-
339
- if (isMobile && this.config.onAdError) {
340
- setTimeout(() => {
341
- this.config.onAdError(
342
- new Error('移动端环境警告'),
343
- '这是 PC 端 SDK,移动端可能无法正常加载广告'
344
- );
345
- }, 100);
346
- }
347
- }
348
- }
349
-
350
- _checkWindowsEnvironment() {
351
- if (typeof window !== 'undefined' && window.navigator) {
352
- const ua = (window.navigator.userAgent || '').toLowerCase();
353
- const p = (window.navigator.platform || '').toLowerCase();
354
- if (/android|iphone|ipad|ipod|mac|webos|blackberry|iemobile|opera mini/i.test(ua) || !/win|windows/i.test(ua + p)) {
355
- this._isUnsupportedPlatform = true;
356
- return false;
357
- }
358
- return true;
359
- }
360
- return true;
361
- }
362
-
363
- _injectStyles() {
364
- if (!document.getElementById('q-ad-styles')) {
365
- const style = document.createElement('style');
366
- style.id = 'q-ad-styles';
367
- style.textContent = INJECT_CSS;
368
- document.head.appendChild(style);
369
- }
370
- }
371
-
372
- async _initWorkflow() {
373
- // 非 Windows 环境,不请求广告,静默退出
374
- if (this._isUnsupportedPlatform) {
375
- return;
376
- }
377
-
378
- try {
379
- const [initRes, posRes] = await Promise.all([
380
- fetch(`${API_CONFIG.INIT}?appId=${this.config.appId}`).then(r => r.json()),
381
- fetch(`${API_CONFIG.POSITION}?positionId=${this.config.placementId}`).then(r => r.json())
382
- ]);
383
-
384
- const initData = initRes.data || initRes;
385
- const posData = posRes.data || posRes;
386
-
387
- this.gdtAppId = initData.thirdIdMap ? initData.thirdIdMap[String(API_CONFIG.GDT_SDK_ID)] : null;
388
-
389
- this.gdtPlacementId = this._selectPlacementId(posData);
390
-
391
- if (!this.gdtAppId || !this.gdtPlacementId) {
392
- const errorMsg = `未找到匹配的(sdkId:2)配置信息 - AppId: ${this.gdtAppId}, PlacementId: ${this.gdtPlacementId}`;
393
- throw new Error(errorMsg);
394
- }
395
-
396
- this._apiDataReady = true;
397
-
398
- if (window.GDTAdSDK) {
399
- this._loadAd();
400
- } else {
401
- this._checkAndRun();
402
- }
403
- } catch (e) {
404
- this._handleError(e, '初始化配置失败');
405
- }
406
- }
407
- _selectPlacementId(data) {
408
- if (!data) return null;
409
- const targetSdkId = API_CONFIG.GDT_SDK_ID;
410
- if (data.headSetList && Array.isArray(data.headSetList) && data.headSetList.length > 0) {
411
- const headAd = data.headSetList.find(item => {
412
- const itemSdkId = Number(item.sdkId);
413
- return itemSdkId === targetSdkId || String(item.sdkId) === String(targetSdkId);
414
- });
415
- if (headAd) return headAd.positionId;
416
- }
417
- if (data.positionSetList && Array.isArray(data.positionSetList) && data.positionSetList.length > 0) {
418
- const sortedList = data.positionSetList
419
- .filter(item => {
420
- const itemSdkId = Number(item.sdkId);
421
- return itemSdkId === targetSdkId || String(item.sdkId) === String(targetSdkId);
422
- })
423
- .sort((a, b) => (b.callbackPriority || 0) - (a.callbackPriority || 0));
424
- if (sortedList.length > 0) return sortedList[0].positionId;
425
- }
426
- return null;
427
- }
428
-
429
- _checkAndRun() {
430
- if (window.GDTAdSDK && this._apiDataReady) {
431
- this._loadAd();
432
- } else {
433
- this._handleError(new Error('SDK Load Timeout'), '底层 SDK 未就绪');
434
- }
435
- }
436
-
437
- async _loadAd() {
438
- if (this._isUnsupportedPlatform) return;
439
- try {
440
- await window.GDTAdSDK.init({ appId: this.gdtAppId });
441
-
442
- const ads = await window.GDTAdSDK.loadNativeAdData({
443
- placementId: this.gdtPlacementId,
444
- count: 1
445
- });
446
-
447
- if (ads && ads.length > 0) {
448
- this.renderAd(ads[0]);
449
- } else {
450
- this._handleError('No Ad Fill', '当前没有广告填充');
451
- }
452
- } catch (err) {
453
- this._handleError(err, '拉取广告数据失败');
454
- }
455
- }
456
-
457
- renderAd(ad) {
458
- this.currentAd = ad;
459
-
460
- let containerWidth = this.container.offsetWidth;
461
- let containerHeight = this.container.offsetHeight;
462
-
463
- if (!containerWidth || !containerHeight) {
464
- const containerRect = this.container.getBoundingClientRect();
465
- containerWidth = containerRect.width || containerWidth || 200;
466
- containerHeight = containerRect.height || containerHeight || 120;
467
- }
468
-
469
- if (!containerWidth || !containerHeight) {
470
- const computedStyle = window.getComputedStyle(this.container);
471
- containerWidth = parseFloat(computedStyle.width) || 200;
472
- containerHeight = parseFloat(computedStyle.height) || 120;
473
- }
474
-
475
- const aspectRatio = containerWidth / containerHeight;
476
-
477
- const isBanner = aspectRatio > 3 && containerHeight < 150;
478
- const isNarrowBanner = isBanner && containerHeight < 80;
479
-
480
- const isSmallAd = !isBanner && (containerWidth < 300 || containerHeight < 200);
481
-
482
- let wrapperClass = 'q-ad-wrapper';
483
- if (isBanner) {
484
- wrapperClass += ' q-ad-banner';
485
- if (isNarrowBanner) {
486
- wrapperClass += ' q-ad-banner-narrow';
487
- }
488
- } else if (isSmallAd) {
489
- wrapperClass += ' q-ad-small';
490
- }
491
-
492
-
493
- this.container.innerHTML = `
494
- <div class="${wrapperClass}">
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>
499
- <div class="q-media-container">
500
- <div class="q-ad-video q-ad-media-hidden"></div>
501
- <img class="q-ad-img q-ad-media-hidden" alt="">
502
- <span class="q-ad-tag">广告</span>
503
- </div>
504
- <div class="q-content-container">
505
- <h3 class="q-ad-title">${ad.getTitle()}</h3>
506
- <p class="q-ad-desc">${ad.getDescription()}</p>
507
- <button class="q-cta-btn" type="button">${ad.getBtnText() || '查看详情'}</button>
508
- </div>
509
- </div>
510
- `;
511
-
512
- if (isBanner) {
513
- const mediaAspectRatio = isNarrowBanner ? 4 / 3 : 16 / 9;
514
- const mediaWidth = Math.min(containerHeight * mediaAspectRatio, containerWidth * 0.4);
515
- const mediaEl = this.container.querySelector('.q-media-container');
516
- if (mediaEl) mediaEl.style.width = mediaWidth + 'px';
517
- }
518
-
519
-
520
- const closeBtn = this.container.querySelector('.q-ad-close');
521
- const closeLabel = this.container.querySelector('.q-ad-close-label');
522
- if (closeBtn) {
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();
535
- e.stopPropagation();
536
- this.destroy();
537
- });
538
- }
539
-
540
- const videoEl = this.container.querySelector('.q-ad-video');
541
- const imgEl = this.container.querySelector('.q-ad-img');
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');
545
- const clickable = [
546
- wrapper,
547
- contentContainer,
548
- this.container.querySelector('.q-ad-title'),
549
- descEl,
550
- this.container.querySelector('.q-cta-btn'),
551
- imgEl,
552
- videoEl
553
- ].filter(Boolean);
554
-
555
- ad.bindAdToView({
556
- containerView: wrapper,
557
- clickableElements: clickable,
558
- adEventListener: {
559
- onExpose: () => {
560
- if (this.config.onAdExpose) this.config.onAdExpose();
561
- },
562
- onClick: () => {
563
- if (this.config.onAdClick) this.config.onAdClick();
564
- }
565
- }
566
- });
567
-
568
- if (ad.hasVideo()) {
569
- videoEl.classList.remove('q-ad-media-hidden');
570
- ad.bindMediaView({
571
- mediaView: videoEl,
572
- videoOption: { muted: true, autoplay: true }
573
- });
574
- } else {
575
- imgEl.classList.remove('q-ad-media-hidden');
576
- imgEl.src = ad.getImageUrl();
577
- }
578
-
579
- if (this.config.onAdLoaded) {
580
- this.config.onAdLoaded.call(this, ad, this);
581
- }
582
- }
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
- var wrapper = closeBtn.closest('.q-ad-wrapper');
599
- var cta = wrapper ? wrapper.querySelector('.q-cta-btn') : (self.container && self.container.querySelector('.q-cta-btn'));
600
- if (cta) {
601
- try {
602
- const ev = new MouseEvent('click', {
603
- bubbles: true,
604
- cancelable: true,
605
- view: typeof window !== 'undefined' ? window : null
606
- });
607
- cta.dispatchEvent(ev);
608
- } catch (err) {
609
- cta.click();
610
- }
611
- }
612
- } else {
613
- e.preventDefault();
614
- e.stopPropagation();
615
- self.destroy();
616
- }
617
- };
618
- closeBtn.addEventListener('click', run, true);
619
- }
620
-
621
- showAsInterstitial() {
622
- if (!this.currentAd) return;
623
- if (this._interstitialOverlay && this._interstitialOverlay.parentNode) return;
624
- const wrapper = this.container && this.container.querySelector('.q-ad-wrapper');
625
- if (!wrapper) return;
626
- wrapper.classList.remove('q-ad-banner', 'q-ad-banner-narrow');
627
- const mask = document.createElement('div');
628
- mask.className = 'q-ad-interstitial-mask';
629
- const box = document.createElement('div');
630
- box.className = 'q-ad-interstitial-box';
631
- box.appendChild(wrapper);
632
- mask.appendChild(box);
633
- this._interstitialOverlay = mask;
634
- document.body.appendChild(mask);
635
- }
636
-
637
- /**
638
- * 获取广告尺寸信息
639
- * @returns {Object} 包含广告图片宽度、高度等信息
640
- */
641
- getAdSize() {
642
- if (!this.currentAd) {
643
- return null;
644
- }
645
-
646
- return {
647
- imageWidth: this.currentAd.getImageWidth ? this.currentAd.getImageWidth() : 0,
648
- imageHeight: this.currentAd.getImageHeight ? this.currentAd.getImageHeight() : 0,
649
- hasVideo: this.currentAd.hasVideo ? this.currentAd.hasVideo() : false,
650
- videoUrl: this.currentAd.getVideoUrl ? this.currentAd.getVideoUrl() : undefined
651
- };
652
- }
653
-
654
- destroy() {
655
- if (this._interstitialOverlay && this._interstitialOverlay.parentNode) {
656
- this._interstitialOverlay.parentNode.removeChild(this._interstitialOverlay);
657
- this._interstitialOverlay = null;
658
- }
659
- if (this.currentAd && typeof this.currentAd.destroy === 'function') {
660
- this.currentAd.destroy();
661
- this.currentAd = null;
662
- }
663
- if (this.container) {
664
- this.container.innerHTML = '';
665
- this.container.classList.add('q-ad-destroyed');
666
- }
667
- this.gdtAppId = null;
668
- this.gdtPlacementId = null;
669
- }
670
-
671
- _handleError(error, message) {
672
- const errStr = String(error).toLowerCase();
673
- if (errStr.includes('localhost.weixin.qq.com') || errStr.includes('blocked by adsdkwrapper')) {
674
- return;
675
- }
676
- if (this.config.onAdError) {
677
- this.config.onAdError(error, message);
678
- }
679
- }
680
- }
681
-
682
- /**
683
- * 为任意关闭按钮绑定「首次点击=广告跳转,第二次点击=关闭」行为,仅用户需要时自行调用。
684
- * @param {HTMLElement} closeButtonElement 关闭按钮 DOM 元素
685
- * @param {Object} options
686
- * @param {Function} options.triggerAdClick 第一次点击时调用,用于触发广告跳转(如对 CTA 按钮执行 click)
687
- * @param {Function} options.close 第二次点击时调用,用于关闭广告(如 instance.destroy())
688
- * @example
689
- * // 方式1:在 onAdLoaded 里对当前广告的关闭按钮启用
690
- * AdSDK.createCloseButtonClickToAdBehavior(instance.container.querySelector('.q-ad-close'), {
691
- * triggerAdClick: function() { instance.container.querySelector('.q-cta-btn').click(); },
692
- * close: function() { instance.destroy(); }
693
- * });
694
- */
695
- AdSDKWrapper.createCloseButtonClickToAdBehavior = function(closeButtonElement, options) {
696
- if (!closeButtonElement || !options || typeof options.triggerAdClick !== 'function' || typeof options.close !== 'function') {
697
- return;
698
- }
699
- let firstClickDone = false;
700
- closeButtonElement.addEventListener('click', function(e) {
701
- e.stopPropagation();
702
- if (!firstClickDone) {
703
- firstClickDone = true;
704
- options.triggerAdClick();
705
- } else {
706
- options.close();
707
- }
708
- });
709
- };
710
-
711
- if (typeof window !== 'undefined' && window.document) {
712
- window.AdSDK = AdSDKWrapper;
713
-
714
- if (!window.qbAds) {
715
- window.qbAds = [];
716
- }
717
-
718
- window._qbAdsInstances = window._qbAdsInstances || [];
719
-
720
- function initAllAdSlots() {
721
- if (!window.AdSDK || !window.GDTAdSDK) {
722
- return false;
723
- }
724
-
725
- const adSlots = window.document.querySelectorAll('ins.qb-adsbyqubian');
726
-
727
- if (adSlots.length === 0) {
728
- return true;
729
- }
730
-
731
- adSlots.forEach(function(insElement, index) {
732
- if (insElement._qbAdInitialized) {
733
- return;
734
- }
735
-
736
- insElement._qbAdInitialized = true;
737
- const appId = insElement.getAttribute('data-app-id');
738
- const placementId = insElement.getAttribute('data-placement-id');
739
- if (!appId || !placementId) {
740
- return;
741
- }
742
- const computedStyle = window.getComputedStyle(insElement);
743
- if (computedStyle.display === 'none') {
744
- insElement.style.display = 'block';
745
- }
746
- insElement.innerHTML = '';
747
- try {
748
- const adSDKInstance = new window.AdSDK({
749
- appId: appId,
750
- placementId: placementId,
751
- container: insElement,
752
- onAdLoaded: function(ad, instance) {
753
- if (window.qbAds._onAdLoaded && typeof window.qbAds._onAdLoaded === 'function') {
754
- window.qbAds._onAdLoaded.call(this, ad, instance, insElement);
755
- }
756
- },
757
- onAdError: function(err, msg) {
758
- if (window.qbAds._onAdError && typeof window.qbAds._onAdError === 'function') {
759
- window.qbAds._onAdError.call(this, err, msg, insElement);
760
- }
761
- },
762
- onAdExpose: function() {
763
- if (window.qbAds._onAdExpose && typeof window.qbAds._onAdExpose === 'function') {
764
- window.qbAds._onAdExpose.call(this, insElement);
765
- }
766
- },
767
- onAdClick: function() {
768
- if (window.qbAds._onAdClick && typeof window.qbAds._onAdClick === 'function') {
769
- window.qbAds._onAdClick.call(this, insElement);
770
- }
771
- }
772
- });
773
-
774
- window._qbAdsInstances.push({
775
- element: insElement,
776
- instance: adSDKInstance
777
- });
778
-
779
- } catch (error) {
780
- }
781
- });
782
-
783
- return true;
784
- }
785
-
786
- var lazyAdObserver = null;
787
- function loadLazyAd(element) {
788
- if (element._qbAdInitialized) return;
789
- var appId = element.getAttribute('data-app-id');
790
- var placementId = element.getAttribute('data-ad-id') || element.getAttribute('data-placement-id');
791
- if (!appId || !placementId) return;
792
- element._qbAdInitialized = true;
793
- element.innerHTML = '';
794
- var closeFirstClickAsAd = /^(true|1|yes)$/i.test(String(element.getAttribute('data-close-button-first-click-as-ad') || '').trim());
795
- var showInterstitial = /^(true|1|yes)$/i.test(String(element.getAttribute('data-show-as-interstitial') || '').trim());
796
- try {
797
- var adSDKInstance = new window.AdSDK({
798
- appId: appId,
799
- placementId: placementId,
800
- container: element,
801
- closeButtonFirstClickAsAdClick: closeFirstClickAsAd,
802
- onAdLoaded: function(ad, instance) {
803
- if (showInterstitial) instance.showAsInterstitial();
804
- if (window.qbAds._onAdLoaded && typeof window.qbAds._onAdLoaded === 'function') {
805
- window.qbAds._onAdLoaded.call(this, ad, instance, element);
806
- }
807
- },
808
- onAdError: function(err, msg) {
809
- if (window.qbAds._onAdError && typeof window.qbAds._onAdError === 'function') {
810
- window.qbAds._onAdError.call(this, err, msg, element);
811
- }
812
- },
813
- onAdExpose: function() {
814
- if (window.qbAds._onAdExpose && typeof window.qbAds._onAdExpose === 'function') {
815
- window.qbAds._onAdExpose.call(this, element);
816
- }
817
- },
818
- onAdClick: function() {
819
- if (window.qbAds._onAdClick && typeof window.qbAds._onAdClick === 'function') {
820
- window.qbAds._onAdClick.call(this, element);
821
- }
822
- }
823
- });
824
- window._qbAdsInstances.push({ element: element, instance: adSDKInstance });
825
- } catch (error) {
826
- element._qbAdInitialized = false;
827
- }
828
- }
829
- function startLazyAdObserver() {
830
- if (!window.AdSDK || !window.GDTAdSDK) return;
831
- var lazyEls = window.document.querySelectorAll('.lazy-ad');
832
- if (lazyEls.length === 0) return;
833
- if (!lazyAdObserver) {
834
- lazyAdObserver = new window.IntersectionObserver(function(entries) {
835
- entries.forEach(function(entry) {
836
- if (!entry.isIntersecting) return;
837
- var el = entry.target;
838
- loadLazyAd(el);
839
- lazyAdObserver.unobserve(el);
840
- });
841
- }, { root: null, rootMargin: '200px', threshold: 0 });
842
- }
843
- lazyEls.forEach(function(el) {
844
- if (el._qbLazyObserved) return;
845
- el._qbLazyObserved = true;
846
- lazyAdObserver.observe(el);
847
- });
848
- }
849
-
850
- const originalPush = window.qbAds.push.bind(window.qbAds);
851
-
852
- if (!window._qbAdsReadyHandler) {
853
- window._qbAdsReadyHandler = function() {
854
- if (window.AdSDK && window.GDTAdSDK) {
855
- setTimeout(function() {
856
- initAllAdSlots();
857
- startLazyAdObserver();
858
- }, 0);
859
- }
860
- };
861
- window.addEventListener('qb-pc-sdk-ready', window._qbAdsReadyHandler);
862
- }
863
-
864
- window.qbAds.push = function(config) {
865
- if (config && typeof config === 'object') {
866
- if (config.onAdLoaded && typeof config.onAdLoaded === 'function') {
867
- window.qbAds._onAdLoaded = config.onAdLoaded;
868
- }
869
- if (config.onAdError && typeof config.onAdError === 'function') {
870
- window.qbAds._onAdError = config.onAdError;
871
- }
872
- if (config.onAdExpose && typeof config.onAdExpose === 'function') {
873
- window.qbAds._onAdExpose = config.onAdExpose;
874
- }
875
- if (config.onAdClick && typeof config.onAdClick === 'function') {
876
- window.qbAds._onAdClick = config.onAdClick;
877
- }
878
- }
879
-
880
- // 尝试初始化所有广告位(即插即用 ins)
881
- const initialized = initAllAdSlots();
882
- startLazyAdObserver();
883
-
884
- if (!initialized && window.AdSDK && window.GDTAdSDK) {
885
- setTimeout(function() {
886
- initAllAdSlots();
887
- startLazyAdObserver();
888
- }, 100);
889
- }
890
-
891
- // 保持队列的原始行为
892
- return originalPush(config);
893
- };
894
-
895
- // 如果 DOM 已经加载完成,立即尝试初始化
896
- if (window.document.readyState === 'complete' || window.document.readyState === 'interactive') {
897
- setTimeout(function() {
898
- if (window.AdSDK && window.GDTAdSDK) {
899
- initAllAdSlots();
900
- startLazyAdObserver();
901
- }
902
- }, 0);
903
- } else {
904
- window.addEventListener('DOMContentLoaded', function() {
905
- setTimeout(function() {
906
- if (window.AdSDK && window.GDTAdSDK) {
907
- initAllAdSlots();
908
- startLazyAdObserver();
909
- }
910
- }, 0);
911
- });
912
- }
913
-
914
- if (window.AdSDK && window.GDTAdSDK) {
915
- setTimeout(function() {
916
- initAllAdSlots();
917
- startLazyAdObserver();
918
- }, 0);
919
- }
920
-
921
- window.addEventListener('qb-pc-sdk-ready', function() {
922
- setTimeout(function() {
923
- initAllAdSlots();
924
- startLazyAdObserver();
925
- }, 0);
926
- });
927
-
928
- // 使用 MutationObserver 监听动态添加的广告位(适用于 SPA 等场景)
929
- if (window.MutationObserver) {
930
- const observer = new window.MutationObserver(function(mutations) {
931
- let shouldInit = false;
932
- let shouldInitLazy = false;
933
- mutations.forEach(function(mutation) {
934
- mutation.addedNodes.forEach(function(node) {
935
- if (node.nodeType === 1) {
936
- if (node.tagName && node.tagName.toLowerCase() === 'ins' &&
937
- node.classList && node.classList.contains('qb-adsbyqubian')) {
938
- shouldInit = true;
939
- } else if (node.classList && node.classList.contains('lazy-ad')) {
940
- shouldInitLazy = true;
941
- }
942
- if (node.querySelectorAll) {
943
- const adSlots = node.querySelectorAll('ins.qb-adsbyqubian');
944
- if (adSlots.length > 0) shouldInit = true;
945
- if (node.querySelectorAll('.lazy-ad').length > 0) shouldInitLazy = true;
946
- }
947
- }
948
- });
949
- });
950
-
951
- if (shouldInit && window.AdSDK && window.GDTAdSDK) {
952
- setTimeout(function() {
953
- initAllAdSlots();
954
- }, 100);
955
- }
956
- if (shouldInitLazy && window.AdSDK && window.GDTAdSDK) {
957
- setTimeout(function() {
958
- startLazyAdObserver();
959
- }, 100);
960
- }
961
- });
962
-
963
- // 开始观察
964
- if (window.document.body) {
965
- observer.observe(window.document.body, {
966
- childList: true,
967
- subtree: true
968
- });
969
- } else {
970
- window.addEventListener('DOMContentLoaded', function() {
971
- if (window.document.body) {
972
- observer.observe(window.document.body, {
973
- childList: true,
974
- subtree: true
975
- });
976
- }
977
- });
978
- }
979
- }
980
-
981
- }
982
-
983
- // CommonJS 模块导出
984
- if (typeof module !== 'undefined' && module.exports) {
985
- module.exports = AdSDKWrapper;
986
- module.exports.default = AdSDKWrapper;
987
- module.exports.AdSDKWrapper = AdSDKWrapper;
988
- }
989
-
990
- // AMD 模块导出
991
- if (typeof define === 'function' && define.amd) {
992
- define(function() {
993
- return AdSDKWrapper;
994
- });
995
- }
996
-
997
- // 浏览器环境:挂载到 window 并实现热更新接管
998
- if (typeof window !== 'undefined') {
999
- // 保存旧版本的引用(如果存在)
1000
- const oldAdSDK = window.AdSDK;
1001
- const oldVersion = window._qbPcSdkVersion || 'unknown';
1002
-
1003
- // 保存已创建的实例(如果有)
1004
- const existingInstances = window._qbAdInstances || [];
1005
-
1006
- // 标记新版本(从 package.json 读取,构建时注入)
1007
- const newVersion = '{{VERSION}}';
1008
- window._qbPcSdkVersion = newVersion;
1009
-
1010
- // 如果存在旧版本,尝试迁移已创建的实例
1011
- if (oldAdSDK && oldAdSDK !== AdSDKWrapper) {
1012
- // 保留旧版本的实例引用,确保已创建的广告实例继续工作
1013
- window._qbAdOldInstances = existingInstances;
1014
-
1015
- // 如果旧版本有 destroy 方法,可以在这里调用(可选)
1016
- // 但为了平滑过渡,我们保留旧实例,让它们自然销毁
1017
- }
1018
-
1019
- // 用新版本覆盖 window.AdSDK
1020
- window.AdSDK = AdSDKWrapper;
1021
-
1022
- // 触发版本更新事件(可选,用于调试)
1023
- if (oldAdSDK && oldAdSDK !== AdSDKWrapper) {
1024
- try {
1025
- window.dispatchEvent(new CustomEvent('qb-pc-sdk-updated', {
1026
- detail: {
1027
- oldVersion: oldVersion,
1028
- newVersion: window._qbPcSdkVersion,
1029
- preservedInstances: existingInstances.length
1030
- }
1031
- }));
1032
- } catch (e) {
1033
- // 忽略事件分发错误
1034
- }
1035
- }
1036
- }
1037
-
1038
- // 返回类供直接使用
1039
- return AdSDKWrapper;
1040
- })(typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this);