【扩展版】uni-app跨端自定义弹窗uaPopup.vue

4,081 阅读2分钟

基于uniapp开发超多功能的全端弹窗组件ua-popup。集成实现了ios、android、微信等弹窗效果。支持编译到h5+小程序+App端,还支持在Nvue等原生页面调用。

H5+小程序+App端预览效果

未标题-1.png

Nvue页面,完美覆盖在video原生组件上面。

p4.gif

由于HBuilderX 2.5.5起支持easycom组件模式,只需把组件放置components目录即可。无需再手动引入/注册。

调用方式

ua-popup弹窗支持 组件写法+函数写法 两种方式来控制弹窗显示/隐藏。

  • 组件式写法 支持自定义插槽内容,丰富弹窗场景。
<!-- msg提示(自定义背景) -->
<ua-popup v-model="showMsgBg" anim="footer" content="hello uniapp" shade="false" time="2" :custom-style="{'backgroundColor': 'rgba(0,0,0,.6)', 'border-radius': '8px', 'color': '#fff'}" />

<!-- 信息框 -->
<ua-popup v-model="showInfo" anim="scaleIn"
	content="阳光下人走不出自己的影子,黑暗中人看不见自己的影子。只要还有明天,今天就永远是起跑线。"
	:btns="[
		{text: '知道了', style: 'color:#999;', click: hideInfo},
	]"
/>
  • 函数式调用 支持多层嵌套函数调用。会自动累加zIndex来控制最顶显示。
// 函数式嵌套调用
handleInfo() {
	let $ua = this.$refs.uapopup
	let $toast = this.$refs.uatoast
	$ua.open({
		content: '人生漫漫,且行且珍惜',
		customStyle: {'background-color': 'rgba(170, 0, 127, 0.6)', 'color': '#fff'},
		time: 3,
		onClose() {
			$ua.open({
				type: 'android',
				content: '<div style="color:#aa007f">预测未来的最好办法是自己亲手创造未来</div>',
				customStyle: {'width': '200px'},
				zIndex: 202120,
				btns: [
					{
						text: 'close', click() {
							$ua.close()
						}
					},
					{
						text: 'Get一下',
						style: 'color:#00aa00;',
						click() {
							$toast.open({
								type: 'toast',
								icon: 'loading',
								content: '请稍后...',
								opacity: .2,
								time: 2,
								zIndex: 202125,
							})
						}
					}
				]
			})
		}
	})
},

p2.gif

编码实现

  • 自定义参数配置 支持超过20+种参数自由搭配实现各种效果。
## props [参数配置]
v-model			当前组件是否显示
title			标题(支持富文本div标签、自定义插槽内容)
content			内容(支持富文本div标签、自定义插槽内容)
type			弹窗类型(toast | footer | actionsheet | actionsheetPicker | android/ios)
customStyle		自定义弹窗样式
icon			toast图标(loading | success | fail | warn | info)
shade			是否显示遮罩层
shadeClose		是否点击遮罩时关闭弹窗
opacity			遮罩层透明度
round			是否显示圆角
xclose			是否显示关闭图标
xposition		关闭图标位置(left | right | top | bottom)
xcolor			关闭图标颜色
anim			弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown)
position		弹出位置(top | right | bottom | left)
follow			长按/右键弹窗(坐标点)
time			弹窗自动关闭秒数(123)
zIndex			弹窗层叠(默认202107)
btns			弹窗按钮(参数:text|style|disabled|click)
------------------------------------------
## slot [插槽]
<template slot="title"></template>
<template slot="content"></template>
------------------------------------------
## emit
open        		打开弹出层时触发(@open="xxx")
close       		关闭弹出层时触发(@close="xxx")
------------------------------------------
## event
onOpen      		打开弹窗回调
onClose     		关闭弹窗回调
  • 弹窗模板
<template>
	<!-- #ifdef APP-NVUE -->
	<view v-if="opts.visible" class="ua__popup" :class="{'ua__popup-closed': closeAnim}">
	<!-- #endif -->
	<!-- #ifndef APP-NVUE -->
	<view v-show="opts.visible" class="ua__popup" :class="{'ua__popup-closed': closeAnim}">
	<!-- #endif -->
		<!-- 遮罩层 -->
		<view v-if="opts.shade && opts.shade!='false'" class="uapopup__overlay" @touchstart="handleShadeClick" :style="{'opacity': opts.opacity >= 0 ? opts.opacity : '', 'z-index': oIndex-1}"></view>
		<!-- 窗口层 -->
		<view class="uapopup__wrap" :style="{'z-index': oIndex}">
			<view class="uapopup__child" :id="'uapopup-'+uuid" :class="['anim-'+opts.anim, opts.type&&'popui__'+opts.type, opts.round&&'round', opts.position]" :style="[opts.follow&&positionStyle, opts.customStyle]">
				<!-- //标题 -->
				<view v-if="opts.title || $slots.title" class="uapopup__title">
					<template v-if="$slots.title"><slot name="title" /></template>
					<rich-text v-else :nodes="opts.title"></rich-text>
				</view>
				
				<!-- //toast -->
				<!-- <view v-if="opts.type=='toast'&&opts.icon" class="toast__icons" :class="['toast__icons-'+opts.icon]" :style="{'background-image': `url(${toastIcon[opts.icon]})`}"></view> -->
				<image v-if="opts.type=='toast'&&opts.icon" class="toast__icons" :class="['toast__icons-'+opts.icon]" :src="toastIcon[opts.icon]" mode="widthFix"></image>
				<!-- //内容 -->
				<view v-if="opts.content || $slots.content" class="uapopup__content">
					<template v-if="$slots.content"><slot name="content" /></template>
					<rich-text v-else :nodes="opts.content"></rich-text>
				</view>
				<slot />
				
				<!-- //按钮组 -->
				<view v-if="opts.btns" class="uapopup__actions">
					<rich-text v-for="(btn,index) in opts.btns" :key="index" class="btn" :class="{'disabled': btn.disabled}" :style="btn.style" @click="handleBtnClick($event, index)" :nodes="btn.text"></rich-text>
				</view>
				
				<!-- //关闭按钮 -->
				<view v-if="opts.xclose" class="uapopup__xclose" :class="opts.xposition" :style="{'color': opts.xcolor}" @click="close"></view>
			</view>
		</view>
	</view>
</template>
/**
 * @Desc     uni-app多端自定义弹框组件
 * @Time     andy by 2021/7/10
 * @About    Q:282310962  wx:xy190310
 */
<script>
	let index = 0
	export default {
		...
		data() {
			return {
				// 混入props参数,处理函数式调用
				opts: {
					visible: false,
				},
				toastIcon: {
					...
				},
				closeAnim: false,
				oIndex: 202107,
				timer: null,
				// 长按定位初始化(避免弹框跳动闪烁)
				positionStyle: { position: 'absolute', left: '-999px', top: '-999px' },
			}
		},
		watch: {
			value(val) {
				const type = val ? 'open' : 'close'
				this[type]()
			}
		},
		computed: {
			uuid() {
				return Math.floor(Math.random() * 10000)
			},
		},
		methods: {
			// 打开弹框
			open(options) {
				if(this.opts.visible) return
				this.opts = Object.assign({}, this.$props, options)
				this.opts.visible = true
				
				// nvue 的各组件在安卓端默认是透明的,如果不设置background-color,可能会导致出现重影的问题
				// #ifdef APP-NVUE
				if(!this.opts.customStyle['background'] && !this.opts.customStyle['background-color']) {
					this.opts.customStyle['background'] = '#fff'
				}
				// #endif
				
				let _index = ++index
				this.oIndex = _index + parseInt(this.opts.zIndex)
				
				this.$emit('open')
				typeof this.opts.onOpen === 'function' && this.opts.onOpen()
				
				// 长按处理
				if(this.opts.follow) {
					...
				}
				
				...
			},
			// 关闭弹框
			close() {
				if(!this.opts.visible) return
				
				this.closeAnim = true
				setTimeout(() => {
					this.opts.visible = false
					this.closeAnim = false
					
					this.$emit('input', false)
					this.$emit('close')
					typeof this.opts.onClose === 'function' && this.opts.onClose()
					
					this.timer && clearTimeout(this.timer)
					delete this.timer
				}, 200)
			},
			
			...
			
			// 获取dom宽高
			getDom(id) {
				return new Promise((resolve, inject) => {
					uni.createSelectorQuery().in(this).select('#uapopup-' + id).fields({
						size: true,
					}, data => {
						resolve(data)
					}).exec()
				})
			},
			
			// 自适应坐标点
			getPos(x, y, ow, oh, winW, winH) {
				let l = (x + ow) > winW ? x - ow : x;
				let t = (y + oh) > winH ? y - oh : y;
				return [l, t];
			},
		}
	}
</script>

其中content支持富文本string格式,自定义插槽内容。

004360截图20210710074422658.png

<!-- 询问框 -->
<ua-popup v-model="showConfirm" shadeClose="false" title="标题" xclose z-index="2001"
	content="<div style='color:#ff557f;padding:20px 40px;'>一切都将一去杳然,任何人都无法将其捕获。</div>"
	:btns="[
		{text: '取消', click: hideConfirm},
		{text: '确定', style: 'color:#00aa00;', click: handleInfo},
	]"
/>

--036360截图20210710075431596.png

<!-- 组件调用 -->
<ua-popup v-model="showComponent" xclose xposition="top" :shadeClose="false" content="自定义模板信息"
	:btns="[
		{text: '确认', style: 'color:#00aa00;', click: hideComponent},
	]"
	@open="handleOpen" @close="handleClose"
>
	<template slot="content"><div style="color:#aa007f;padding: 0 10px;">实力的来源不是胜利。唯有奋斗才能增强实力。当你历经苦难而不气馁,那便是实力。</div></template>
	<view style="padding: 0 20px 20px;">
		<image src="https://img.yzcdn.cn/vant/apple-2.jpg" style="width:100%;" @click="handleContextPopup" />
	</view>
</ua-popup>

end,以上就是uniapp开发兼容多端自定义弹窗组件的一些分享,希望对大家有所帮助!

Vue3+Electron跨端仿QQ聊天实例

Electron13+Vite2+ElementPlus仿Mac桌面管理后台系统

s13.sinaimg.gif