vuejs怎样封装一个插件(以封装vue-toast为例扩展)

插件介绍

插件通常会为 Vue 添加全局功能。插件的范围没有限制——一般有下面几种:

  • 1.添加全局方法或者属性,如: vue-custom-element
  • 2.添加全局资源:指令/过滤器/过渡等,如 vue-touch
  • 3.通过全局 mixin 方法添加一些组件选项,如: vue-router
  • 4.添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  • 5.一个库,提供自己的 API,同时提供上面提到的一个或多个功能,如 vue-router

Vue.js 的插件应当有一个公开方法 install 。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

使用方式很简单,通过全局方法Vue.use()来使用插件。

下面开发一个简单的在控制台打印消息插件,新建一个toast.js:

var Toast = {};
Toast.install = function (Vue, options) {
    Vue.prototype.$msg = 'Hello World';
}
export default Toast;

在main.js中引入该toast.js并使用vue.use()安装该toast插件:

import Vue from 'vue';
import Toast from './toast.js';
Vue.use(Toast);

然后我们在任意vue页面就可以调用:

export default {
    mounted(){
        console.log(this.$msg);         // Hello World
    }
}

 开发vue-toast插件

该插件实现的效果:

  • 1.提示内容可以显示在不同的位置(顶部、底部、中间),调用方式this.$toast.top('填写的提示内容')this.$toast.center('填写的提示内容')this.$toast.bottom('填写的提示内容')
  • 2.通过this.$toast('填写的提示内容')调用该插件,默认展示在底部;
  • 3.toast有默认的展示持续时间,也可配置toast展示的持续时间。

toast.js实现代码如下:

let Toast = {};
Toast.install = function(Vue,options) {
  let opts = {
    type: 'bottom', // 显示位置
    duration: 2500  // 持续时间
  }
  for(let prop in options) {
    if(options.hasOwnProperty(prop)) {
      opts[prop] = options[prop];
    }
  }
  
  Vue.prototype.$toast = (tips,type) => {
    if(type) {
      opts.type = type;
    }
    if(document.getElementsByClassName('vue-toast').length) {
      return false;
    }
    let ToastEl = Vue.extend({
      template: `<div class="vue-toast vue-toast__${opts.type}">${tips}</div>`
    })
    let toastElement = new ToastEl().$mount().$el;
    document.body.append(toastElement);
    setTimeout(() => {
      document.body.removeChild(toastElement);
    },opts.duration);
  }
  
  ['top','center','bottom'].forEach((type) => {
    Vue.prototype.$toast[type] = (tips) => {
     return Vue.prototype.$toast(tips,type)
    }
  })
}

export default Toast;

 优化vue-toast

调用方式更通用,如下所示:

this.toast = this.$toast({
        message: '这是一段信息',
        position: 'top',
        duration: 0
      });

可以手动关闭弹框

this.toast.close();

实现方式就是封装成一个构造函数,整体代码如下:

function Toast() {
  this.toastTimer = false; // toastTimer:toast定时器
  this.toastVM = null; // toastVM:存储toast VM
  this.install = function(Vue,options) {
    let opts = {
      message: '', // 文本内容
      position: 'bottom', // 显示位置
      duration: 2500,  // 展示时长(ms),值为 0 时,toast 不会消失
      className: ''  // 自定义类名
    }

    /** toast提示方法
     * @params {Object|String} config 配置,如果为字符串=message
     * */
    Vue.prototype.$toast = (config) => {
      let option = {};
      let self = this;
      Object.assign(option,opts,options);

      if(typeof config === 'object') {
        Object.assign(option,config)
      } else { // 如果是字符串,传递的是message文本内容
        option.message = config;
      }

      if(this.toastTimer) { // 如果toast还在,则取消上次消失时间
        clearTimeout(this.toastTimer);
        this.toastVM.show = false;
      }

      if(!this.toastVM) {
        let ToastEl = Vue.extend({
          data() {
            return {
              show: false,
              message: option.message,
              className: option.className
            }
          },
          methods: {
            // 关闭toast
            close() {
              self.toastVM.show = false;
            }
          },
          render(createElement) {
            if(!this.show) {
              return false;
            }
            return createElement(
            'div',
            {
              class: ['lx-toast',`lx-toast-${option.position}`,this.className],
              show: this.show,
              domProps: {
                innerHTML: this.message
              }
            },
            )
          }
        })
        this.toastVM = new ToastEl();
        document.body.append(this.toastVM.$mount().$el);
      }
      this.toastVM.show = true;
      this.toastVM.message = option.message;
      this.toastVM.className = option.className;
      this.toastVM.position = option.position;

      if(option.duration != 0) {// 为0的时候一直显示
        this.toastTimer = setTimeout(() => {
            this.toastVM.show = this.toastTimer = false;
        },option.duration)
      }
      return this.toastVM;
    }
  }
}

export default new Toast();

使用方式:

<template>
  <div class="test">
    <button @click="open">开启弹框</button>
    <button @click="close">关闭弹框</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      toast: null
    }
  },

  methods: {
    open() {
      this.toast = this.$toast({
        message: '这是一段信息',
        position: 'top',
        duration: 0
      });
    },
    close() {
      this.toast.close();
    }
  }
}
</script>

添加loading方法

完整代码:

function Toast() {
  this.toastTimer = false; // toastTimer:toast定时器
  this.toastVM = null; // toastVM:存储toast VM
  this.showLoad = false;  // 存储loading显示状态
  this.loadNode = null; // 存储loading节点元素
  this.install = function(Vue,options) {
    let opts = {
      message: '', // 文本内容
      position: 'bottom', // 显示位置
      duration: 2500,  // 展示时长(ms),值为 0 时,toast 不会消失
      className: ''  // 自定义类名
    }

    /** toast提示方法
     * @params {Object|String} config 配置,如果为字符串=message
     * */
    Vue.prototype.$toast = (config) => {
      let option = {};
      let self = this;
      Object.assign(option,opts,options);

      if(typeof config === 'object') {
        Object.assign(option,config)
      } else { // 如果是字符串,传递的是message文本内容
        option.message = config;
      }

      if(this.toastTimer) { // 如果toast还在,则取消上次消失时间
        clearTimeout(this.toastTimer);
        this.toastVM.show = false;
      }

      if(!this.toastVM) {
        let ToastEl = Vue.extend({
          data() {
            return {
              show: false,
              message: option.message,
              className: option.className
            }
          },
          methods: {
            // 关闭toast
            close() {
              self.toastVM.show = false;
            }
          },
          render(createElement) {
            if(!this.show) {
              return false;
            }
            return createElement(
            'div',
            {
              class: ['lx-toast',`lx-toast-${option.position}`,this.className],
              show: this.show,
              domProps: {
                innerHTML: this.message
              }
            },
            )
          }
        })
        this.toastVM = new ToastEl();
        document.body.append(this.toastVM.$mount().$el);
      }
      this.toastVM.show = true;
      this.toastVM.message = option.message;
      this.toastVM.className = option.className;
      this.toastVM.position = option.position;

      if(option.duration != 0) {// 为0的时候一直显示
        this.toastTimer = setTimeout(() => {
            this.toastVM.show = this.toastTimer = false;
        },option.duration)
      }
      return this.toastVM;
    }

    /** $loading加载
     * @params {Object|String} config 配置,如果为字符串=message
     * */
    Vue.prototype.$loading = (config) => {
      let option = {};
      let self = this;
      if(typeof config === 'object') {
        Object.assign(option,config)
      } else { // 传递的字符串
        option.message = config;
      }

      if(option.type == 'close') {
        if(this.loadNode) {
           this.loadNode.show = this.showLoad = false;
        }
      } else {
        if(this.showLoad && this.loadNode) {
          this.loadNode.message = option.message;
          return false;
        }
        const loadEl = Vue.extend({
          data() {
            return {
              show: false,
              message: option.message
            }
          },
          methods: {
            close() {
              self.loadNode.show = self.showLoad = false;
            }
          },
          render(h) {
            if(!this.show) {
              return false;
            }
            return h('div',{
              class: 'lx-load-mark',
              show: this.show
            },[
              h('div',{
                class: 'lx-load-box'
              },[
                h('div',{
                  class: this.message ? 'lx-loading':'lx-loading-nocontent'
                },Array.apply(null,{length: 12}).map((value,index) => {
                  return h('div',{
                    class: ['loading_leaf',`loading_leaf_${index}`]
                  })
                })),
                h('div',{
                  class: 'lx-load-content',
                  domProps: {
                    innerHTML: this.message
                  }
                })
              ])
            ])
          }
        })

        this.loadNode = new loadEl();
        document.body.appendChild(this.loadNode.$mount().$el);
        this.loadNode.show = this.showLoad = true;
      }
      return this.loadNode;
    }

    ['open','close'].forEach(type => {
      Vue.prototype.$loading[type] = (message) => {
        return Vue.prototype.$loading({type,message})
      }
    })
  }
}

export default new Toast();

完整的调用方式:

<template>
  <div class="test">
    <button @click="open">开启弹框</button>
    <button @click="close">关闭弹框</button>
  </div>
</template>

<script>
export default {
  data () {
    return {
      toast: null
    }
  },

  mounted() {
    let loading = this.$loading({
      message: '这是loading文字'
    })

    setTimeout(() => { // 5s 后关闭弹框
      loading.close();
    },5000);
  },

  methods: {
    open() {
      this.toast = this.$toast({
        message: '这是一段信息',
        position: 'top',
        duration: 0
      });
    },
    close() {
      this.toast.close();
    }
  }
}
</script>

 toast.css代码:

.lx-toast {
    position: fixed;
    bottom: 100px;
    left: 50%;
    -webkit-box-sizing: border-box;
            box-sizing: border-box;
    max-width: 80%;
    height: 40px;
    line-height: 20px;
    padding: 10px 20px;
    transform: translateX(-50%);
    -webkit-transform: translateX(-50%);
    text-align: center;
    z-index: 9999;
    font-size: 14px;
    color: #fff;
    border-radius: 5px;
    background: rgba(0, 0, 0, 0.7);
    animation: show-toast .5s;
    -webkit-animation: show-toast .5s;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.lx-toast.lx-word-wrap {
    width: 80%;
    white-space: inherit;
    height: auto;
}

.lx-toast.lx-toast-top {
    top: 50px;
    bottom: inherit;
}

.lx-toast.lx-toast-center {
    top: 50%;
    margin-top: -20px;
    bottom: inherit;
}

@-webkit-keyframes show-toast {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

@keyframes show-toast {
    from {
        opacity: 0;
    }
    to {
        opacity: 1;
    }
}

.lx-load-mark {
    position: fixed;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 9999;
}

.lx-load-box {
    position: fixed;
    z-index: 3;
    width: 7.6em;
    min-height: 7.6em;
    top: 180px;
    left: 50%;
    margin-left: -3.8em;
    background: rgba(0, 0, 0, 0.7);
    text-align: center;
    border-radius: 5px;
    color: #FFFFFF;
}

.lx-load-content {
    margin-top: 64%;
    font-size: 14px;
}

.lx-loading, .lx-loading-nocontent {
    position: absolute;
    width: 0px;
    left: 50%;
    top: 38%;
}

.lx-loading-nocontent {
    top: 50%;
}

.loading_leaf {
    position: absolute;
    top: -1px;
    opacity: 0.25;
}

.loading_leaf:before {
    content: " ";
    position: absolute;
    width: 9.14px;
    height: 3.08px;
    background: #d1d1d5;
    -webkit-box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px;
            box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px;
    border-radius: 1px;
    -webkit-transform-origin: left 50% 0px;
    transform-origin: left 50% 0px;
}

.loading_leaf_0 {
    -webkit-animation: opacity-0 1.25s linear infinite;
    animation: opacity-0 1.25s linear infinite;
}

.loading_leaf_0:before {
    -webkit-transform: rotate(0deg) translate(7.92px, 0px);
    transform: rotate(0deg) translate(7.92px, 0px);
}

.loading_leaf_1 {
    -webkit-animation: opacity-1 1.25s linear infinite;
    animation: opacity-1 1.25s linear infinite;
}

.loading_leaf_1:before {
    -webkit-transform: rotate(30deg) translate(7.92px, 0px);
    transform: rotate(30deg) translate(7.92px, 0px);
}

.loading_leaf_2 {
    -webkit-animation: opacity-2 1.25s linear infinite;
    animation: opacity-2 1.25s linear infinite;
}

.loading_leaf_2:before {
    -webkit-transform: rotate(60deg) translate(7.92px, 0px);
    transform: rotate(60deg) translate(7.92px, 0px);
}

.loading_leaf_3 {
    -webkit-animation: opacity-3 1.25s linear infinite;
    animation: opacity-3 1.25s linear infinite;
}

.loading_leaf_3:before {
    -webkit-transform: rotate(90deg) translate(7.92px, 0px);
    transform: rotate(90deg) translate(7.92px, 0px);
}

.loading_leaf_4 {
    -webkit-animation: opacity-4 1.25s linear infinite;
    animation: opacity-4 1.25s linear infinite;
}

.loading_leaf_4:before {
    -webkit-transform: rotate(120deg) translate(7.92px, 0px);
    transform: rotate(120deg) translate(7.92px, 0px);
}

.loading_leaf_5 {
    -webkit-animation: opacity-5 1.25s linear infinite;
    animation: opacity-5 1.25s linear infinite;
}

.loading_leaf_5:before {
    -webkit-transform: rotate(150deg) translate(7.92px, 0px);
    transform: rotate(150deg) translate(7.92px, 0px);
}

.loading_leaf_6 {
    -webkit-animation: opacity-6 1.25s linear infinite;
    animation: opacity-6 1.25s linear infinite;
}

.loading_leaf_6:before {
    -webkit-transform: rotate(180deg) translate(7.92px, 0px);
    transform: rotate(180deg) translate(7.92px, 0px);
}

.loading_leaf_7 {
    -webkit-animation: opacity-7 1.25s linear infinite;
    animation: opacity-7 1.25s linear infinite;
}

.loading_leaf_7:before {
    -webkit-transform: rotate(210deg) translate(7.92px, 0px);
    transform: rotate(210deg) translate(7.92px, 0px);
}

.loading_leaf_8 {
    -webkit-animation: opacity-8 1.25s linear infinite;
    animation: opacity-8 1.25s linear infinite;
}

.loading_leaf_8:before {
    -webkit-transform: rotate(240deg) translate(7.92px, 0px);
    transform: rotate(240deg) translate(7.92px, 0px);
}

.loading_leaf_9 {
    -webkit-animation: opacity-9 1.25s linear infinite;
    animation: opacity-9 1.25s linear infinite;
}

.loading_leaf_9:before {
    -webkit-transform: rotate(270deg) translate(7.92px, 0px);
    transform: rotate(270deg) translate(7.92px, 0px);
}

.loading_leaf_10 {
    -webkit-animation: opacity-10 1.25s linear infinite;
    animation: opacity-10 1.25s linear infinite;
}

.loading_leaf_10:before {
    -webkit-transform: rotate(300deg) translate(7.92px, 0px);
    transform: rotate(300deg) translate(7.92px, 0px);
}

.loading_leaf_11 {
    -webkit-animation: opacity-11 1.25s linear infinite;
    animation: opacity-11 1.25s linear infinite;
}

.loading_leaf_11:before {
    -webkit-transform: rotate(330deg) translate(7.92px, 0px);
    transform: rotate(330deg) translate(7.92px, 0px);
}

@-webkit-keyframes opacity-0 {
    0% {
        opacity: 0.25;
    }
    0.01% {
        opacity: 0.25;
    }
    0.02% {
        opacity: 1;
    }
    60.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes opacity-0 {
    0% {
        opacity: 0.25;
    }
    0.01% {
        opacity: 0.25;
    }
    0.02% {
        opacity: 1;
    }
    60.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@-webkit-keyframes opacity-1 {
    0% {
        opacity: 0.25;
    }
    8.34333% {
        opacity: 0.25;
    }
    8.35333% {
        opacity: 1;
    }
    68.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes opacity-1 {
    0% {
        opacity: 0.25;
    }
    8.34333% {
        opacity: 0.25;
    }
    8.35333% {
        opacity: 1;
    }
    68.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@-webkit-keyframes opacity-2 {
    0% {
        opacity: 0.25;
    }
    16.6767% {
        opacity: 0.25;
    }
    16.6867% {
        opacity: 1;
    }
    76.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes opacity-2 {
    0% {
        opacity: 0.25;
    }
    16.6767% {
        opacity: 0.25;
    }
    16.6867% {
        opacity: 1;
    }
    76.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@-webkit-keyframes opacity-3 {
    0% {
        opacity: 0.25;
    }
    25.01% {
        opacity: 0.25;
    }
    25.02% {
        opacity: 1;
    }
    85.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes opacity-3 {
    0% {
        opacity: 0.25;
    }
    25.01% {
        opacity: 0.25;
    }
    25.02% {
        opacity: 1;
    }
    85.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@-webkit-keyframes opacity-4 {
    0% {
        opacity: 0.25;
    }
    33.3433% {
        opacity: 0.25;
    }
    33.3533% {
        opacity: 1;
    }
    93.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@keyframes opacity-4 {
    0% {
        opacity: 0.25;
    }
    33.3433% {
        opacity: 0.25;
    }
    33.3533% {
        opacity: 1;
    }
    93.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.25;
    }
}

@-webkit-keyframes opacity-5 {
    0% {
        opacity: 0.270958333333333;
    }
    41.6767% {
        opacity: 0.25;
    }
    41.6867% {
        opacity: 1;
    }
    1.67667% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.270958333333333;
    }
}

@keyframes opacity-5 {
    0% {
        opacity: 0.270958333333333;
    }
    41.6767% {
        opacity: 0.25;
    }
    41.6867% {
        opacity: 1;
    }
    1.67667% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.270958333333333;
    }
}

@-webkit-keyframes opacity-6 {
    0% {
        opacity: 0.375125;
    }
    50.01% {
        opacity: 0.25;
    }
    50.02% {
        opacity: 1;
    }
    10.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.375125;
    }
}

@keyframes opacity-6 {
    0% {
        opacity: 0.375125;
    }
    50.01% {
        opacity: 0.25;
    }
    50.02% {
        opacity: 1;
    }
    10.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.375125;
    }
}

@-webkit-keyframes opacity-7 {
    0% {
        opacity: 0.479291666666667;
    }
    58.3433% {
        opacity: 0.25;
    }
    58.3533% {
        opacity: 1;
    }
    18.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.479291666666667;
    }
}

@keyframes opacity-7 {
    0% {
        opacity: 0.479291666666667;
    }
    58.3433% {
        opacity: 0.25;
    }
    58.3533% {
        opacity: 1;
    }
    18.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.479291666666667;
    }
}

@-webkit-keyframes opacity-8 {
    0% {
        opacity: 0.583458333333333;
    }
    66.6767% {
        opacity: 0.25;
    }
    66.6867% {
        opacity: 1;
    }
    26.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.583458333333333;
    }
}

@keyframes opacity-8 {
    0% {
        opacity: 0.583458333333333;
    }
    66.6767% {
        opacity: 0.25;
    }
    66.6867% {
        opacity: 1;
    }
    26.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.583458333333333;
    }
}

@-webkit-keyframes opacity-9 {
    0% {
        opacity: 0.687625;
    }
    75.01% {
        opacity: 0.25;
    }
    75.02% {
        opacity: 1;
    }
    35.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.687625;
    }
}

@keyframes opacity-9 {
    0% {
        opacity: 0.687625;
    }
    75.01% {
        opacity: 0.25;
    }
    75.02% {
        opacity: 1;
    }
    35.01% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.687625;
    }
}

@-webkit-keyframes opacity-10 {
    0% {
        opacity: 0.791791666666667;
    }
    83.3433% {
        opacity: 0.25;
    }
    83.3533% {
        opacity: 1;
    }
    43.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.791791666666667;
    }
}

@keyframes opacity-10 {
    0% {
        opacity: 0.791791666666667;
    }
    83.3433% {
        opacity: 0.25;
    }
    83.3533% {
        opacity: 1;
    }
    43.3433% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.791791666666667;
    }
}

@-webkit-keyframes opacity-11 {
    0% {
        opacity: 0.895958333333333;
    }
    91.6767% {
        opacity: 0.25;
    }
    91.6867% {
        opacity: 1;
    }
    51.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.895958333333333;
    }
}

@keyframes opacity-11 {
    0% {
        opacity: 0.895958333333333;
    }
    91.6767% {
        opacity: 0.25;
    }
    91.6867% {
        opacity: 1;
    }
    51.6767% {
        opacity: 0.25;
    }
    100% {
        opacity: 0.895958333333333;
    }
}

参考

posted @ 2021-02-18 15:02  风雨后见彩虹  阅读(142)  评论(0编辑  收藏