需求
平时主要是用uni.showModal 实现弹窗效果,但是各平台app和h5样式都不一样,ui给了统一的弹窗样式。
为了兼容app和h5,实现跟uni.showModal一样简单使用就能触发弹窗,且可扩展,兼容样式,按钮修改,弹窗内容富文本展示等,决定自己封装一个全局弹窗
实现
我们知道,app里面如果用普通view自定义弹窗,无法覆盖导航栏和底部tab栏
解决办法:
1、可以使用subnvue webview子窗体
2、使用 plus.nativeObj.View
3、参考插件市场里的解决方案,新建一个页面进行跳转,可以实现伪弹窗(其实是打开一个背景透明的页面)
第一个方案,需要在每个页面(page.json)里配置subNVues ,麻烦且违背了全局的概念。第二个编写复杂,且页面样式较难自定义,最后选择了第三个方案
需求,能够接收外部参数(props)。关于外部参数这里,我使用了vuex,定于全局变量然后传入组件
实施过程
1、定义props类型
popupConfig:
{
type: 1, // 弹窗类型(1、提交反馈弹窗 2、modal弹窗 3、自定义弹窗(设为此后下面其他参数将无效))
showCancel: false, // 是否显示取消按钮
confirmText:'确定', // 确定按钮文字
cancelText:'取消', // 取消按钮文字
closeOpacity:false,// 点击遮罩是否关闭弹窗
title: '', // 标题
content: '' ,// 内容文本
icon: 1// 反馈弹窗状态 (1、成功 2、失败 3、正常)
},
2、编写弹窗组件和方法
这里因为是用的页面跳转方法,那实际应该是个页面
pages.json里注册好
{
"path": "pages/globalPopup/globalPopup",
"style": {
"navigationStyle": "custom",
"backgroundColor": "transparent",
"app-plus": {
"animationType": "fade-in",
"background": "transparent",
"popGesture": "none",
"bounce": "none",
"titleNView": false
}
}
},
弹窗显示就是跳转页面uni.navigateTo,隐藏就是返回uni.navigateBack,这里展示一部分,具体代码可查看附件里的源码
<!-- 成功或失败弹窗 -->
<template v-if="params.type === 1">
<view class="popup-box">
<template v-if="params.icon === 1">
<image class="icon-img" style="width:210rpx;" src="/static/image/popup_success.png" mode="widthFix"></image>
</template>
<template v-else-if="params.icon === 2">
<image class="icon-img" style="width:120rpx;" src="/static/image/popup_fail.png" mode="widthFix"></image>
</template>
<template v-else-if="params.icon === 3">
<image class="icon-img" style="width:120rpx;" src="/static/image/popup_info.png" mode="widthFix"></image>
</template>
<view class="title">
<text>{{params.title}}</text>
</view>
<view class="content">
<text>{{params.content}}</text>
</view>
<view class="btn-box" style="margin-top: 30rpx;">
<template v-if="params.showCancel">
<button class="btn btn-two" :style="`border:1rpx solid;color:${primaryColor};`" @click="cancel">{{params.cancelText}}</button>
<button class="btn btn-two" :style="`color:#fff;background:${primaryColor};`" @click="confirm">{{params.confirmText}}</button>
</template>
<template v-else>
<button class="btn btn-one" @click="confirm">{{params.confirmText}}</button>
</template>
</view>
</view>
</template>
<!-- modal弹窗 -->
<template v-else-if="params.type === 2">
<view class="modal-box">
<view class="modal-top">
<text class="modal-title">{{params.title}}</text>
<text class="modal-content">{{params.content}}</text>
</view>
<view class="modal-btn">
<template v-if="params.showCancel">
<view class="item" @click="cancel">{{params.cancelText}}</view>
<u-line direction="col" color="#eee" />
<view class="item active" @click="confirm">{{params.confirmText}}</view>
</template>
<template v-else>
<view class="item active" style="width: 500rpx;" @click="confirm">{{params.confirmText}}</view>
</template>
</view>
</view>
</template>
<!-- 自定义弹窗 -->
<template v-else-if="params.type === 3">
<view class="rich-box">
<rich-text :nodes="params.content"></rich-text>
</view>
</template>
关键点在于逻辑,因为要使用诸如uni.showModel 这样的api来实现弹窗效果,需要进行api封装, 添加显示 show() 隐藏 hide() 的方法,另外需要将点击按钮后的回调传给api,使用了uni.navigateTo 的事件发送。页面跳转的方式不适用于h5,所以h5的需要单独处理(h5的弹窗其实是一个dom节点。使用dom操作),弹窗展示后需要重置数据,不然下个弹窗会出现上个弹窗的信息
import Vue from 'vue';
import globalPopup from './globalPopup.vue'
class GlobalPopup {
constructor () {
const PopupVue = Vue.extend(globalPopup);
this.popupDom = new PopupVue()
}
// 弹窗成功后需要重置popupConfig数据
init(){
const config = {
type: 1, // 弹窗类型(1、提交反馈弹窗 2、modal弹窗 3、自定义弹窗(设为此后其他参数无效))
showCancel: false, // 是否显示取消按钮
confirmText:'确定', // 确定按钮文字
cancelText:'取消', // 取消按钮文字
closeOpacity:false,// 点击遮罩是否关闭弹窗
title: '', // 标题
content: '' ,// 内容文本
icon: 1// 反馈弹窗状态 (1、成功 2、失败 3、正常)
}
Vue.prototype.$store.dispatch('updatePopupConfig',config)
}
// 显示弹窗
show (params) {
let that = this;
// Vue.store 传递参数
Vue.prototype.$store.dispatch('updatePopupConfig',params)
// #ifdef APP-PLUS
uni.navigateTo({
url: '/pages/globalPopup/globalPopup',
events:{
confirm: function(data) {
that.init()
params.confirm && params.confirm()
},
cancel: function(data) {
that.init()
params.cancel && params.cancel()
}
}
})
// #endif
// #ifdef H5
this.popupDom.cancel = params?.cancel || this.popupDom.cancel;
this.popupDom.confirm = params?.confirm || this.popupDom.confirm;
this.popupDom.vm = this.popupDom.$mount();
this.popupDom.show = true;
const lastEl = document.body.lastElementChild;
if(lastEl.id !== 'popup-box'){
setTimeout(()=>{
document.body.appendChild(that.popupDom.vm.$el)
})
}
// #endif
};
// 隐藏弹窗 h5弹窗调用回调后需要手动隐藏弹窗(因为无法在回调中获取对象本身,有大佬可以自行优化)
hide () {
// #ifdef H5
let that = this;
this.popupDom.show = false;
const lastEl = document.body.lastElementChild;
if(lastEl.id === 'popup-box'){
setTimeout(()=>{
document.body.removeChild(lastEl)
that.init()
},500)
}
// #endif
}
}
将事件注册到全局
main.js 添加
import globalPopup from '@/pages/globalPopup/globalPopup.js'
Vue.prototype.$popup = globalPopup;
3、效果展示
this.$popup.show({
title:'提交成功',
content:'请等待确认',
confirm:()=>{
// #ifdef H5
this.$popup.hide()
// #endif
}
})
2023-6-9 更新
一直想把小程序的全局弹窗也做了,原计划把小程序和h5封装为同一个方法,即使用vue的全局注册功能,将自定义弹窗注册为一个全局组件,但是发现小程序不像h5一样可以操作dom,即使注册了要在页面中使用还是得用标签加上,找了很多资料都没有类似js添加dom的操作,知道发现一个插件vue-inset-loader,作者使用sfc模板在编译阶段指定位置插入自定义内容,参考文档,解决了我的困扰。
但是在使用中还是发现了不少问题,如文件只能放在components文件夹中,数据传输只能用vuex,猜测可能是uni的easycom 配置影响,如果有大佬明白,还请告知一下。
还有之前一直存在的一个问题,在h5弹窗调用回调后需要手动隐藏弹窗(因为无法在回调中获取对象本身),当时自己弄了半天,结果今天意外就找到了解决办法,那就是把方法挂载到vuex中,通过跨页面调用实现回调!
已将该插件放到插件市场,需要的可以访问查看
