【Vue3】封装两个全局的弹层组件

9844次阅读 849人点赞 作者: WuBin 发布时间: 2021-12-13 16:12:02
扫码到手机查看

文件结构

之前文章《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);

createApp官方API

如果要看如何引用,直接看《四》

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中的使用情况,容我测试后再更新吧,先留个坑,以后再填。

相关资料

点赞 支持一下 觉得不错?客官您就稍微鼓励一下吧!
关键词:createApp,comVNode,render,app.config.globalProperties
推荐阅读
  • uniapp实现被浏览器唤起的功能

    当用户打开h5链接时候,点击打开app若用户在已经安装过app的情况下直接打开app,若未安装过跳到应用市场下载安装这个功能在实现上主要分为两种场景,从普通浏览器唤醒以及从微信唤醒。

    9352次阅读 603人点赞 发布时间: 2022-12-14 16:34:53 立即查看
  • Vue

    盘点Vue2和Vue3的10种组件通信方式

    Vue中组件通信方式有很多,其中Vue2和Vue3实现起来也会有很多差异;本文将通过选项式API组合式API以及setup三种不同实现方式全面介绍Vue2和Vue3的组件通信方式。

    4072次阅读 302人点赞 发布时间: 2022-08-19 09:40:16 立即查看
  • JS

    几个高级前端常用的API

    推荐4个前端开发中常用的高端API,分别是MutationObserver、IntersectionObserver、getComputedstyle、getBoundingClientRect、requ...

    14279次阅读 934人点赞 发布时间: 2021-11-11 09:39:54 立即查看
  • PHP

    【正则】一些常用的正则表达式总结

    在日常开发中,正则表达式是非常有用的,正则表达式在每个语言中都是可以使用的,他就跟JSON一样,是通用的。了解一些常用的正则表达式,能大大提高你的工作效率。

    13219次阅读 462人点赞 发布时间: 2021-10-09 15:58:58 立即查看
  • 【中文】免费可商用字体下载与考证

    65款免费、可商用、无任何限制中文字体打包下载,这些字体都是经过长期验证,经得住市场考验的,让您规避被无良厂商起诉的风险。

    11801次阅读 943人点赞 发布时间: 2021-07-05 15:28:45 立即查看
  • Vue

    Vue3开发一个v-loading的自定义指令

    在vue3中实现一个自定义的指令,有助于我们简化开发,简化复用,通过一个指令的调用即可实现一些可高度复用的交互。

    15974次阅读 1273人点赞 发布时间: 2021-07-02 15:58:35 立即查看
  • JS

    关于手机上滚动穿透问题的解决

    当页面出现浮层的时候,滑动浮层的内容,正常情况下预期应该是浮层下边的内容不会滚动;然而事实并非如此。在PC上使用css即可解决,但是在手机端,情况就变的比较复杂,就需要禁止触摸事件才可以。

    15018次阅读 1223人点赞 发布时间: 2021-05-31 09:25:50 立即查看
  • Vue

    Vue+html2canvas截图空白的问题

    在使用vue做信网单页专题时,有海报生成的功能,这里推荐2个插件:一个是html2canvas,构造好DOM然后转canvas进行截图;另外使用vue-canvas-poster(这个截止到2021年3月...

    29440次阅读 2310人点赞 发布时间: 2021-03-02 09:04:51 立即查看
  • Vue

    vue-router4过度动画无效解决方案

    在初次使用vue3+vue-router4时候,先后遇到了过度动画transition进入和退出分别无效的情况,搜遍百度没没找到合适解决方法,包括vue-route4有一些API都进行了变化,以前的一些操...

    25377次阅读 1954人点赞 发布时间: 2021-02-23 13:37:20 立即查看
交流 收藏 目录