乐趣区

关于vue3:基于Vue30自定义移动端弹层组件Vue3Popup

介绍

V3Popup 一款基于 vue3.0 开发手机端弹出框组件。在开发设计之初参考借鉴了 Vant3Antdv2.0中弹框组件化思维。反对超过 20+ 种参数灵便配置。


引入组件

// 在 main.js 中全局引入
import {createApp} from 'vue'
import App from './App.vue'

// 引入弹窗组件 v3popup
import V3Popup from './components/v3popup'
 
createApp(App).use(V3Popup).mount('#app')

遵循极简的调用形式,反对 组件式 + 函数式 两种调用。

  • 组件式
<!-- 提示框 -->
<v3-popup v-model="showMsg" anim="fadeIn" content="msg 提示框测试(3s 后窗口敞开)" shadeClose="false" time="3" />
 
<!-- 询问框 -->
<v3-popup v-model="showConfirm" shadeClose="false" title="题目" xclose z-index="2020"
    content="<div style='color:#1be769;padding:20px;'> 确认框(这里是确认框提示信息,这里确认框提示信息,这里是确认框提示信息)</div>"
    :btns="[{text: '勾销', click: () => showConfirm=false},
        {text: '确定', style: 'color:#f90;', click: handleInfo},
    ]"
/>
  • 函数式
let $el = this.$v3popup({
    title: '题目',
    content: '<div style='color:#f90;padding:10px;'> 这里是内容信息!</div>',
    type: 'android',
    shadeClose: false,
    xclose: true,
    btns: [{text: '勾销', click: () => {$el.close(); }},
        {text: '确认', style: 'color:#f90;', click: () => handleOK},
    ],
    onSuccess: () => {},
    onEnd: () => {}
})

vue2 中能够通过 prototype 原型链挂载全局办法。vue3 如果挂载全局办法呢?
其实 vue3 中提供了两种形式来挂载全局办法。
两种形式。
1、通过app.config.globalProperties.$v3popup = V3Popup

// vue2 中调用
methods: {showDialog() {this.$v3popup({...})
    }
}
 
// vue3 中调用
setup() {
    // 获取上下文
    const {ctx} = getCurrentInstance()
    ctx.$v3popup({...})
}

2、通过app.provide('v3popup', V3Popup)

// vue2 中调用
methods: {showDialog() {this.v3popup({...})
    }
}
 
// vue3 中调用
setup() {const v3popup = inject('v3popup')
    
    const showDialog = () => {v3popup({...})
    }
 
    return {
        v3popup,
        showDialog
    }
}

不过 vue 作者更举荐第二种形式挂载办法。

预览图










参数配置

v3popup 反对如下参数混合搭配应用。

|props 参数 |
v-model         是否显示弹框
title           题目
content         内容(反对 String、带标签内容、自定义插槽内容)*** 如果 content 内容比较复杂,举荐应用标签式写法
type            弹窗类型(toast | footer | actionsheet | actionsheetPicker | android | ios)popupStyle      自定义弹窗款式
icon            toast 图标(loading | success | fail)shade           是否显示遮罩层
shadeClose      是否点击遮罩时敞开弹窗
opacity         遮罩层透明度
round           是否显示圆角
xclose          是否显示敞开图标
xposition       敞开图标地位(left | right | top | bottom)xcolor          敞开图标色彩
anim            弹窗动画(scaleIn | fadeIn | footer | fadeInUp | fadeInDown)position        弹出地位(top | right | bottom | left)follow          长按 / 右键弹窗(坐标点)time            弹窗主动敞开秒数(1、2、3)zIndex          弹窗层叠(默认 8080)teleport        指定挂载节点(默认是挂载组件标签地位,可通过 teleport 自定义挂载地位)teleport="body | #xxx | .xxx"
btns            弹窗按钮(参数:text|style|disabled|click)++++++++++++++++++++++++++++++++++++++++++++++
|emit 事件触发 |
success         层弹出后回调(@success="xxx")end             层销毁后回调(@end="xxx")++++++++++++++++++++++++++++++++++++++++++++++
|event 事件 |
onSuccess       层关上回调事件
onEnd           层敞开回调事件
<template>
    <div ref="elRef" v-show="opened" class="vui__popup" :class="{'vui__popup-closed': closeCls}" :id="id">
        <!-- // 蒙层 -->
        <div v-if="JSON.parse(shade)" class="vui__overlay" @click="shadeClicked" :style="{opacity}"></div>
        <div class="vui__wrap">
            <div class="vui__wrap-section">
                <div class="vui__wrap-child" :class="['anim-'+anim, type&&'popupui__'+type, round&&'round', position]" :style="[popupStyle]">
                    <div v-if="title" class="vui__wrap-tit" v-html="title"></div>
                    <div v-if="type=='toast'&&icon" class="vui__toast-icon" :class="['vui__toast-'+icon]" v-html="toastIcon[icon]"></div>
                    <!-- 判断插槽是否存在 -->
                    <template v-if="$slots.content">
                        <div class="vui__wrap-cnt"><slot name="content" /></div>
                    </template>
                    <template v-else>
                        <div v-if="content" class="vui__wrap-cnt" v-html="content"></div>
                    </template>
                    <slot />
                    <div v-if="btns" class="vui__wrap-btns">
                        <span v-for="(btn, index) in btns" :key="index" class="btn" :style="btn.style" @click="btnClicked($event, index)" v-html="btn.text"></span>
                    </div>
                    <span v-if="xclose" class="vui__xclose" :class="xposition" :style="{'color': xcolor}" @click="close"></span>
                </div>
            </div>
        </div>
    </div>
</template>
 
/**
 * @Desc     Vue3.0 自定义弹层 V3Popup
 * @Time     andy by 2020-12
 * @About    Q:282310962  wx:xy190310
 */
<script>
    import {onMounted, ref, reactive, watch, toRefs, nextTick} from 'vue'
    let $index = 0, $locknum = 0, $timer = {}
    export default {
        props: {// 接管父组件 v -model 值,如果 v -model:open,则这里需写 open: {...}
            modelValue: {type: Boolean, default: false},
            // 标识符,雷同 ID 共享一个实例
            id: {type: String, default: ''},
            title: String,
            content: String,
            type: String,
            popupStyle: String,
            icon: String,
            shade: {type: [Boolean, String], default: true },
            shadeClose: {type: [Boolean, String], default: true },
            opacity: {type: [Number, String], default: '' },
            round: Boolean,
            xclose: Boolean,
            xposition: {type: String, default: 'right'},
            xcolor: {type: String, default: '#333'},
            anim: {type: String, default: 'scaleIn'},
            position: String,
            follow: {type: Array, default: null},
            time: {type: [Number, String], default: 0 },
            zIndex: {type: [Number, String], default: '8080' },
            teleport: [String, Object],
            btns: {type: Array, default: null},
            onSuccess: {type: Function, default: null},
            onEnd: {type: Function, default: null},
        },
        emits: ['update:modelValue'],
        setup(props, context) {const elRef = ref(null)
 
            const data = reactive({
                opened: false,
                closeCls: '',
                toastIcon: {...}
            })
 
            onMounted(() => {...})
 
            // 监听弹层 v -model
            watch(() => props.modelValue, (val) => {if(val) {open()
                }else {close()
                }
            })
 
            // 关上弹层
            const open = () => {if(data.opened) return
                data.opened = true
                typeof props.onSuccess === 'function' && props.onSuccess()
 
                const dom = elRef.value
                dom.style.zIndex = getZIndex() + 1
 
                ...
 
                // 倒计时
                if(props.time) {
                    $index++
                    // 防止反复操作
                    if($timer[$index] !== null) clearTimeout($timer[$index])
                    $timer[$index] = setTimeout(() => {close()
                    }, parseInt(props.time) * 1000)
                }
 
                // 长按 | 右键菜单
                if(props.follow) {...}
            }
 
            // 敞开弹层
            const close = () => {if(!data.opened) return
 
                data.closeCls = true
                setTimeout(() => {
                    ...
 
                    context.emit('update:modelValue', false)
                    typeof props.onEnd === 'function' && props.onEnd()}, 200)
            }
 
            // 点击遮罩层
            const shadeClicked = () => {if(JSON.parse(props.shadeClose)) {close()
                }
            }
            // 按钮事件
            const btnClicked = (e, index) => {let btn = props.btns[index];
                if(!btn.disabled) {typeof btn.click === 'function' && btn.click(e)
                }
            }
            
            ...
 
            return {...toRefs(data),
                elRef,
                close,
                shadeClicked,
                btnClicked,
            }
        }
    }
</script>

vue3 中如何把弹框实例挂载到 body 上呢?其实可通过 createAppcreateVNode实现。
createApp第一个参数传入组件模板,第二个参数传入自定义参数即可。

import {createApp} from 'vue'
import PopupConstructor from './popup.vue'
 
let $inst
// 创立挂载实例
let createMount = (opts) => {const mountNode = document.createElement('div')
    document.body.appendChild(mountNode)
 
    const app = createApp(PopupConstructor, {
        ...opts, modelValue: true,
        remove() {app.unmount(mountNode)
            document.body.removeChild(mountNode)
        }
    })
    return app.mount(mountNode)
}
 
function V3Popup(options = {}) {options.id = options.id || 'v3popup_' + generateId()
    $inst = createMount(options)
    
    return $inst
}
 
V3Popup.install = app => {app.component('v3-popup', PopupConstructor)
    // app.config.globalProperties.$v3popup = V3Popup
    app.provide('v3popup', V3Popup)
}

通过如上办法就能够实现函数式调用组件了。

okey,基于 Vue3.x 开发自定义弹出层组件就分享到这里。心愿能喜爱~✍

退出移动版