5***@qq.com
5***@qq.com
  • 发布:2023-06-02 16:14
  • 更新:2024-11-19 14:23
  • 阅读:9699

uniapp全局弹窗的封装实现

分类:uni-app

需求

平时主要是用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中,通过跨页面调用实现回调!

已将该插件放到插件市场,需要的可以访问查看

本篇探讨一种全局弹出的封装方法,大佬们可以在此基础上优化使用,源码在附件中

5 关注 分享
onceone 1***@qq.com HRK_01 喜欢技术的前端 一子非鱼一

要回复文章请先登录注册

仲夏夜之梦

仲夏夜之梦

回复 s***@askdwl.com :
这个解决了吗? 遇到同样的问题
2024-11-19 14:23
1***@qq.com

1***@qq.com

IOS下, 上一页的页面上半部分会变成白色的, 安卓没问题,请问有解决办法吗
2024-01-08 17:29
s***@askdwl.com

s***@askdwl.com

在vue和nvue页面弹窗弹出或关闭频繁触发onshow,onhide怎么处理
2024-01-05 16:42