提交 f2622618 编写于 作者: d-u-a's avatar d-u-a

feat: vue3 add ad-fullscreen-video,ad-rewarded-video,ad-interstitial,uniad

上级 a236b9bc
......@@ -12,6 +12,7 @@ declare namespace NodeJS {
UNI_CLI_CONTEXT: string
UNI_SUBPACKAGE?: string
UNI_MP_PLUGIN?: 'true'
UNI_MP_UNIAD?: Boolean
UNI_COMPILER_VERSION: string
UNI_COMPILER_VERSION_TYPE: 'a' | 'r'
UNI_HBUILDERX_PLUGINS: string
......
<template>
<view @click="_onclick">
<slot :options="options" :loading="loading" :error="errorMessage" />
<!-- #ifdef MP-WEIXIN -->
<uniad-plugin class="uniad-plugin" :adpid="adpid" :unit-id="unitId" @load="_onmpload" @close="_onmpclose" @error="_onmperror"></uniad-plugin>
<!-- #endif -->
</view>
</template>
<script>
// #ifndef MP-WEIXIN
import adMixin from "../ad/ad.mixin.js"
// #endif
// #ifdef MP-WEIXIN
import adMixin from "../ad/ad.mixin.mp.js"
// #endif
export default {
name: 'AdFullscreenVideo',
mixins: [adMixin],
props: {
adType: {
type: String,
default: 'FullScreenVideo'
}
}
}
</script>
<template>
<view @click="_onclick">
<slot :options="options" :loading="loading" :error="errorMessage" />
<!-- #ifdef MP-WEIXIN -->
<uniad-plugin class="uniad-plugin" :adpid="adpid" :unit-id="unitId" @load="_onmpload" @close="_onmpclose" @error="_onmperror"></uniad-plugin>
<!-- #endif -->
</view>
</template>
<script>
// #ifndef MP-WEIXIN
import adMixin from "../ad/ad.mixin.js"
// #endif
// #ifdef MP-WEIXIN
import adMixin from "../ad/ad.mixin.mp.js"
// #endif
export default {
name: 'AdInterstitial',
mixins: [adMixin],
props: {
adType: {
type: String,
default: 'Interstitial'
}
}
}
</script>
<template>
<view @click="_onclick">
<slot :options="options" :loading="loading" :error="errorMessage" />
<!-- #ifdef MP-WEIXIN -->
<uniad-plugin class="uniad-plugin" :adpid="adpid" :unit-id="unitId" @load="_onmpload" @close="_onmpclose" @error="_onmperror"></uniad-plugin>
<!-- #endif -->
</view>
</template>
<script>
// #ifndef MP-WEIXIN
import adMixin from "../ad/ad.mixin.js"
// #endif
// #ifdef MP-WEIXIN
import adMixin from "../ad/ad.mixin.mp.js"
// #endif
export default {
name: 'AdRewardedVideo',
mixins: [adMixin],
props: {
adType: {
type: String,
default: 'RewardedVideo'
}
}
}
</script>
const ADType = {
RewardedVideo: 'RewardedVideo',
FullScreenVideo: 'FullScreenVideo',
Interstitial: 'Interstitial'
}
const EventType = {
Load: 'load',
Close: 'close',
Error: 'error'
}
const EXPIRED_TIME = 1000 * 60 * 30
const ProviderType = {
CSJ: 'csj',
GDT: 'gdt'
}
const RETRY_COUNT = 1
class AdBase {
constructor (adInstance, options = {}) {
this._isLoad = false
this._isLoading = false
this._isPlaying = false
this._lastLoadTime = 0
this._lastError = null
this._retryCount = 0
if (options.retry !== undefined) {
this._retry = options.retry
} else {
this._retry = true
}
this._loadCallback = null
this._closeCallback = null
this._errorCallback = null
const ad = this._ad = adInstance
ad.onLoad((e) => {
this._isLoading = false
this._isLoad = true
this._lastLoadTime = Date.now()
this.onLoad()
})
ad.onClose((e) => {
this._isLoad = false
this._isPlaying = false
this.onClose(e)
})
ad.onVerify && ad.onVerify((e) => {
// e.isValid
})
ad.onError(({
code,
message
}) => {
this._isLoading = false
const data = {
code: code,
errMsg: message
}
if (this._retry && code === -5008) {
this._loadAd()
return
}
if (this._retry && this._retryCount < RETRY_COUNT) {
this._retryCount += 1
this._loadAd()
return
}
this._lastError = data
this.onError(data)
})
}
get isExpired () {
return (this._lastLoadTime !== 0 && (Math.abs(Date.now() - this._lastLoadTime) > EXPIRED_TIME))
}
get isLoad () {
return this._isLoad
}
get isLoading () {
return this._isLoading
}
getProvider () {
return this._ad.getProvider()
}
load (onload, onerror) {
this._loadCallback = onload
this._errorCallback = onerror
if (this._isPlaying) {
onerror && onerror()
return
}
if (this._isLoading) {
return
}
if (this._isLoad) {
this.onLoad()
return
}
this._retryCount = 0
this._loadAd()
}
show (onclose, onshow) {
this._closeCallback = onclose
if (this._isLoading || this._isPlaying || !this._isLoad) {
return
}
if (this._lastError !== null) {
this.onError(this._lastError)
return
}
const provider = this.getProvider()
if (provider === ProviderType.CSJ && this.isExpired) {
if (this._retry) {
this._loadAd()
} else {
this.onError(this._lastError)
}
return
}
this._isPlaying = true
this._ad.show()
onshow && onshow()
}
onLoad (e) {
if (this._loadCallback != null) {
this._loadCallback()
}
}
onClose (e) {
if (this._closeCallback != null) {
this._closeCallback({
isEnded: e.isEnded
})
}
}
onError (e) {
if (this._errorCallback != null) {
this._errorCallback(e)
}
}
destroy () {
this._ad.destroy()
}
_loadAd () {
this._isLoad = false
this._isLoading = true
this._lastError = null
this._ad.load()
}
}
class RewardedVideo extends AdBase {
constructor (options = {}) {
super(plus.ad.createRewardedVideoAd(options), options)
}
}
class FullScreenVideo extends AdBase {
constructor (options = {}) {
super(plus.ad.createFullScreenVideoAd(options), options)
}
}
class Interstitial extends AdBase {
constructor (options = {}) {
super(plus.ad.createInterstitialAd(options), options)
}
}
class AdHelper {
constructor (adType) {
this._ads = {}
this._adType = adType
this._lastWaterfallIndex = -1
}
load (options, onload, onerror) {
if (!options.adpid || this.isBusy(options.adpid)) {
return
}
this.get(options).load(onload, onerror)
}
show (options, onload, onerror, onclose, onshow) {
const ad = this.get(options)
if (ad.isLoad) {
this._lastWaterfallIndex = -1
ad.show((e) => {
onclose && onclose(e)
}, () => {
onshow && onshow()
})
} else {
ad.load(() => {
this._lastWaterfallIndex = -1
onload && onload()
ad.show((e) => {
onclose && onclose(e)
}, () => {
onshow && onshow()
})
}, (err) => {
onerror && onerror(err)
})
}
}
// 底价预载逻辑
loadWaterfall (options, onload, onfail, index = 0) {
const {
adpid,
urlCallback
} = options
if (!Array.isArray(adpid)) {
return
}
const options2 = {
adpid: adpid[index],
urlCallback,
retry: false
}
console.log('ad.loadWaterfall::index=' + index)
this.load(options2, (res) => {
this._lastWaterfallIndex = index
onload(options2)
}, (err) => {
index++
if (index >= adpid.length) {
onfail(err)
} else {
this.loadWaterfall(options, onload, onfail, index)
}
})
}
// 底价逻辑,失败后下一个,无重试机制
showWaterfall (options, onload, onfail, onclose, onshow, index = 0) {
const {
adpid,
urlCallback
} = options
if (!Array.isArray(adpid)) {
return
}
if (this._lastWaterfallIndex > -1) {
index = this._lastWaterfallIndex
}
const options2 = {
adpid: adpid[index],
urlCallback,
retry: false
}
console.log('ad.showWaterfall::index=' + index)
this.show(options2, () => {
onload()
}, (err) => {
index++
if (index >= adpid.length) {
onfail(err)
} else {
this.showWaterfall(options, onload, onfail, onclose, onshow, index)
}
}, (res) => {
onclose(res)
}, () => {
onshow()
})
}
// 预载底价瀑布流
preloadWaterfall (options, index = 0, step = 1) {
if (step === 1) {
this.loadWaterfall(options, (res) => {
console.log('preloadWaterfall.success::', res)
}, (err) => {
console.log('loadWaterfall.fail', err)
})
return
}
const {
adpid,
urlCallback
} = options
for (let i = 0; i < step; i++) {
if (index < adpid.length) {
const options2 = {
adpid: adpid[index],
urlCallback
}
this.loadWaterfall(options2, (res) => {
console.log('preloadWaterfall.success::', res)
}, (err) => {
console.log('loadWaterfall.fail', err)
this.preloadWaterfall(options, index, step)
})
index++
} else {
break
}
}
}
isBusy (adpid) {
return (this._ads[adpid] && this._ads[adpid].isLoading)
}
get (options) {
const {
adpid
} = options
if (!this._ads[adpid]) {
this._ads[adpid] = this._createInstance(options)
}
return this._ads[adpid]
}
getProvider (adpid) {
if (this._ads[adpid]) {
return this._ads[adpid].getProvider()
}
return null
}
remove (adpid) {
if (this._ads[adpid]) {
this._ads[adpid].destroy()
delete this._ads[adpid]
}
}
_createInstance (options) {
const adType = options.adType || this._adType
delete options.adType
let ad = null
if (adType === ADType.RewardedVideo) {
ad = new RewardedVideo(options)
} else if (adType === ADType.FullScreenVideo) {
ad = new FullScreenVideo(options)
} else if (adType === ADType.Interstitial) {
ad = new Interstitial(options, true)
}
return ad
}
}
export default {
props: {
options: {
type: [Object, Array],
default () {
return {}
}
},
disabled: {
type: [Boolean, String],
default: false
},
adpid: {
type: [Number, String, Array],
default: ''
},
preload: {
type: [Boolean, String],
default: true
},
loadnext: {
type: [Boolean, String],
default: false
},
urlCallback: {
type: Object,
default () {
return {}
}
}
},
data () {
return {
loading: false,
errorMessage: null
}
},
created() {
this.$watch('adpid', (newValue, oldValue) => {
this._removeInstance(oldValue)
if (this.preload) {
this._loadAd()
}
})
// 服务器回调透传参数,仅在创建广告实例时可传递参数,如果发生变化需要重新创建广告实例
this.$watch('urlCallback', () => {
this._removeInstance()
})
this._adHelper = new AdHelper(this.adType)
setTimeout(() => {
if (this.preload) {
this._loadAd()
}
}, 100)
},
methods: {
load () {
if (this.isLoading) {
return
}
this._startLoading()
const invoke = this._isWaterfall() ? 'loadWaterfall' : 'load'
this._adHelper[invoke](this._getAdOptions(), () => {
this._onLoad()
}, (err) => {
this._onLoadFail(err)
})
},
show () {
if (this.isLoading) {
return
}
this._startLoading()
const invoke = this._isWaterfall() ? 'showWaterfall' : 'show'
this._adHelper[invoke](this._getAdOptions(), () => {
this._onLoad()
}, (err) => {
this._onLoadFail(err)
}, (res) => {
this._dispatchEvent(EventType.Close, res)
if (this.loadnext) {
this.load()
}
}, () => {
// show
this.loading = false
})
},
getProvider () {
if (Array.isArray(this.adpid)) {
return null
}
return this._adHelper.getProvider(this.adpid)
},
_loadAd () {
if (this._canCreateAd()) {
this.load()
}
},
_onclick () {
if (!this.disabled) {
this.show()
}
},
_getAdOptions () {
return {
adpid: this.adpid,
urlCallback: this.urlCallback
}
},
_isWaterfall () {
return (Array.isArray(this.adpid) && this.adpid.length > 0)
},
_canCreateAd () {
let result = false
if (Array.isArray(this.adpid) && this.adpid.length > 0) {
result = true
} else if (typeof this.adpid === 'string' && this.adpid.length > 0) {
result = true
} else if (typeof this.adpid === 'number') {
result = true
}
return result
},
_removeInstance (adpid) {
const id = adpid || this.adpid
if (Array.isArray(id)) {
id.forEach((item) => {
this._adHelper.remove(item)
})
} else if (id) {
this._adHelper.remove(id)
}
},
_startLoading () {
this.loading = true
this.errorMessage = null
},
_onLoad () {
this.loading = false
this._dispatchEvent(EventType.Load, {})
},
_onLoadFail (err) {
this.loading = false
this.errorMessage = JSON.stringify(err)
this._dispatchEvent(EventType.Error, err)
},
_dispatchEvent (type, data) {
this.$emit(type, {
detail: data
})
}
}
}
const EventType = {
Load: 'load',
Close: 'close',
Error: 'error'
}
export default {
props: {
options: {
type: [Object, Array],
default () {
return {}
}
},
adpid: {
type: [Number, String],
default: ''
},
unitId: {
type: [Number, String],
default: ''
},
preload: {
type: [Boolean, String],
default: true
},
loadnext: {
type: [Boolean, String],
default: false
}
},
data () {
return {
loading: false,
errorMessage: null
}
},
created () {
this._ad = null
setTimeout(() => {
if (this.preload && this._canCreateAd()) {
this.load()
}
}, 100)
},
methods: {
load () {
if (this.loading) {
return
}
this._startLoading()
setTimeout(() => {
this.loading = false
}, 3000)
},
show () {
this.errorMessage = null
this._ad = this.selectComponent('.uniad-plugin')
this._ad.show()
},
_onclick () {
this.show()
},
_startLoading () {
this.loading = true
this.errorMessage = null
},
_canCreateAd () {
let result = false
if (typeof this.adpid === 'string' && this.adpid.length > 0) {
result = true
} else if (typeof this.adpid === 'number') {
result = true
}
return result
},
_onmpload (e) {
this.loading = false
this._dispatchEvent(EventType.Load, {})
},
_onmpclose (e) {
this._dispatchEvent(EventType.Close, e.detail)
},
_onmperror (e) {
this.loading = false
this.errorMessage = JSON.stringify(e.detail)
this._dispatchEvent(EventType.Error, e.detail)
},
_dispatchEvent (type, data) {
this.$emit(type, {
detail: data
})
}
}
}
<template>
<view @click="onclick">
<uniad-plugin
class="uniad-plugin"
:adpid="adpid"
:unit-id="unitId"
@load="_onmpload"
@close="_onmpclose"
@error="_onmperror"
/>
</view>
</template>
<script>
import adMixin from '../ad/ad.mixin.mp.js'
export default {
name: 'Uniad',
mixins: [adMixin]
}
</script>
......@@ -8,6 +8,9 @@ import {
transformRef,
} from '@dcloudio/uni-cli-shared'
import { UniMiniProgramPluginOptions } from '@dcloudio/uni-mp-vite'
import { transformAd } from './transforms/transformAd'
import uniadAppJson from './uniad.app.json'
import source from './project.config.json'
......@@ -19,7 +22,7 @@ export const customElements = [
]
export const compilerOptions: CompilerOptions = {
nodeTransforms: [transformRef, transformComponentLink],
nodeTransforms: [transformRef, transformComponentLink, transformAd],
}
const COMPONENTS_DIR = 'wxcomponents'
......@@ -97,6 +100,12 @@ export const options: UniMiniProgramPluginOptions = {
darkmode: true,
subpackages: true,
plugins: true,
normalize(appJson) {
if ((process.env.UNI_MP_UNIAD = true)) {
uniadAppJson(appJson)
}
return appJson
},
},
project: {
filename: projectConfigFilename,
......
import { isElementNode } from '@dcloudio/uni-cli-shared'
import {
findProp,
RootNode,
ElementTypes,
TemplateChildNode,
TransformContext,
} from '@vue/compiler-core'
const AD_COMPONENTS: Array<string> = [
'uniad',
'ad-rewarded-video',
'ad-fullscreen-video',
'ad-interstitial',
]
export function transformAd(
node: RootNode | TemplateChildNode,
context: TransformContext
) {
if (!isElementNode(node)) {
return
}
const adpidProp = findProp(node, 'adpid')
if (node.tag === 'ad' && adpidProp) {
node.tag = 'uniad'
node.tagType = ElementTypes.COMPONENT
}
if (AD_COMPONENTS.indexOf(node.tag) > -1) {
process.env.UNI_MP_UNIAD = true
}
}
module.exports = function (appJson) {
if (!appJson.plugins) {
appJson.plugins = {}
}
if (!appJson.plugins['uni-ad']) {
appJson.plugins['uni-ad'] = {
version: '1.0.3',
provider: 'wx999bf02c8e05dfc9',
}
}
if (!appJson.plugins['coral-adv']) {
appJson.plugins['coral-adv'] = {
version: '1.0.7',
provider: 'wx0e203209e27b1e66',
}
}
if (!appJson.usingComponents) {
appJson.usingComponents = {}
}
if (!appJson.usingComponents['uniad-plugin']) {
appJson.usingComponents['uniad-plugin'] = 'plugin://uni-ad/ad'
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册