初始化

This commit is contained in:
yziiy
2025-08-11 11:06:07 +08:00
parent 083bc37c00
commit 5607d11395
19772 changed files with 3108723 additions and 18 deletions

View File

@@ -0,0 +1,28 @@
<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>

View File

@@ -0,0 +1,151 @@
<template>
<view @click="onclick">
<slot :options="options" :data="adData" :loading="loading" :error="errorMessage" />
</view>
</template>
<script>
const AD_URL = 'https://wxac1.dcloud.net.cn/openPage/acs'
const AD_REPORT_URL = 'https://wxac1.dcloud.net.cn/openPage/acs'
//const WEBVIEW_PATH = '/uni_modules/uni-ad-interactive/pages/uni-ad-interactive/uni-ad-interactive'
const events = {
load: 'load',
close: 'close',
error: 'error'
}
const OpenTypes = {
Interactive: 'interactive'
}
export default {
name: 'AdInteractive',
props: {
options: {
type: [Object, Array],
default () {
return {}
}
},
disabled: {
type: [Boolean, String],
default: false
},
adpid: {
type: [Number, String],
default: ''
},
openType: {
type: String,
default: 'interactive'
},
openPagePath: {
type: String,
default: ""
}
},
data() {
return {
adData: null,
loading: false,
errorMessage: ""
}
},
created() {
this._interactiveUrl = null
if (this.openPagePath) {
this.getAdData()
}
},
methods: {
getAdData() {
if (!this.adpid) {
this.$emit(events.error, {
code: -5002,
message: 'invalid adpid'
})
return
}
this.loading = true
uni.request({
url: AD_URL,
method: 'POST',
data: {
adpid: this.adpid
},
timeout: 5000,
dataType: 'json',
success: (res) => {
if (res.statusCode !== 200) {
this.$emit(events.error, {
errCode: res.statusCode,
errMsg: res.statusCode
})
return
}
const responseData = res.data
if (responseData.ret === 0) {
this._interactiveUrl = responseData.data.adp_url
this.adData = {
imgUrl: responseData.data.icon_url,
openPath: this.openPagePath + '?url=' + encodeURIComponent(this._interactiveUrl)
}
this.$emit(events.load, this.adData)
} else {
const errMsg = {
errCode: responseData.ret,
errMsg: responseData.msg
}
this.errorMessage = errMsg
this.$emit(events.error, errMsg)
}
},
fail: (err) => {
this.$emit(events.error, {
errCode: '',
errMsg: err.errMsg
})
},
complete: () => {
this.loading = false
}
})
},
onclick() {
if (this.disabled) {
return
}
if (!this._interactiveUrl) {
return
}
uni.navigateTo({
url: this.adData.openPath
})
this._report()
},
_report() {
uni.request({
url: AD_REPORT_URL,
data: {
adpid: this.adpid,
t: '10019'
},
timeout: 5000,
dataType: 'json'
})
}
}
}
</script>
<style>
view {
display: block;
}
</style>

View File

@@ -0,0 +1,28 @@
<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>

View File

@@ -0,0 +1,28 @@
<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>

View File

@@ -0,0 +1,556 @@
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
})
}
}
}

View File

@@ -0,0 +1,159 @@
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
},
urlCallback: {
type: Object,
default () {
return {}
}
}
},
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()
},
show () {
this.errorMessage = null
this._ad = this.selectComponent('.uniad-plugin')
if (this._hasCallback()) {
const userCryptoManager = wx.getUserCryptoManager()
userCryptoManager.getLatestUserKey({
success: ({
encryptKey,
iv,
version,
expireTime
}) => {
this._ad.show({
userId: this.urlCallback.userId || '',
extra: this.urlCallback.extra || '',
encryptKey,
iv,
version,
expireTime
})
},
fail: (err) => {
this._dispatchEvent(EventType.Error, err)
}
})
} else {
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
},
_hasCallback () {
return (typeof this.urlCallback === 'object' && Object.keys(this.urlCallback).length > 0)
},
_onmpload (e) {
this.loading = false
this._dispatchEvent(EventType.Load, {})
},
_onmpclose (e) {
this._dispatchEvent(EventType.Close, e.detail)
if (e.detail.adsdata) {
const adv = e.detail.adv
const adsdata = e.detail.adsdata
const version = e.detail.version
/* eslint-disable no-undef */
uniCloud.callFunction({
name: 'uniAdCallback',
data: {
adv: adv,
adsdata: adsdata,
version: version
},
secretType: 'both',
success: (res) => {
},
fail: (err) => {
this._dispatchEvent(EventType.Error, err)
}
})
delete e.detail.adv
delete e.detail.adsdata
delete e.detail.version
}
},
_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
})
}
}
}

View File

@@ -0,0 +1,127 @@
<template>
<uni-tabbar v-if="hasTabBar" v-show="showTabBar">
<div
:style="{
'flex-direction': direction === 'vertical' ? 'column' : 'row',
backgroundColor: tabBar.backgroundColor,
}"
class="uni-tabbar"
>
<template v-for="(item, index) in tabBar.list" :key="item.pagePath">
<div
v-if="item.visible !== false"
class="uni-tabbar__item"
@click="switchTab(item, index)"
>
<div class="uni-tabbar__bd">
<div
v-if="showIcon && item.iconPath"
:class="{ 'uni-tabbar__icon__diff': !item.text }"
class="uni-tabbar__icon"
>
<img
:src="
getRealPath(
selectedIndex === index
? item.selectedIconPath
: item.iconPath
)
"
/>
<div
v-if="item.redDot"
:class="{ 'uni-tabbar__badge': !!item.badge }"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
<div
v-if="item.text"
:style="{
color:
selectedIndex === index ? tabBar.selectedColor : tabBar.color,
fontSize: showIcon && item.iconPath ? '10px' : '14px',
}"
class="uni-tabbar__label"
>
{{ item.text }}
<div
v-if="item.redDot && (!showIcon || !item.iconPath)"
:class="{ 'uni-tabbar__badge': !!item.badge }"
class="uni-tabbar__reddot"
>
{{ item.badge }}
</div>
</div>
</div>
</div>
</template>
</div>
</uni-tabbar>
</template>
<script>
import { computed, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { getRealPath } from '@dcloudio/uni-h5'
import { useTabBar } from '@dcloudio/uni-h5'
export default {
name: 'CustomTabBar',
props: {
selected: {
type: Number,
default: 0
},
showIcon: {
type: Boolean,
default: true
},
direction: {
type: String,
default: 'horizontal'
}
},
setup(props, { emit }) {
const tabBar = useTabBar()
const route = useRoute()
const hasTabBar = computed(() => tabBar.list && tabBar.list.length)
const selectedIndex = ref(props.selected)
watch(() => props.selected, value => selectedIndex.value = value)
watch(() => selectedIndex.value, value => tabBar.selectedIndex = value)
watch(() => {
const meta = route.meta
return [meta.isTabBar, meta.route]
}, ([isTabBar, pagePath]) => {
if (isTabBar) {
const index = tabBar.list.findIndex(item => pagePath === item.pagePath)
if (index > -1) {
selectedIndex.value = index
}
}
})
function switchTab(item, index) {
selectedIndex.value = index
const detail = {
index,
text: item.text,
pagePath: item.pagePath,
}
emit('onTabItemTap', detail)
}
return {
tabBar,
getRealPath,
selectedIndex,
hasTabBar,
showTabBar: true,
switchTab,
}
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,159 @@
<template>
<view style="display: none;" />
</template>
<script>
const attrs = [
'titleIcon',
'titleIconRadius',
'subtitleText',
'subtitleSize',
'subtitleColor',
'subtitleOverflow',
'titleAlign',
'backgroundImage',
'backgroundRepeat',
'blurEffect'
]
export default {
props: {
title: {
type: String,
default: ''
},
titleIcon: {
type: String,
default: ''
},
titleIconRadius: {
type: String,
default: ''
},
subtitleText: {
type: String,
default: ''
},
subtitleSize: {
type: String,
default: ''
},
subtitleColor: {
type: String,
default: ''
},
subtitleOverflow: {
type: String,
default: ''
},
titleAlign: {
type: String,
default: ''
},
backgroundImage: {
type: String,
default: ''
},
backgroundRepeat: {
type: String,
default: ''
},
blurEffect: {
type: String,
default: ''
},
loading: {
type: Boolean,
default: false
},
frontColor: {
type: String,
default: '#ffffff'
},
backgroundColor: {
type: String,
default: '#000000'
},
colorAnimationDuration: {
type: Number,
default: 0
},
colorAnimationTimingFunc: {
type: String,
default: 'linear'
}
},
created () {
const pages = getCurrentPages()
const page = pages[pages.length - 1]
this.__$page = page
this.$watch('title', () => {
this.setNavigationBarTitle()
})
this.$watch('loading', () => {
this.setNavigationBarLoading()
})
this.$watch(() => [
this.frontColor,
this.backgroundColor,
this.colorAnimationDuration,
this.colorAnimationTimingFunc
],
() => {
this.setNavigationBarColor()
})
// #ifdef APP-PLUS
this.__$webview = page.$getAppWebview()
attrs.forEach(key => {
const titleNView = {}
if (this[key] || this[key].length > 0) {
titleNView[key] = this[key]
}
this.setTitleNView(titleNView)
this.$watch(key, (val) => {
const titleStyle = {}
titleStyle[key] = val
this.setTitleNView(titleStyle)
})
})
// #endif
},
beforeMount () {
this.title && this.setNavigationBarTitle()
this.setNavigationBarLoading()
this.setNavigationBarColor()
},
methods: {
setNavigationBarTitle () {
uni.setNavigationBarTitle({
__page__: this.__$page,
title: this.title
})
},
setNavigationBarLoading () {
uni[(this.loading ? 'show' : 'hide') + 'NavigationBarLoading']({
__page__: this.__$page
})
},
setNavigationBarColor () {
uni.setNavigationBarColor({
__page__: this.__$page,
frontColor: this.frontColor,
backgroundColor: this.backgroundColor,
animation: {
duration: this.colorAnimationDuration,
timingFunc: this.colorAnimationTimingFunc
}
})
},
setTitleNView (titleNView) {
const webview = this.__$webview
const style = webview.getStyle()
if (style && style.titleNView) {
webview.setStyle({
titleNView: titleNView
})
}
}
}
}
</script>

View File

@@ -0,0 +1,10 @@
<template>
<teleport to="head">
<slot />
</teleport>
</template>
<script>
export default {
name: 'PageMetaHead',
}
</script>

View File

@@ -0,0 +1,207 @@
<template>
<view style="display: none;">
<slot />
</view>
</template>
<script>
import { onMounted } from 'vue'
// #ifndef H5
import { onResize } from '@dcloudio/uni-app'
// #endif
// #ifdef H5
import { onWindowResize } from '@dcloudio/uni-h5'
// #endif
const scrolldoneEvent = {
type: 'scrolldone',
target: {
id: '',
offsetLeft: 0,
offsetTop: 0,
dataset: {}
},
currentTarget: {
id: '',
offsetLeft: 0,
offsetTop: 0,
dataset: {}
},
detail: {}
}
export default {
name: 'PageMeta',
setup(props, { emit }) {
// #ifndef H5
onResize((evt) => {
emit('resize', evt)
})
// #endif
// #ifdef H5
onMounted(() => {
onWindowResize(evt => {
emit('resize', evt)
})
})
// #endif
// let currentInstance = getCurrentInstance()
// let proxy = currentInstance.proxy
// onPageScroll((evt) => {
// if (proxy._invokePageScrollToFlag === true && evt.scrollTop === proxy._invokeScrollTop) {
// proxy._invokePageScrollToFlag = false
// emit('scrolldone', scrolldoneEvent)
// }
// })
},
props: {
backgroundTextStyle: {
type: String,
default: 'dark',
validator (value) {
return ['dark', 'light'].indexOf(value) !== -1
}
},
backgroundColor: {
type: String,
default: '#ffffff'
},
backgroundColorTop: {
type: String,
default: '#ffffff'
},
backgroundColorBottom: {
type: String,
default: '#ffffff'
},
scrollTop: {
type: String,
default: ''
},
scrollDuration: {
type: Number,
default: 300
},
pageStyle: {
type: String,
default: ''
},
enablePullDownRefresh: {
type: [Boolean, String],
default: false
},
rootFontSize: {
type: String,
default: ''
}
},
created () {
const page = getCurrentPages()[0]
this.$pageVm = page.$vm || page
// #ifdef APP-PLUS
this._currentWebview = page.$getAppWebview()
if (this.enablePullDownRefresh) {
this.setPullDownRefresh(this._currentWebview, true)
}
this.$watch('enablePullDownRefresh', (val) => {
this.setPullDownRefresh(this._currentWebview, val)
})
// #endif
// props
this.$watch('backgroundTextStyle', () => {
this.setBackgroundTextStyle()
})
this.$watch(() => [
this.rootFontSize,
this.pageStyle
], () => {
this.setPageMeta()
})
this.$watch(() => [
this.backgroundColor,
this.backgroundColorTop,
this.backgroundColorBottom
], () => {
this.setBackgroundColor()
})
this.$watch(() => [
this.scrollTop,
this.scrollDuration
], () => {
this.pageScrollTo()
})
// this._invokeScrollTop = -1
// this._invokePageScrollToFlag = false
},
beforeMount () {
this.setBackgroundColor()
if (this.rootFontSize || this.pageStyle) {
this.setPageMeta()
}
this.backgroundTextStyle && this.setBackgroundTextStyle()
},
mounted() {
this.scrollTop && this.pageScrollTo()
},
methods: {
setPullDownRefresh (webview, enabled) {
webview.setStyle({
pullToRefresh: {
support: enabled,
style: plus.os.name === 'Android' ? 'circle' : 'default'
}
})
},
setPageMeta () {
// h5 和 app-plus 设置 rootFontSize
// #ifdef H5 || APP-PLUS
uni.setPageMeta({
pageStyle: this.pageStyle,
rootFontSize: this.rootFontSize
})
// #endif
},
setBackgroundTextStyle () {
// TODO h5 app-plus 暂不支持
// #ifdef MP
uni.setBackgroundTextStyle && uni.setBackgroundTextStyle({
textStyle: this.backgroundTextStyle
})
// #endif
},
setBackgroundColor () {
// TODO h5 app-plus 暂不支持
// #ifdef MP
uni.setBackgroundColor && uni.setBackgroundColor({
backgroundColor: this.backgroundColor,
backgroundColorTop: this.backgroundColorTop,
backgroundColorBottom: this.backgroundColorBottom
})
// #endif
},
pageScrollTo () {
let scrollTop = String(this.scrollTop)
if (scrollTop.indexOf('rpx') !== -1) {
scrollTop = uni.upx2px(scrollTop.replace('rpx', ''))
}
scrollTop = parseFloat(scrollTop)
if (isNaN(scrollTop)) {
return
}
// this._invokeScrollTop = scrollTop
uni.pageScrollTo({
scrollTop,
duration: this.scrollDuration,
success: () => {
//this._invokePageScrollToFlag = true
}
})
}
}
}
</script>

View File

@@ -0,0 +1,73 @@
<template>
<view v-show="matches">
<slot />
</view>
</template>
<script>
let mediaQueryObserver
export default {
name: 'UniMatchMedia',
props: {
width: {
type: [Number, String],
default: ''
},
minWidth: {
type: [Number, String],
default: ''
},
maxWidth: {
type: [Number, String],
default: ''
},
height: {
type: [Number, String],
default: ''
},
minHeight: {
type: [Number, String],
default: ''
},
maxHeight: {
type: [Number, String],
default: ''
},
orientation: {
type: String,
default: ''
}
},
data () {
return {
matches: true
}
},
mounted () {
mediaQueryObserver = uni.createMediaQueryObserver(this)
mediaQueryObserver.observe({
width: this.width,
maxWidth: this.maxWidth,
minWidth: this.minWidth,
height: this.height,
minHeight: this.minHeight,
maxHeight: this.maxHeight,
orientation: this.orientation
}, matches => {
this.matches = matches
})
},
destroyed () {
mediaQueryObserver.disconnect()
}
}
</script>
<style>
view {
display: block;
}
</style>

View File

@@ -0,0 +1,20 @@
<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>

View File

@@ -0,0 +1,6 @@
{
"uniCloud.component.add.success": "Success",
"uniCloud.component.update.success": "Success",
"uniCloud.component.remove.showModal.title": "Tips",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}

View File

@@ -0,0 +1,6 @@
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}

View File

@@ -0,0 +1,6 @@
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}

View File

@@ -0,0 +1,12 @@
import en from './en.json'
import es from './es.json'
import fr from './fr.json'
import zhHans from './zh-Hans.json'
import zhHant from './zh-Hant.json'
export default {
en,
es,
fr,
'zh-Hans': zhHans,
'zh-Hant': zhHant
}

View File

@@ -0,0 +1,6 @@
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否删除该数据"
}

View File

@@ -0,0 +1,6 @@
{
"uniCloud.component.add.success": "新增成功",
"uniCloud.component.update.success": "修改成功",
"uniCloud.component.remove.showModal.title": "提示",
"uniCloud.component.remove.showModal.content": "是否刪除數據"
}

View File

@@ -0,0 +1,632 @@
<template>
<view>
<slot
:options="options"
:data="dataList"
:pagination="paginationInternal"
:loading="loading"
:hasMore="hasMore"
:error="errorMessage"
/>
</view>
</template>
<script>
import { onMounted, getCurrentInstance } from 'vue'
import { ssrRef, shallowSsrRef } from '@dcloudio/uni-app'
import { initVueI18n } from '@dcloudio/uni-i18n'
import messages from './i18n/index'
const isArray = Array.isArray
const { t } = initVueI18n(messages)
const events = {
load: 'load',
error: 'error'
}
const pageMode = {
add: 'add',
replace: 'replace'
}
const loadMode = {
auto: 'auto',
onready: 'onready',
manual: 'manual'
}
const attrs = [
'pageCurrent',
'pageSize',
'collection',
'action',
'field',
'getcount',
'orderby',
'where',
'groupby',
'groupField',
'distinct'
]
export default {
name: 'UniClouddb',
// #ifdef VUE3
setup(props) {
// 单条记录时使用shallowRef仅支持赋值修改列表时采用ref支持push等修改
const dataListRef = props.ssrKey ? (props.getone ? shallowSsrRef(undefined, props.ssrKey) : ssrRef([], props.ssrKey)) : (props.getone ? shallowSsrRef(undefined) : ssrRef([]))
const instance = getCurrentInstance()
onMounted(() => {
// client端判断是否需要再次请求数据正常情况下SSR返回的html中已包含此数据状态无需再次额外请求
if ((!dataListRef.value || dataListRef.value.length === 0) && !props.manual && props.loadtime === loadMode.auto) {
instance.proxy.loadData()
}
})
return { dataList: dataListRef }
},
// 服务端serverPrefetch生命周期用于服务端加载数据等将来全端支持Suspense时可以采用 Suspense + async setup 来实现一版
async serverPrefetch() {
if (!this.manual && this.loadtime === loadMode.auto) {
return this.loadData()
}
},
// #endif
props: {
options: {
type: [Object, Array],
default() {
return {}
}
},
spaceInfo: {
type: Object,
default() {
return {}
}
},
collection: {
type: [String, Array],
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
getone: {
type: [Boolean, String],
default: false
},
gettree: {
type: [Boolean, String, Object],
default: false
},
gettreepath: {
type: [Boolean, String],
default: false
},
startwith: {
type: String,
default: ''
},
limitlevel: {
type: Number,
default: 10
},
groupby: {
type: String,
default: ''
},
groupField: {
type: String,
default: ''
},
distinct: {
type: [Boolean, String],
default: false
},
pageIndistinct: {
type: [Boolean, String],
default: false
},
foreignKey: {
type: String,
default: ''
},
loadtime: {
type: String,
default: 'auto'
},
manual: {
type: Boolean,
default: false
},
ssrKey: {
type: [String, Number],
default: ""
}
},
data() {
return {
loading: false,
hasMore: false,
// #ifndef VUE3
dataList: [],
// #endif
paginationInternal: {},
errorMessage: ''
}
},
computed: {
collectionArgs () {
return isArray(this.collection) ? this.collection : [this.collection]
},
isLookup () {
return (isArray(this.collection) && this.collection.length > 1) || (typeof this.collection === 'string' && this.collection.indexOf(',') > -1)
},
mainCollection () {
if (typeof this.collection === 'string') {
return this.collection.split(',')[0]
}
const mainQuery = JSON.parse(JSON.stringify(this.collection[0]))
return mainQuery.$db[0].$param[0]
}
},
created() {
this._isEnded = false
this.paginationInternal = {
current: this.pageCurrent,
size: this.pageSize,
count: 0
}
// #ifndef VUE3
if (this.getone) {
this.dataList = undefined
}
// #endif
this.$watch(() => {
var al = []
attrs.forEach(key => {
al.push(this[key])
})
return al
}, (newValue, oldValue) => {
this.paginationInternal.size = this.pageSize
if (newValue[0] !== oldValue[0]) {
this.paginationInternal.current = this.pageCurrent
}
if (this.loadtime === loadMode.manual) {
return
}
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] !== oldValue[i]) {
needReset = true
break
}
}
if (needReset) {
this.clear()
this.reset()
}
this._execLoadData()
})
// #ifdef MP-TOUTIAO
let changeName
const events = this.$scope.dataset.eventOpts || []
for (var i = 0; i < events.length; i++) {
const event = events[i]
if (event[0].includes('^load')) {
changeName = event[1][0][0]
}
}
if (changeName) {
let parent = this.$parent
let maxDepth = 16
this._changeDataFunction = null
while (parent && maxDepth > 0) {
const fun = parent[changeName]
if (fun && typeof fun === 'function') {
this._changeDataFunction = fun
maxDepth = 0
break
}
parent = parent.$parent
maxDepth--
}
}
// #endif
},
// #ifndef VUE3
mounted() {
if (!this.manual && this.loadtime === loadMode.auto) {
this.loadData()
}
},
// #endif
methods: {
loadData(args1, args2) {
let callback = null
let clear = false
if (typeof args1 === 'object') {
if (args1.clear) {
if (this.pageData === pageMode.replace) {
this.clear()
} else {
clear = args1.clear
}
this.reset()
}
if (args1.current !== undefined) {
this.paginationInternal.current = args1.current
}
if (typeof args2 === 'function') {
callback = args2
}
} else if (typeof args1 === 'function') {
callback = args1
}
return this._execLoadData(callback, clear)
},
loadMore() {
if (this._isEnded || this.loading) {
return
}
if (this.pageData === pageMode.add) {
this.paginationInternal.current++
}
this._execLoadData()
},
refresh() {
this.clear()
this._execLoadData()
},
clear() {
this._isEnded = false
this.dataList = []
},
reset() {
this.paginationInternal.current = 1
},
add(value, {
action,
showToast = true,
toastTitle,
success,
fail,
complete,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (needLoading) {
uni.showLoading({
title: loadingTitle
})
}
/* eslint-disable no-undef */
let db = uniCloud.database(this.spaceInfo)
if (action) {
db = db.action(action)
}
db.collection(this.mainCollection).add(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
title: toastTitle || t('uniCloud.component.add.success')
})
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
remove(id, {
action,
success,
fail,
complete,
confirmTitle,
confirmContent,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (!id || !id.length) {
return
}
if (!needConfirm) {
this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
return
}
uni.showModal({
title: confirmTitle || t('uniCloud.component.remove.showModal.title'),
content: confirmContent || t('uniCloud.component.remove.showModal.content'),
showCancel: true,
success: (res) => {
if (!res.confirm) {
return
}
this._execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle)
}
})
},
update(id, value, {
action,
showToast = true,
toastTitle,
success,
fail,
complete,
needConfirm = true,
needLoading = true,
loadingTitle = ''
} = {}) {
if (needLoading) {
uni.showLoading({
title: loadingTitle
})
}
let db = uniCloud.database(this.spaceInfo)
if (action) {
db = db.action(action)
}
return db.collection(this.mainCollection).doc(id).update(value).then((res) => {
success && success(res)
if (showToast) {
uni.showToast({
title: toastTitle || t('uniCloud.component.update.success')
})
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
getTemp(isTemp = true) {
let db = uniCloud.database(this.spaceInfo)
if (this.action) {
db = db.action(this.action)
}
db = db.collection(...this.collectionArgs)
if (this.foreignKey) {
db = db.foreignKey(this.foreignKey)
}
if (!(!this.where || !Object.keys(this.where).length)) {
db = db.where(this.where)
}
if (this.field) {
db = db.field(this.field)
}
if (this.groupby) {
db = db.groupBy(this.groupby)
}
if (this.groupField) {
db = db.groupField(this.groupField)
}
if (this.distinct === true) {
db = db.distinct()
}
if (this.orderby) {
db = db.orderBy(this.orderby)
}
const {
current,
size
} = this.paginationInternal
const getOptions = {}
if (this.getcount) {
getOptions.getCount = this.getcount
}
const treeOptions = {
limitLevel: this.limitlevel,
startWith: this.startwith
}
if (this.gettree) {
getOptions.getTree = treeOptions
}
if (this.gettreepath) {
getOptions.getTreePath = treeOptions
}
db = db.skip(size * (current - 1)).limit(size)
if (isTemp) {
db = db.getTemp(getOptions)
db.udb = this
} else {
db = db.get(getOptions)
}
return db
},
setResult(result) {
if (result.code === 0) {
this._execLoadDataSuccess(result)
} else {
this._execLoadDataFail(new Error(result.message))
}
},
_execLoadData(callback, clear) {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
return this._getExec().then((res) => {
this.loading = false
this._execLoadDataSuccess(res.result, callback, clear)
}).catch((err) => {
this.loading = false
this._execLoadDataFail(err, callback)
})
},
_execLoadDataSuccess(result, callback, clear) {
const {
data,
count
} = result
this._isEnded = count !== undefined ? (this.paginationInternal.current * this.paginationInternal.size >= count) : (data.length < this.pageSize)
this.hasMore = !this._isEnded
const data2 = this.getone ? (data.length ? data[0] : undefined) : data
if (this.getcount) {
this.paginationInternal.count = count
}
callback && callback(data2, this._isEnded, this.paginationInternal)
this._dispatchEvent(events.load, data2)
if (this.getone || this.pageData === pageMode.replace) {
this.dataList = data2
} else {
if (clear) {
this.dataList = data2
} else {
this.dataList.push(...data2)
}
}
},
_execLoadDataFail(err, callback) {
this.errorMessage = err
callback && callback()
this.$emit(events.error, err)
if (process.env.NODE_ENV === 'development') {
console.error(err)
}
},
_getExec() {
return this.getTemp(false)
},
_execRemove(id, action, success, fail, complete, needConfirm, needLoading, loadingTitle) {
if (!this.collection || !id) {
return
}
const ids = isArray(id) ? id : [id]
if (!ids.length) {
return
}
if (needLoading) {
uni.showLoading({
mask: true,
title: loadingTitle
})
}
const db = uniCloud.database(this.spaceInfo)
const dbCmd = db.command
let exec = db
if (action) {
exec = exec.action(action)
}
exec.collection(this.mainCollection).where({
_id: dbCmd.in(ids)
}).remove().then((res) => {
success && success(res.result)
if (this.pageData === pageMode.replace) {
this.refresh()
} else {
this.removeData(ids)
}
}).catch((err) => {
fail && fail(err)
if (needConfirm) {
uni.showModal({
content: err.message,
showCancel: false
})
}
}).finally(() => {
if (needLoading) {
uni.hideLoading()
}
complete && complete()
})
},
removeData(ids) {
const il = ids.slice(0)
const dl = this.dataList
for (let i = dl.length - 1; i >= 0; i--) {
const index = il.indexOf(dl[i]._id)
if (index >= 0) {
dl.splice(i, 1)
il.splice(index, 1)
}
}
},
_dispatchEvent(type, data) {
if (this._changeDataFunction) {
this._changeDataFunction(data, this._isEnded, this.paginationInternal)
} else {
this.$emit(type, data, this._isEnded, this.paginationInternal)
}
}
}
}
</script>