更新记录
1.0.0(2026-10-22)
首个版本:
- 支持激励视频、插屏广告、插屏式激励视频、开屏广告(仅 iOS)与 Banner 横幅组件
- Android 端为纯 UTS 实现(三方依赖走 config.json 的 Maven dependencies),iOS 端为 UTS + Swift 混编,随包 GoogleMobileAds 12.11.0
平台兼容性
uni-app(3.99)
| Vue2 | Vue3 | Chrome | Safari | app-vue | app-nvue | Android | iOS | 鸿蒙 |
|---|---|---|---|---|---|---|---|---|
| √ | √ | × | × | √ | √ | 5.0 | 12 | × |
| 微信小程序 | 支付宝小程序 | 抖音小程序 | 百度小程序 | 快手小程序 | 京东小程序 | 鸿蒙元服务 | QQ小程序 | 飞书小程序 | 小红书小程序 | 快应用-华为 | 快应用-联盟 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| × | × | × | × | × | × | × | × | × | × | × | × |
admob-uts(Google AdMob 广告插件 UTS 版)
Google AdMob 广告插件,支持 激励视频、插屏广告、插屏式激励视频、开屏广告(仅 iOS) 与 Banner 横幅组件,Android / iOS 双端。
本插件为完全明文源码,不做任何加密(
package.json中uni_modules.encrypt为空数组,插件目录内也不存在encrypt文件),可自由二次开发。
一、能力一览
| 能力 | 函数 / 组件 | Android | iOS |
|---|---|---|---|
| 激励视频 | loadRewardedAd / showRewardedAd |
√ | √ |
| 插屏广告 | loadInterstitialAd / showInterstitialAd |
√ | √ |
| 插屏式激励视频 | loadRewardedInterstitialAd / showRewardedInterstitialAd |
√ | √ |
| 开屏广告 | loadAppOpenAD / showAppOpenAD |
×(直接回调"仅支持 iOS") | √ |
| Banner 横幅 | 组件 admob-banner(仅 nvue / uvue 页面) |
√ | √ |
三、接入前配置
调试原生插件必须使用自定义基座。
1. 配置 App ID
iOS:项目根目录新建 Info.plist(注意大小写)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>GADApplicationIdentifier</key>
<string>这里填写你应用 iOS 的 APPID</string>
</dict>
</plist>
Android:项目根目录新建 AndroidManifest.xml(注意大小写)
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="io.dcloud.nativeresouce">
<application>
<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="这里填写你应用安卓的APPID"/>
</application>
</manifest>
2. 引用插件
import {
loadAppOpenAD,
showAppOpenAD,
loadRewardedAd,
showRewardedAd,
loadInterstitialAd,
showInterstitialAd,
loadRewardedInterstitialAd,
showRewardedInterstitialAd
} from '@/uni_modules/admob-uts'
3. 注意
插件测试过程中,请按谷歌要求使用测试广告位 ID 进行测试,以免账号被封停:
| 类型 | Android | iOS |
|---|---|---|
| 激励视频 | ca-app-pub-3940256099942544/5224354917 | ca-app-pub-3940256099942544/1712485313 |
| 插屏广告 | ca-app-pub-3940256099942544/1033173712 | ca-app-pub-3940256099942544/4411468910 |
| 插屏式激励视频 | ca-app-pub-3940256099942544/5354046379 | ca-app-pub-3940256099942544/6978759866 |
| 开屏广告 | ca-app-pub-3940256099942544/5575463023 | ca-app-pub-3940256099942544/5575463023 |
| Banner | ca-app-pub-3940256099942544/6300978111 | ca-app-pub-3940256099942544/2934735716 |
四、回调数据格式
所有回调均为 JSON 字符串:
{
"errorCode": 0,
"code": 100,
"msg": "激励视频加载成功",
"data": {}
}
| 字段 | 说明 |
|---|---|
errorCode |
0 成功,1 失败 |
code |
事件码,见下表 |
msg |
文本提示,可直接用于 toast |
data |
附加数据(可选,仅激励发放 / Banner 为空) |
事件码:
| code | 含义 |
|---|---|
| 100 | 加载成功 |
| 101 | 广告曝光(Impression) |
| 102 | 广告点击 |
| 103 | 广告关闭 |
| 104 | 加载失败 |
| 105 | 激励发放(仅激励视频 / 插屏式激励视频) |
| 106 | 展示失败 |
| 107 | 广告已展示(打开全屏内容) |
load* 方法只要 errorCode == 0 即代表加载完成,可以调用对应的 show* 播放;
show* 方法在广告播放过程中会持续回调(曝光 / 点击 / 关闭 / 激励发放)。
五、API 说明
5.1 激励视频
loadRewardedAd({
adid: 'ca-app-pub-3940256099942544/5224354917'
}, res => {
const resJson = JSON.parse(res)
if (resJson.errorCode == 0) {
console.log('广告 load 完成,可以播放')
} else {
console.log('广告 load 失败:' + resJson.msg)
}
})
// 加载成功后播放;customData 会在激励发放时原样返回
showRewardedAd({
adid: 'ca-app-pub-3940256099942544/5224354917',
customData: '123'
}, res => {
const resJson = JSON.parse(res)
if (resJson.code == 105) {
// 激励发放,此时才可给用户发放奖励
console.log('奖励发放', resJson.data)
}
})
5.2 插屏广告
loadInterstitialAd({
adid: 'ca-app-pub-3940256099942544/1033173712'
}, res => {
console.log('插屏加载进度更新 = ' + res)
})
showInterstitialAd({
adid: 'ca-app-pub-3940256099942544/1033173712'
}, res => {
console.log('插屏播放进度更新 = ' + res)
})
5.3 插屏式激励视频
loadRewardedInterstitialAd({
adid: 'ca-app-pub-3940256099942544/5354046379'
}, res => {
console.log('插屏式激励视频加载进度更新 = ' + res)
})
showRewardedInterstitialAd({
adid: 'ca-app-pub-3940256099942544/5354046379',
customData: '123'
}, res => {
console.log('插屏式激励视频播放进度更新 = ' + res)
})
5.4 开屏广告(仅 iOS)
loadAppOpenAD({
adid: 'ca-app-pub-3940256099942544/5575463023'
}, res => {
console.log('开屏广告加载进度更新 = ' + res)
})
showAppOpenAD({
adid: 'ca-app-pub-3940256099942544/5575463023'
}, res => {
console.log('开屏广告播放进度更新 = ' + res)
})
5.5 辅助方法
import { isAdReady, resetAd } from '@/uni_modules/admob-uts'
isAdReady('rewarded') // 是否有已加载待播放的激励视频
resetAd('rewarded') // 清空已缓存的激励视频(切换广告位 ID 时使用)
format 取值:rewarded / interstitial / rewardedInterstitial / appOpen。
5.6 Banner 横幅广告
Banner 为原生组件,仅支持 nvue / uvue 页面。
<admob-banner ref="admobBanner" @eventCallBack="eventCallBack"
style="width:375px;height:100px;background-color:aqua;"></admob-banner>
// 加载广告:参数为(广告位 ID, 宽, 高),宽高单位为 px,需与 style 中保持一致
this.$refs['admobBanner'].loadAD('ca-app-pub-3940256099942544/9214589741', 375, 100)
eventCallBack(res) {
console.log(res.detail) // JSON 字符串,与上述回调格式一致
}
Banner 事件码沿用统一事件码(100 加载成功、101 曝光、102 点击、103 关闭、104 加载失败、105 打开、106 滑动点击),
其中 105/106 仅 Banner 组件会产生。
六、常见问题
-
xxx not found:先确认@/uni_modules/admob-uts路径正确,且已使用自定义基座(标准基座不含本插件)。 -
加载失败且
msg提示 Application ID 缺失:检查项目根目录的AndroidManifest.xml/Info.plist是否配置了 App ID。 -
当前无可用 Activity(Android):请勿在onLaunch阶段立即调用,等页面onReady之后再调用。 -
激励视频没有回调
code == 105:用户未完整观看(提前关闭)时不会发放奖励,属正常行为;请仅在105时发放奖励。 -
报
callback回调函数已释放,不能再次执行(两端都可能): HBuilderX 4.25 起,UTS 插件导出方法里的回调参数改为「触发一次后立即自动回收」。 本插件的回调天然要触发多次(107 已展示→101 曝光→102 点击→105 激励发放→103 关闭), 所以两端实现里的导出方法都带了@UTSJS.keepAlive装饰器 (官方文档)。 改动代码时不要删这个装饰器,也不要把export function改回export const xxx: AdMobLoad = ...—— 装饰器不支持那种导出写法。注意 app-android / app-ios 两端都要配,少一端就会在那一端复现。 该装饰器会让回调常驻内存,因此请勿高频调用 load/show(按需加载即可)。 -
Android 报「激励视频加载异常」:所有 SDK 调用都已切到主线程 (
UTSAndroid.getDispatcher("main").async(...),Google Mobile Ads SDK 要求在主线程初始化)。 若仍失败,用adb logcat | grep admob-uts看完整异常堆栈 —— 详情只写日志、不放回调文案, 因为 UTS 里catch (e)的e是any,不能取属性、也不能参与字符串拼接。 -
iOS 报
激励视频加载失败…:Request Error: Invalid request.(先别改代码,八成不是代码问题): iOS 实现会把诊断信息一起回给 JS(adUnitId/appId/initStatus/netProbe/sdklog),照着看即可:netProbe: googleads.g.doubleclick.net=失败(TLS错误导致安全连接失败。)⇒ 设备网络连不上 Google 广告服务,SDK 拉不到初始化配置 (initStatus里GADMobileAds会是Not Ready: Could not retrieve application configuration data.), 请求在本地就被判为无效。此时detail里的Response ID必然是(null)、Adapter Response 为空。 修法在网络层(开代理 / 换网络),与本插件代码、广告位、App ID 都无关。- 中国大陆网络环境下 iOS 端 AdMob 无法稳定变现(TLS 会被阻断), 面向国内用户的 App 建议 iOS 走国内广告联盟,或只对可访问 Google 的地区投放。
appId=显示(未配置)⇒ 基座里的 Info.plist 没生效;initStatus里出现 Not Ready ⇒ 初始化没成功。sdklog字段是用OSLogStore读本进程 os_log(iOS 15+)拿到的 Google SDK 自身日志 —— 这些行默认只在 Xcode/设备日志里,这样就能直接在 HBuilderX 控制台看到。- 广告位必须按平台取:iOS 与 Android 的测试广告位不通用(见上文对照表),
两端不要共用一个常量;经典 uni-app 的 vue 服务层里
#ifdef APP-IOS不生效, 要用uni.getSystemInfoSync().platform做运行时判断。
七、最简可运行 Demo
不想读文档、只想先跑起来看效果的话,把下面这份代码整段覆盖到 pages/index/index.vue,
用自定义基座跑到手机上就能出测试广告。它覆盖了插件全部能力,
也把「激励视频优先 + 插屏式激励视频兜底」这个最容易踩坑的组合写全了。
跑之前确认三件事(缺一跑不出来):
- 项目根目录的
AndroidManifest.xml/Info.plist已填好 App ID(测试阶段用 Google 官方测试 App ID 即可); - 用自定义基座运行,标准基座不含本插件,会报
admob-uts not found; - 别在
onLaunch阶段调用,等页面onLoad之后再调(Android 需要 Activity 就绪)。
预期表现:进页面后三个(iOS 四个)状态点陆续变绿=广告加载成功;点「看广告解锁」会弹出全屏激励视频; 点「加载 / 展示」可单独测试每种广告;页面底部实时打印插件回调的原始事件码。
<template>
<view class="page">
<view class="header">
<view class="title">AdMob UTS 插件 · 最简 Demo</view>
<view class="sub">当前平台:{{ platformText }}(广告位已按平台自动切换)</view>
</view>
<!-- 广告位就绪状态 -->
<view class="card">
<view class="card-title">广告位状态</view>
<view class="status-row" v-for="item in adList" :key="item.format">
<view class="status-name">{{ item.name }}</view>
<view class="dot" :class="ready[item.format] ? 'dot-on' : 'dot-off'"></view>
<view class="status-text" :class="ready[item.format] ? 'text-on' : 'text-off'">
{{ ready[item.format] ? '已就绪' : '未就绪' }}
</view>
</view>
</view>
<!-- 场景一:看广告解锁 -->
<view class="card">
<view class="card-title">场景一 · 看广告解锁(激励视频优先,插屏式激励兜底)</view>
<view class="unlock-text" :class="unlocked ? 'text-on' : 'text-off'">
{{ unlocked ? '第 1 集已解锁' : '第 1 集未解锁,需完整观看激励类广告' }}
</view>
<view class="btn btn-primary" :class="{ 'btn-disabled': unlocking }" @click="adUnlock">
{{ unlocking ? '广告播放中…' : '看广告解锁' }}
</view>
</view>
<!-- 场景二:单独加载 / 展示 -->
<view class="card">
<view class="card-title">场景二 · 单独加载 / 展示</view>
<view class="ad-row" v-for="item in adList" :key="item.format">
<view class="ad-name">{{ item.name }}</view>
<view class="btn btn-mini" @click="loadAd(item.format)">加载</view>
<view class="btn btn-mini btn-primary" :class="{ 'btn-disabled': !ready[item.format] }"
@click="showAd(item.format)">展示</view>
</view>
</view>
<!-- 回调日志 -->
<view class="card">
<view class="card-title">
<text>回调日志(最新在最上面)</text>
<text class="clear-btn" @click="logs = []">清空</text>
</view>
<scroll-view class="log-box" scroll-y>
<view class="log-line" v-for="line in logs" :key="line.id">{{ line.text }}</view>
<view class="log-empty" v-if="!logs.length">暂无日志,点上面的按钮试试</view>
</scroll-view>
</view>
</view>
</template>
<script>
import {
loadAppOpenAD,
showAppOpenAD,
loadRewardedAd,
showRewardedAd,
loadInterstitialAd,
showInterstitialAd,
loadRewardedInterstitialAd,
showRewardedInterstitialAd
} from '@/uni_modules/admob-uts'
// ------------------------------------------------------------------ 平台与广告位
//
// ⚠️ 用运行时判断,不要用 `#ifdef APP-IOS` / `#ifdef APP-ANDROID`。
// 经典 uni-app 的 vue 服务层两端共用同一份 app-service.js,
// 条件编译分支会被整体丢弃,写了等于没写。
const IS_IOS = (() => {
try {
return uni.getSystemInfoSync().platform === 'ios'
} catch (e) {
return false
}
})()
// 测试广告位:Google 官方测试 ID,上线前整体替换成自己后台的正式广告位。
// ⚠️ 两端广告位**不通用**,必须按平台取;用错平台会返回
// 「Request Error: Invalid request.」,广告永远拉不起来。
const AD_UNIT_IDS = {
rewarded: IS_IOS ? 'ca-app-pub-3940256099942544/1712485313' : 'ca-app-pub-3940256099942544/5224354917',
rewardedInterstitial: IS_IOS ? 'ca-app-pub-3940256099942544/6978759866' : 'ca-app-pub-3940256099942544/5354046379',
interstitial: IS_IOS ? 'ca-app-pub-3940256099942544/4411468910' : 'ca-app-pub-3940256099942544/1033173712',
appOpen: IS_IOS ? 'ca-app-pub-3940256099942544/5575463023' : 'ca-app-pub-3940256099942544/9257395921'
}
/** 事件码,与插件 utssdk/interface.uts 中的定义一致 */
const CODE = {
LOADED: 100,
IMPRESSION: 101,
CLICK: 102,
CLOSED: 103,
LOAD_FAILED: 104,
REWARD: 105,
SHOW_FAILED: 106,
OPENED: 107
}
const CODE_NAME = {
100: '加载成功',
101: '曝光',
102: '点击',
103: '关闭',
104: '加载失败',
105: '激励发放',
106: '展示失败',
107: '已展示'
}
/** 全部广告位,Android 上会过滤掉开屏广告(插件在 Android 端直接回调失败) */
const ALL_ADS = [
{ format: 'rewarded', name: '激励视频' },
{ format: 'rewardedInterstitial', name: '插屏式激励视频' },
{ format: 'interstitial', name: '插屏广告' },
{ format: 'appOpen', name: '开屏广告(仅 iOS)' }
]
const pad2 = (n) => (n < 10 ? '0' + n : '' + n)
export default {
data() {
return {
platformText: IS_IOS ? 'iOS' : 'Android',
// 每个广告位的加载状态:true = 已加载好,可以展示
ready: {
rewarded: false,
rewardedInterstitial: false,
interstitial: false,
appOpen: false
},
unlocking: false, // 「看广告解锁」流程进行中,防重复点击
unlocked: false, // 演示用的解锁结果
pendingUnlock: false, // 本次广告播放是否在等解锁奖励
rewardGranted: false, // 本次播放是否已发放奖励(只有 code=105 才置 true)
rewardFormat: '', // 当前播放的是哪个激励类广告位
autoShowAppOpen: IS_IOS, // iOS:开屏广告加载完成后自动展示一次
logs: [], // 回调日志
logId: 0
}
},
computed: {
adList() {
// Android 不支持开屏广告,直接从列表里去掉,避免误导
return IS_IOS ? ALL_ADS : ALL_ADS.filter((item) => item.format !== 'appOpen')
}
},
onLoad() {
// 广告是异步加载的,提前预加载好才能在需要时立即展示
this.loadAd('rewarded') // 广告解锁优先使用
this.loadAd('rewardedInterstitial') // 广告解锁兜底
this.loadAd('interstitial') // 切集插屏
if (IS_IOS) {
this.loadAd('appOpen') // 开屏广告仅 iOS 支持
}
},
methods: {
// ---------------------------------------------------------- 通用工具
log(text) {
const now = new Date()
const time = pad2(now.getHours()) + ':' + pad2(now.getMinutes()) + ':' + pad2(now.getSeconds())
this.logId++
this.logs.unshift({ id: this.logId, text: '[' + time + '] ' + text })
if (this.logs.length > 60) this.logs.pop()
console.log('[admob-demo]', text)
},
// 插件所有回调都是 JSON 字符串,这里统一解析;顺手兼容对象形式
parse(res) {
if (res && typeof res === 'object') return res
try {
return JSON.parse(res)
} catch (e) {
return {}
}
},
codeName(code) {
return CODE_NAME[code] || '未知事件 ' + code
},
// ---------------------------------------------------------- 加载 / 展示
/**
* 加载广告
* @param {String} format rewarded / rewardedInterstitial / interstitial / appOpen
* @param {Function} callback 可选,参数 ready:Boolean
*/
loadAd(format, callback) {
if (format === 'appOpen' && !IS_IOS) {
this.log('开屏广告仅 iOS 支持,Android 跳过')
if (typeof callback === 'function') callback(false)
return
}
const options = { adid: AD_UNIT_IDS[format] }
const done = (res) => {
const resJson = this.parse(res)
const ok = resJson.errorCode === 0
this.ready[format] = ok
this.log(format + ' 加载' + (ok ? '成功' : '失败') + (resJson.msg ? ':' + resJson.msg : ''))
if (typeof callback === 'function') callback(ok)
// iOS 开屏广告:加载完成后自动展示一次,模拟真实「开屏」场景
if (ok && format === 'appOpen' && this.autoShowAppOpen) {
this.autoShowAppOpen = false
this.showAd('appOpen')
}
}
if (format === 'rewarded') {
loadRewardedAd(options, done)
} else if (format === 'rewardedInterstitial') {
loadRewardedInterstitialAd(options, done)
} else if (format === 'interstitial') {
loadInterstitialAd(options, done)
} else {
loadAppOpenAD(options, done)
}
},
/**
* 展示广告(一次性消耗品:展示后必须重新 load 才能再次播放)
* @param {String} format 同上
*/
showAd(format) {
if (!this.ready[format]) {
uni.showToast({ title: '广告还没加载好,先点「加载」', icon: 'none' })
return
}
// 激励类广告每次播放前重置奖励标记,只有收到 code=105 才算发放
if (format === 'rewarded' || format === 'rewardedInterstitial') {
this.rewardGranted = false
this.rewardFormat = format
}
this.ready[format] = false // 播放后即被消耗,关闭/失败时再重新加载
const options = {
adid: AD_UNIT_IDS[format],
// customData 会在激励发放时原样返回,真实业务里一般传剧集 ID
customData: 'demo-episode-1'
}
const onEvent = (res) => this.onAdEvent(format, res)
if (format === 'rewarded') {
showRewardedAd(options, onEvent)
} else if (format === 'rewardedInterstitial') {
showRewardedInterstitialAd(options, onEvent)
} else if (format === 'interstitial') {
showInterstitialAd(options, onEvent)
} else {
showAppOpenAD(options, onEvent)
}
},
/**
* 播放过程中的事件统一处理
* 100 加载成功 / 101 曝光 / 102 点击 / 103 关闭 / 104 加载失败 / 105 激励发放 /
* 106 展示失败 / 107 已展示
*/
onAdEvent(format, res) {
const resJson = this.parse(res)
const code = resJson.code
this.log(format + ' → code=' + code + '(' + this.codeName(code) + ')' +
(resJson.msg ? ' ' + resJson.msg : ''))
// 激励发放:只有这个事件码才能给用户发奖励
if (code === CODE.REWARD) {
this.rewardGranted = true
this.log('激励发放完成' + (resJson.data ? ',customData=' + resJson.data : ''))
if (this.pendingUnlock) {
this.pendingUnlock = false
this.unlocking = false
this.unlocked = true
uni.showToast({ title: '解锁成功', icon: 'none' })
}
return
}
// 广告关闭:重新预加载下一次
if (code === CODE.CLOSED) {
if (this.pendingUnlock && !this.rewardGranted) {
// 没看完就关了,不发奖励,属正常行为
this.pendingUnlock = false
this.unlocking = false
uni.showToast({ title: '未完整观看,未发放奖励', icon: 'none' })
}
this.loadAd(format)
return
}
// 展示失败 / 加载失败
if (code === CODE.SHOW_FAILED || resJson.errorCode !== 0) {
if (this.pendingUnlock) {
this.pendingUnlock = false
this.unlocking = false
uni.showToast({ title: resJson.msg || '广告展示失败', icon: 'none' })
}
this.loadAd(format)
}
},
// ---------------------------------------------------------- 场景一
// 看广告解锁:激励视频优先,拉不起来时用插屏式激励视频兜底
adUnlock() {
if (this.unlocking) return
this.unlocking = true
this.unlocked = false
this.pendingUnlock = true
this.log('开始「看广告解锁」流程…')
this.ensureRewardAd((format) => {
if (!format) {
this.pendingUnlock = false
this.unlocking = false
this.log('两种激励类广告都没能加载出来')
uni.showToast({ title: '广告暂不可用,请稍后重试', icon: 'none' })
return
}
this.log('准备播放:' + format)
this.showAd(format)
})
},
/**
* 确保有一个可播放的激励类广告
* @param {Function} callback 参数为可播放的 format,都没有则回传空字符串
*/
ensureRewardAd(callback) {
if (this.ready.rewarded) {
callback('rewarded')
return
}
if (this.ready.rewardedInterstitial) {
callback('rewardedInterstitial')
return
}
// 都没就绪,先补一次加载:激励视频 → 插屏式激励视频
this.loadAd('rewarded', (ok) => {
if (ok) {
callback('rewarded')
return
}
this.loadAd('rewardedInterstitial', (ok2) => {
callback(ok2 ? 'rewardedInterstitial' : '')
})
})
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background-color: #f2f3f5;
padding: 24rpx 24rpx 60rpx;
box-sizing: border-box;
}
.header {
padding: 16rpx 8rpx 24rpx;
}
.title {
font-size: 40rpx;
font-weight: bold;
color: #1f2329;
}
.sub {
margin-top: 8rpx;
font-size: 24rpx;
color: #8a8f99;
}
.card {
background-color: #ffffff;
border-radius: 16rpx;
padding: 28rpx;
margin-bottom: 24rpx;
}
.card-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 28rpx;
font-weight: bold;
color: #1f2329;
margin-bottom: 20rpx;
}
.clear-btn {
font-size: 24rpx;
font-weight: normal;
color: #8a8f99;
}
/* 广告位状态 */
.status-row,
.ad-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
}
.status-row:last-child,
.ad-row:last-child {
margin-bottom: 0;
}
.status-name,
.ad-name {
flex: 1;
font-size: 26rpx;
color: #4b5058;
}
.dot {
width: 14rpx;
height: 14rpx;
border-radius: 50%;
margin-right: 10rpx;
}
.dot-on {
background-color: #07c160;
}
.dot-off {
background-color: #c9cdd4;
}
.status-text {
width: 110rpx;
text-align: right;
font-size: 24rpx;
}
.text-on {
color: #07c160;
}
.text-off {
color: #8a8f99;
}
/* 解锁区 */
.unlock-text {
font-size: 26rpx;
margin-bottom: 20rpx;
}
/* 按钮 */
.btn {
display: flex;
align-items: center;
justify-content: center;
height: 72rpx;
border-radius: 12rpx;
font-size: 28rpx;
background-color: #f2f3f5;
color: #1f2329;
}
.btn-primary {
background-color: #1677ff;
color: #ffffff;
}
.btn-mini {
width: 120rpx;
height: 60rpx;
font-size: 26rpx;
margin-left: 16rpx;
}
.btn-disabled {
opacity: 0.45;
}
/* 日志 */
.log-box {
height: 460rpx;
background-color: #1f2329;
border-radius: 12rpx;
padding: 16rpx;
box-sizing: border-box;
}
.log-line {
font-size: 22rpx;
line-height: 34rpx;
color: #a6c8ff;
font-family: Menlo, Consolas, monospace;
word-break: break-all;
}
.log-empty {
font-size: 24rpx;
color: #6b7280;
}
</style>
7.1 这份 Demo 里值得抄走的几条规则
| 规则 | 原因 |
|---|---|
广告位用 IS_IOS 运行时三元取,不用条件编译 |
经典 uni-app 的 vue 服务层两端同一份 app-service.js,#ifdef 会被丢弃 |
奖励只在 code == 105 时发放 |
提前关闭(103)不会给 105,这是正常行为,别用「关闭」当成功 |
每次 show* 后都重新 load* |
激励类广告是一次性消耗品,不重新加载第二次点就是「加载失败」 |
| 加载失败/展示失败静默处理,不阻塞用户 | 海外网络不稳时广告拉不起来很常见,业务逻辑不能依赖广告成功 |
加载与展示分离(先 ready 再展示) |
load 是异步的,边点边加载必然黑屏或丢广告 |

收藏人数:
购买普通授权版(
试用
使用 HBuilderX 导入示例项目
赞赏(0)
下载 15
赞赏 0
下载 12703542
赞赏 1957
赞赏
京公网安备:11010802035340号