Vue 自定义 alert弹窗/loading组件 挂载全局

3,102 阅读3分钟
根据自己需求,设计出自己想要的弹窗!

该文章用了代码用了typescript,注意一下。

alert弹窗组件

该组件设计需求是模范微信小程序的Toast

效果如图:

代码如下:


<template>
  <div class="alert">
    <!-- 遮罩层防穿透 -->
    <div class="alert-mask" v-if="messageInfo.mask"></div>
    <div class="alert-box" v-if="messageInfo.content">
      <!-- icon -->
      <div v-if="messageInfo.icon==='success'">
        <img class="alert-icon" src="/static/images/icon-success.png" alt />
      </div>
      <!-- 消息 一行设置7个汉字,最多2行-->
      <span>{{messageInfo.content}}</span>
    </div>
  </div>
</template>
<style scoped>
.alert-box {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6);
  color: white;
  text-align: center;
  padding: 10px 8px;
  border-radius: 5px;
  width: 115px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  -webkit-line-clamp: 2;
  z-index: 999;
}
.alert-mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 998;
}
.alert-icon {
  width: 50px;
  height: 50px;
}
</style><script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component({})
export default class Alert extends Vue {
  private messageInfo: Object = {};
  private count = 0;
  public setMsgID() {
    return "alert_" + this.count++;
  }
  public add(msg) {
    this.messageInfo = msg;
    const duration = msg.duration;
    if ((this.messageInfo as any).success) {
      try {
        (this.messageInfo as any).success();
      } catch (error) {
        console.error(error);
      }
    }
    setTimeout(() => {
      this.remove();
    }, duration);
  }
  public remove() {
    this.messageInfo = {};
    return;
  }
  mounted() {}
}
</script>

实例调用:

    this.$Toast.info({
      message:'提交成功',
      duration:1500,
      icon:'success',
      mask:true,
      success:()=>{
        console.log('调用成功')
      }
    })

代码分析:

调用全局$Toast.info方法,创建一个alert组件。以相对于窗口垂直居中显示。

组件设置:宽度固定值115px,一行最多可显示7个中文汉字,至多2行。根据'-webkit-line-clamp: 2;'样式来修改行数。

Loading组件

效果图如图:,实际为动图(不会截GIF图片),类似跳动的动画效果

代码如下:

<style scoped>
.loading-box {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6);
  color: white;
  text-align: center;
  padding: 15px 8px;
  border-radius: 5px;
  width: 115px;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  overflow: hidden;
  -webkit-line-clamp: 2;
  z-index: 999;
}
.loading-mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 998;
}
.loading {
  width: 80px;
  margin: 0 auto;
  display: flex;
  padding: 10px 0;
}
.loading-item {
  width: 4px;
  height: 50px;
  background: white;
  animation: loading 1s infinite;
  margin: 0 auto;
}
.loading-item:nth-child(1) {
  animation-delay: -0.9s;
}
.loading-item:nth-child(2) {
  animation-delay: -0.8s;
}
.loading-item:nth-child(3) {
  animation-delay: -0.7s;
}
.loading-item:nth-child(4) {
  animation-delay: -0.6s;
}
.loading-item:nth-child(5) {
  animation-delay: -0.5s;
}
.loading-item:nth-child(6) {
  animation-delay: -0.4s;
}
@keyframes loading {
  0%,
  40%,
  100% {
    transform: scaleY(0.25);
  }
  20% {
    transform: scaleY(1);
  }
}
</style>
<template>
  <div class="loading">
    <!-- 遮罩层防穿透 -->
    <div class="loading-mask" v-if="loading.mask"></div>
    <div class="loading-box" v-if="loading !=''">
      <div class="loading">
        <div class="loading-item" v-for="item in 6" :key="item"></div>
      </div>
      <!-- 消息 一行设置7个汉字,最多2行-->
      <span v-if="loading.content">{{loading.content}}</span>
    </div>
  </div>
</template>

<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";

@Component({})
export default class Loading extends Vue {
  private loading = "";
  private count = 0;
  mounted() {}

  private setLoadingID() {
    return "loading_" + this.count;
  }
  private add(loading) {
    // 关闭之前调用的loading
    if (this.loading) {
      this.close();
    }

    this.loading = loading;
    if ((this.loading as any).success) {
      try {
        (this.loading as any).success();
      } catch (error) {
        console.error(error);
      }
    }
  }
  public close() {
    this.loading = "";
  }
}
</script>




实例调用:

    this.$Toast.loading({
        message:'加载中',
        duration:30000,
        mask:true,
        success:()=>{
          console.log('调用成功')
        }
    })

代码分析:

loading调用其实跟alert差不多,少了个icon参数。

icon在loading组件中用css动画实现了,以后想实现那种加载样式自己改就行了。(菊花式加载,涟漪缓动等花里胡哨的样式)。

动画参考文章juejin.cn/post/684490…

挂载全局

创建 mountCompoment.ts 文件

import Alert from './alert.vue';
import Loading from './loading.vue'
import Vue from 'vue';

// alert 实例
(Alert as any).newInstance   = ( properties )=> {
    let prop = properties||{}

    const instance = new Vue({
        data:prop,
        render(h) {
            return h(Alert)
        }
    })
    const component = instance.$mount();
    document.body.appendChild(component.$el);
    const alert = instance.$children[0];

    return{
        add(msg) {
            alert.add(msg);
        },
        remove(id) {
            alert.remove(id);
        }
    }
}
let messageInstance;

function getMessageInstance() {
    messageInstance = messageInstance||(Alert as any).newInstance()
    return messageInstance;
}
// 设置 alert组件参数
function message(options) {
    
    let instance = getMessageInstance();
    instance.add({
        content:options.message||'',
        duration:options.duration||1500,
        icon:options.icon||'',
        mask:options.mask||false,
        success:options.success||null
    })
}

// loading 实例
(Loading as any).newInstance   = ( properties )=> {
    let prop = properties||{}

    const instance = new Vue({
        data:prop,
        render(h) {
            return h(Loading)
        }
    })

    const component = instance.$mount();
    document.body.appendChild(component.$el);
    const alert = instance.$children[0];

    return{
        add(loading) {
            alert.add(loading);
        },
        close() {
            alert.close();
        }
    }
}
let loadingInstance;
function getLoadingInstance() {
    loadingInstance = loadingInstance||(Loading as any).newInstance()
    return loadingInstance;
}
//  设置 Loading组件
function setLoading(options,type) {
    
    let instance = getLoadingInstance();
    if(type === 'loading') {
        instance.add({
            content:options.message||'',
            duration:options.duration||60000,
            icon:options.icon||'',
            mask:options.mask||false,
            success:options.success||null
        })
    } else {
        instance.close();
    }
    
}


//  实例挂载
export default{
    // alert 和 loading 只能存在一个

    info(options) {
        if(loadingInstance) {
            loadingInstance.close();
        }
        return message(options)
    },
    loading(options) {
        if(messageInstance) {
            messageInstance.remove()
        }
        return setLoading(options,'loading')
    },
    close(options) {
        return setLoading(options,'close')
    }
}


代码分析:

创建组件:在方法newInstance中,创建了一个Vue实例,用render函数返回组件alert/loading。通过$mount()挂载,插入文档。获取组件alert/loading 方法 add、remove、close。返回给 实例 messageInstance/loadingInstance。

最后export方法(info,loading,close)。设置 alert/loading 组件 只能单独出现。

相互调用的话就突出覆盖掉。

全局挂载

import mount from './components/alert/mountCompoment';
Vue.prototype.$Toast = mount

总结

很少写文章,语言组织的不是很好,见谅

希望这篇文章对你有所帮助,谢谢。

参考文章

 封装Vue组件的一些技巧 juejin.cn/post/684490…  

优化

loading组件 可设置为无时间限制,只能调用close来关闭。

多次调用alert组件,依次显示,参考上面文章链接