qb-pc-sdk 1.0.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.
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 参考文档: https://ylh.qq.com/help_detail.html?cid=5068&pid=12808
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const YLH_AD_CONFIG = {
|
|
6
|
+
// 应用ID
|
|
7
|
+
appId: '1212355090',
|
|
8
|
+
|
|
9
|
+
// 广告位ID
|
|
10
|
+
// 注意:在API调用时使用 placementId 参数名
|
|
11
|
+
posId: '4204613922235231'
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// 导出配置
|
|
15
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
16
|
+
module.exports = YLH_AD_CONFIG;
|
|
17
|
+
} else if (typeof window !== 'undefined') {
|
|
18
|
+
window.YLH_AD_CONFIG = YLH_AD_CONFIG;
|
|
19
|
+
}
|
|
20
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 趣变广告 SDK 封装器
|
|
3
|
+
* 自动处理多级接口映射与原生渲染逻辑
|
|
4
|
+
*/
|
|
5
|
+
(function(window) {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
// 内部常量配置
|
|
9
|
+
const API_CONFIG = {
|
|
10
|
+
INIT: 'http://test.qubiankeji.com:8084/pc/init',
|
|
11
|
+
POSITION: 'http://test.qubiankeji.com:8084/pc/position',
|
|
12
|
+
GDT_SDK_ID: 2 // 标识
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// 内部样式,确保广告一定有默认样式,不会塌缩
|
|
16
|
+
const INJECT_CSS = `
|
|
17
|
+
.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; }
|
|
18
|
+
.q-media-container { width: 100%; height: 200px; background: #000; position: relative; display: flex; align-items: center; justify-content: center; }
|
|
19
|
+
.q-ad-img, .q-ad-video { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
20
|
+
.q-content-container { padding: 12px; flex: 1; display: flex; flex-direction: column; }
|
|
21
|
+
.q-ad-title { font-size: 16px; font-weight: bold; color: #333; margin: 0 0 5px 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
22
|
+
.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; }
|
|
23
|
+
.q-cta-btn { background: #007bff; color: #fff; border: none; padding: 6px 15px; border-radius: 4px; font-size: 12px; cursor: pointer; align-self: flex-start; }
|
|
24
|
+
.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; }
|
|
25
|
+
`;
|
|
26
|
+
|
|
27
|
+
class AdSDKWrapper {
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.config = Object.assign({
|
|
30
|
+
appId: '',
|
|
31
|
+
placementId: '',
|
|
32
|
+
container: null,
|
|
33
|
+
onAdLoaded: null,
|
|
34
|
+
onAdError: null
|
|
35
|
+
}, config);
|
|
36
|
+
|
|
37
|
+
this.gdtAppId = null;
|
|
38
|
+
this.gdtPlacementId = null;
|
|
39
|
+
this.currentAd = null;
|
|
40
|
+
|
|
41
|
+
this._injectStyles();
|
|
42
|
+
|
|
43
|
+
this.container = typeof this.config.container === 'string'
|
|
44
|
+
? document.querySelector(this.config.container)
|
|
45
|
+
: this.config.container;
|
|
46
|
+
|
|
47
|
+
if (this.container) {
|
|
48
|
+
this._initWorkflow();
|
|
49
|
+
} else {
|
|
50
|
+
console.error('[AdSDK] 找不到容器元素');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 注入默认 CSS
|
|
55
|
+
_injectStyles() {
|
|
56
|
+
if (!document.getElementById('q-ad-styles')) {
|
|
57
|
+
const style = document.createElement('style');
|
|
58
|
+
style.id = 'q-ad-styles';
|
|
59
|
+
style.textContent = INJECT_CSS;
|
|
60
|
+
document.head.appendChild(style);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async _initWorkflow() {
|
|
65
|
+
try {
|
|
66
|
+
// 1. 获取 ID 映射及广告位配置
|
|
67
|
+
const [initRes, posRes] = await Promise.all([
|
|
68
|
+
fetch(`${API_CONFIG.INIT}?appId=${this.config.appId}`).then(r => r.json()),
|
|
69
|
+
fetch(`${API_CONFIG.POSITION}?positionId=${this.config.placementId}`).then(r => r.json())
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
// 2. 提取 AppID
|
|
73
|
+
this.gdtAppId = initRes.thirdIdMap ? initRes.thirdIdMap[String(API_CONFIG.GDT_SDK_ID)] : null;
|
|
74
|
+
|
|
75
|
+
// 3. 执行广告位筛选逻辑
|
|
76
|
+
this.gdtPlacementId = this._selectPlacementId(posRes);
|
|
77
|
+
|
|
78
|
+
if (!this.gdtAppId || !this.gdtPlacementId) {
|
|
79
|
+
throw new Error('未找到匹配的(sdkId:2)配置信息');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
this._checkAndRun();
|
|
83
|
+
} catch (e) {
|
|
84
|
+
this._handleError(e, '初始化配置失败');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 核心逻辑:选择广告位 ID
|
|
90
|
+
* 规则:headSetList优先;否则选positionSetList中优先级最高的
|
|
91
|
+
*/
|
|
92
|
+
_selectPlacementId(data) {
|
|
93
|
+
// A. 优先检查 headSetList
|
|
94
|
+
if (data.headSetList && data.headSetList.length > 0) {
|
|
95
|
+
const headAd = data.headSetList.find(item => item.sdkId === API_CONFIG.GDT_SDK_ID);
|
|
96
|
+
if (headAd) return headAd.positionId;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// B. 检查 positionSetList
|
|
100
|
+
if (data.positionSetList && data.positionSetList.length > 0) {
|
|
101
|
+
const sortedList = data.positionSetList
|
|
102
|
+
.filter(item => item.sdkId === API_CONFIG.GDT_SDK_ID)
|
|
103
|
+
.sort((a, b) => b.callbackPriority - a.callbackPriority); // 按优先级从大到小排序
|
|
104
|
+
|
|
105
|
+
return sortedList.length > 0 ? sortedList[0].positionId : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_checkAndRun() {
|
|
112
|
+
let attempts = 0;
|
|
113
|
+
const check = () => {
|
|
114
|
+
if (window.GDTAdSDK) {
|
|
115
|
+
this._loadAd();
|
|
116
|
+
} else if (attempts++ < 50) {
|
|
117
|
+
setTimeout(check, 100);
|
|
118
|
+
} else {
|
|
119
|
+
this._handleError('SDK Load Timeout', '底层SDK加载超时');
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
check();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async _loadAd() {
|
|
126
|
+
try {
|
|
127
|
+
// 初始化底层 SDK
|
|
128
|
+
await window.GDTAdSDK.init({ appId: this.gdtAppId });
|
|
129
|
+
|
|
130
|
+
// 加载数据
|
|
131
|
+
const ads = await window.GDTAdSDK.loadNativeAdData({
|
|
132
|
+
placementId: this.gdtPlacementId,
|
|
133
|
+
count: 1
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (ads && ads.length > 0) {
|
|
137
|
+
this.renderAd(ads[0]);
|
|
138
|
+
} else {
|
|
139
|
+
this._handleError('No Ad Fill', '当前没有广告填充');
|
|
140
|
+
}
|
|
141
|
+
} catch (err) {
|
|
142
|
+
this._handleError(err, '拉取广告数据失败');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 渲染广告 DOM 并绑定事件
|
|
148
|
+
*/
|
|
149
|
+
renderAd(ad) {
|
|
150
|
+
this.currentAd = ad;
|
|
151
|
+
|
|
152
|
+
// 构建 HTML 结构
|
|
153
|
+
this.container.innerHTML = `
|
|
154
|
+
<div class="q-ad-wrapper">
|
|
155
|
+
<div class="q-media-container">
|
|
156
|
+
<div class="q-ad-video" style="display:none"></div>
|
|
157
|
+
<img class="q-ad-img" style="display:none">
|
|
158
|
+
<span class="q-ad-tag">广告</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div class="q-content-container">
|
|
161
|
+
<h3 class="q-ad-title">${ad.getTitle()}</h3>
|
|
162
|
+
<p class="q-ad-desc">${ad.getDescription()}</p>
|
|
163
|
+
<button class="q-cta-btn">${ad.getBtnText() || '查看详情'}</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
|
|
168
|
+
const videoEl = this.container.querySelector('.q-ad-video');
|
|
169
|
+
const imgEl = this.container.querySelector('.q-ad-img');
|
|
170
|
+
const wrapper = this.container.querySelector('.q-ad-wrapper');
|
|
171
|
+
const clickable = [
|
|
172
|
+
this.container.querySelector('.q-ad-title'),
|
|
173
|
+
this.container.querySelector('.q-cta-btn'),
|
|
174
|
+
imgEl,
|
|
175
|
+
videoEl
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
// 绑定曝光和点击
|
|
179
|
+
ad.bindAdToView({
|
|
180
|
+
containerView: wrapper,
|
|
181
|
+
clickableElements: clickable,
|
|
182
|
+
adEventListener: {
|
|
183
|
+
onExpose: () => {
|
|
184
|
+
if (this.config.onAdExpose) this.config.onAdExpose();
|
|
185
|
+
},
|
|
186
|
+
onClick: () => {
|
|
187
|
+
if (this.config.onAdClick) this.config.onAdClick();
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// 处理视频或图片素材
|
|
193
|
+
if (ad.hasVideo()) {
|
|
194
|
+
videoEl.style.display = 'block';
|
|
195
|
+
ad.bindMediaView({
|
|
196
|
+
mediaView: videoEl,
|
|
197
|
+
videoOption: { muted: true, autoplay: true }
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
imgEl.style.display = 'block';
|
|
201
|
+
imgEl.src = ad.getImageUrl();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (this.config.onAdLoaded) this.config.onAdLoaded(ad);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_handleError(error, message) {
|
|
208
|
+
console.error(`[AdSDK Error] ${message}:`, error);
|
|
209
|
+
if (this.config.onAdError) {
|
|
210
|
+
this.config.onAdError(error, message);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
window.AdSDK = AdSDKWrapper;
|
|
216
|
+
})(window);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Native广告接入示例</title>
|
|
7
|
+
<style>
|
|
8
|
+
body { margin: 0; padding: 20px; font-family: sans-serif; background: #f0f2f5; }
|
|
9
|
+
#ad-slot-1 {
|
|
10
|
+
width: 400px;
|
|
11
|
+
height: 300px;
|
|
12
|
+
margin: 0 auto;
|
|
13
|
+
background: #fff;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="ad-slot-1"></div>
|
|
19
|
+
|
|
20
|
+
<script src="https://qzs.gdtimg.com/union/res/pc/ylh-pc-ad-sdk.v1.25.25.js"></script>
|
|
21
|
+
<script src="ad-sdk-wrapper.js"></script>
|
|
22
|
+
|
|
23
|
+
<script>
|
|
24
|
+
window.addEventListener('DOMContentLoaded', () => {
|
|
25
|
+
new AdSDK({
|
|
26
|
+
appId: '1999336062823956569',
|
|
27
|
+
placementId: '1999381081819709520',
|
|
28
|
+
container: '#ad-slot-1',
|
|
29
|
+
onAdLoaded: () => console.log('✅ 广告加载完成'),
|
|
30
|
+
onAdError: (err) => console.log('❌ 广告报错:', err),
|
|
31
|
+
onAdClick: () => console.log('🖱️ 用户点击了广告')
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
</body>
|
|
36
|
+
</html>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
chcp 65001 >nul
|
|
3
|
+
echo ========================================
|
|
4
|
+
echo 广告接入 - 启动本地服务器
|
|
5
|
+
echo ========================================
|
|
6
|
+
echo.
|
|
7
|
+
|
|
8
|
+
REM 检查是否安装了Node.js
|
|
9
|
+
where node >nul 2>&1
|
|
10
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
11
|
+
echo [错误] 未检测到Node.js
|
|
12
|
+
echo 请先安装Node.js: https://nodejs.org/
|
|
13
|
+
echo.
|
|
14
|
+
pause
|
|
15
|
+
exit /b 1
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
echo [信息] 检测到Node.js
|
|
19
|
+
node --version
|
|
20
|
+
echo.
|
|
21
|
+
|
|
22
|
+
echo [信息] 正在启动HTTP服务器(已启用CORS)...
|
|
23
|
+
echo [信息] 服务器地址: http://localhost:8080
|
|
24
|
+
echo [信息] 访问地址: http://localhost:8080/src/example-simple.html
|
|
25
|
+
echo.
|
|
26
|
+
echo [提示] 按 Ctrl+C 停止服务器
|
|
27
|
+
echo ========================================
|
|
28
|
+
echo.
|
|
29
|
+
|
|
30
|
+
REM 启动服务器(启用CORS)
|
|
31
|
+
npx http-server -p 8080 --cors
|
|
32
|
+
|
|
33
|
+
pause
|
|
34
|
+
|