【Vue3】封装两个全局的弹层组件
文件结构
之前文章《vue.extend()+aysnc封装一个弹层组件【vue2】》介绍过如果在vue2下封装一个全局的弹层组件,这一篇文章来说说如何在vue3下如何去实现。
首先说一下弹层组件layer-tips的目的,主要在程序任何位置,直接调用this.$layerTips('消息'),就可以出现一个位于屏幕中央的提示窗,然后2秒后消失;
另一个弹层组件layer-aletr使用不同于layer-tips的方式实现,主要实现点击的时候出现弹层,点确定再清空DOM信息和结构。
| src
|-- components
|-- layer-tips
|-- layer-tips.vue
|-- layerTips.js
|-- layer-alert
|-- layer-alert.vue
|-- layerAlert.js
|-- common
|-- library
|-- index.js // 引入所有全局组件,并导出一个配置,用于 app.use() 安装组件库使用
main.js
layer-tips提示弹层实现
组件结构、样式layer-tips.vue
<template>
<transition name="layerDown">
<section class="layer-tips" v-if="show">
<p>{{ msg }}</p>
</section>
</transition>
</template>
<script type="text/ecmascript-6">
export default {
name: "layerTips",
data() {
return {
show: false,
msg: '',
time: 1000
}
},
methods: {
async open() {
if (this.show) {
return;
}
this.show = true;
let result = await this.close();
return result;
},
close() {
return new Promise((resolve) => {
setTimeout(() => {
this.show = false;
resolve(true);
}, this.time);
});
}
}
}
</script>
<style lang="less" rel="stylesheet/less" scoped>
@import "~common/less/variable.less";
.layer-tips{
position: fixed;
top: 50%;
text-align: center;
left: 0;
right: 0;
color: #fff;
z-index: @zindex-MAX;
p{
display: inline-block;
background: rgba(0,0,0,0.8);
padding: 20px 20px;
border-radius: 5px;
}
}
.layerDown-enter-active,
.layerDown-leave-active {
transition: all 0.5s ease;
}
.layerDown-enter-from,
.layerDown-leave-to {
transform: translate(0, -30%);
opacity: 0;
}
</style>
layerTips.js
import { createApp } from 'vue';
import LayerTipsComponents from 'components/layer-tips/layer-tips';
const LayerTipsId = 'layer-tips-wrapper';
// 内部有个异步方法 所以加async
let LayerTips = async function (msg, time) {
// 给参数默认值
time = time || 2000;
let tipsEl = document.getElementById(LayerTipsId);
// 如果DOM中含有这个元素 那么不执行
if (tipsEl) {
return;
}
// console.log(tipsEl.length)
// 创建一个div外层元素并赋予ID
const div = document.createElement('div');
div.setAttribute('id', LayerTipsId)
document.body.appendChild(div);
// 实例化layerTips的实例
let layerTipsEl = createApp(LayerTipsComponents).mount('#' + LayerTipsId);
// 修改组件中的data的值
layerTipsEl.msg = msg;
layerTipsEl.time = time;
// 执行组件中的方法 等待关闭后返回promise
let hasClosed = await layerTipsEl.open();
// 当tips提示关闭后 再删除外层元素
if (hasClosed) {
setTimeout(() => {
document.body.removeChild(div);
}, 1000);
}
};
export default LayerTips;
这里需要注意的是createApp(组件).mount(挂载到的DOM元素ID);
如果要看如何引用,直接看《四》
layer-alert交互弹层的实现
layer-alert.vue
<template>
<transition name="layerFadeIn">
<section class="layer-alert" v-if="show">
<div class="inner">
<p class="top-p">系统提示</p>
<p class="mid-msg">
<span>{{ msg }}</span>
</p>
<div class="bot-btns">
<button type="button"
class="sure"
@click="close"
:id="buttonId"
>确定</button>
</div>
</div>
</section>
</transition>
</template>
<script type="text/ecmascript-6">
let eventHide = new CustomEvent('layerAlertHide');
export default {
name: "layerAlert",
props: {
msg: {
type: String,
default() {
return '提示'
}
},
buttonId: {
type: String,
default() {
return 'button';
}
}
},
data() {
return {
show: true
}
},
methods: {
close() {
this.show = false;
let button = document.getElementById(this.buttonId);
setTimeout(() => {
if(window.dispatchEvent) {
button.dispatchEvent(eventHide);
} else {
button.fireEvent(eventHide);
}
}, 400);
}
}
}
</script>
<style lang="less" rel="stylesheet/less" scoped>
@alertWidth: 400px;
// 保证内层弹窗有动画
@keyframes fadeDown {
0%{
opacity: 0;
transform: translate(0, -30%);
}
100%{
opacity: 1;
transform: translate(0, 0%);
}
}
.layer-alert{
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 999;
background: rgba(0,0,0,0.8);
.inner{
background: #fff;
width: @alertWidth;
position: absolute;
left: 50%;
margin-left: -(@alertWidth / 2);
top: 35%;
border-radius: 5px;
animation: fadeDown 0.4s both;
.top-p{
color: #555;
padding: 20px 15px;
border-bottom: 1px solid #eee;
}
.mid-msg{
padding: 30px 15px;
border-bottom: 1px solid #eee;
text-align: center;
word-break: break-all;
line-height: 20px;
span{
display: inline-block;
text-align: left;
}
}
.bot-btns{
padding: 15px;
text-align: right;
.sure{
border: 0;
background: #2196f3;
color: #fff;
display: inline-block;
width: 150px;
line-height: 36px;
border-radius: 3px;
cursor: pointer;
}
}
}
}
.layerDown-enter-active,
.layerDown-leave-active {
transition: all 0.5s ease;
}
.layerDown-enter-from,
.layerDown-leave-to {
transform: translate(0, -30%);
opacity: 0;
}
.layerFadeIn-enter-active,
.layerFadeIn-leave-active {
transition: all 0.5s ease;
}
.layerFadeIn-enter-from,
.layerFadeIn-leave-to {
opacity: 0;
}
</style>
对于这个组件,我使用了不同于上面的实现方法,采用了createVNode和render这两个方法
hhttps://v3.cn.vuejs.org/guide/render-function.html#h-参数
layerAlert.js
import { createVNode, render } from 'vue'
import LayerAlertComponent from 'components/layer-alert/layer-alert';
const wrappId = 'layer-alert-wrapper';
const buttonId = 'layer-alert-sure';
// 创建一个<div class="layer-alert-wrapper"></div>
const divVNode = createVNode('div', {
class: wrappId,
id: wrappId
});
// 插入到body中
render(divVNode, document.body);
const div = divVNode.el;
const LayerAlert = (msg) => {
// 1. 动态创建虚拟DOM - createVNode(h) 函数 这里的{是对应props中的参数}
const comVNode = createVNode(LayerAlertComponent, { msg, buttonId });
// 2. 渲染到body页面中 - render 函数
// render(comVNode, document.body)
render(comVNode, div);
// 执行组件内部的方法
// console.log('执行组件中的方法:', comVNode.type.methods.close())
// console.log(comVNode)
// 监听组件派发的事件,当关闭动画执行完毕
document.getElementById(buttonId)
.addEventListener('layerAlertHide',() => {
// 立即清空其中的dom
render(null, div);
// 提示在 20ms 后自动卸载 清空layer-alert-wrapper中的所有dom
/*
setTimeout(() => {
render(null, div);
}, 20);
*/
});
};
export default LayerAlert;
值得注意的是,如果要调用实例化组件的方法,需要执行:comVNode.type.methods.close()
在这里,触发组件内部方法我使用的元神JS的方法,当关闭动画执行完毕后向button触发一个自定义事件,外部DOM也去监听这个事件。总感觉这个实现方法有点问题。欢迎有不同解决方法的朋友跟我交流。
实现引入和使用
实现逻辑编写完成,那么再就是引入这两个组件,然后挂到vue的prototype上了。这里挂载我之前也介绍过:在Vue的原型链上挂载全局变量
library/index.js
这里我们在common中创建一个文件,专门用来加载自定义的全局组件,然后统一在main.js中使用。
import LayerTips from "components/layer-tips/layerTips";
import LayerAlert from "components/layer-alert/layerAlert";
// Vue3 注册全局组件库写法:
// app.component(组件名,组件文件)
// 导出一个配置,用于 app.use() 安装组件库使用
export default {
install (app) {
// 全局挂载 原型函数 过组件实例调用的属性 this.$message
app.config.globalProperties.$layerTips = LayerTips;
app.config.globalProperties.$layerAlert = LayerAlert;
}
}
这是固定写法,记住即可。
在main.js中注册
import { createApp } from 'vue';
import App from './App.vue';
import router from 'pages/image/router/index.js';
import Lib from 'common/library/index';
createApp(App)
.use(router)
.use(Lib)
.mount('#app');
使用
在各个组件中(注意不能是在composition api中使用)
this.$layerTips('抱歉,您的操作太频繁', 2000传入显示停滞的时间可省略);
this.$layerAlert('数据获取失败');
在组合API中的使用情况,容我测试后再更新吧,先留个坑,以后再填。