js的api方式实现vue消息通知弹框

1,273 阅读3分钟

在平常,我们使用vue组件的时候,都要先在.vue文件中引入我们要使用的组件。虽然这样能满足大部分日常开发的需求,但这种方法在某些场景下,就有些难以应对。

  1. 组件的模板是通过调用接口从服务端获取的,需要动态渲染组件;
  2. 实现类似原生 window.alert() 的提示框组件,它的位置是在 下,而非
    ,并且不会通过常规的组件自定义标签的形式使用,而是像 JS 调用函数一样使用。

extend

Vue.extend 的作用,就是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue 的基本一样,但 data 要跟组件一样,是个函数,再配合 $mount ,就可以让组件渲染,并且挂载到任意指定的节点上,比如 body。

import Vue from 'vue';

<!-- 创建了一个构造器,这个过程就可以解决异步获取 template 模板的问题 -->
const AlertComponent = Vue.extend({
  template: '<div>{{ message }}</div>',
  data () {
    return {
      message: 'Hello, Aresn'
    };
  },
});
<!-- 调用 $mount 方法对组件进行了手动渲染, -->
const component = new AlertComponent().$mount();
<!-- 挂载节点 -->
document.body.appendChild(component.$el);
<!-- 快捷的挂载方式 -->
new AlertComponent().$mount('#app');

这样就可以满足我们用js方法调用的方式来控制组件,但是在平常的开发中,我们用的vue的runtime编译环境,不支持template模板,而且用字符串来描述组件的模板,也有些不太友好。那么,我们可以试试用new Vue()的方式。

new Vue

new Vue()也可以直接创建 Vue 实例,并且用一个 Render 函数来渲染一个 .vue 文件。

import Vue from 'vue';
import Notification from './notification.vue';

const props = {};  // 这里可以传入一些组件的 props 选项

const Instance = new Vue({
  render (h) {
    return h(Notification, {
      props: props
    });
  }
});

const component = Instance.$mount();
document.body.appendChild(component.$el);

这样既可以使用 .vue 来写复杂的组件,还可以根据需要传入适当的 props。渲染后,如果想操作 Render 的 Notification 实例,也是很简单的:

const notification = Instance.$children[0];

实例

接下来就给大家,看下我在项目中用new Vue()的方式来实现的弹框组件。首先我们要新建一个vue文件来,完成弹框的基本布局和样式。

modal-dialog 此文件实现了弹框的基本布局,蒙层和关闭事件,弹框内容可以通过插槽来控制,支持的props有width,height,bgSrc(弹框的背景图片),visible,hasClose(是否有关闭‘X’)

<template>
  <transition name="fade">
    <div
      style="position:fixed;top: 0;left: 0;width:100vw;height:100vh;z-index:100;"
      v-if="isShow"
    >
      <div class="mask" @click.stop="closeDialog"></div>
      <div
        class="dialog"
        :class="classObject"
        :style="{
          width,
          height,
          backgroundImage: `url(${bgSrc})`,
          backgroundRepeat: 'no-repeat',
          ...wrapStyle
        }"
      >
        <slot></slot>
        <div
          v-if="hasClose"
          class="close"
          @click.stop="closeDialog"
          :style="closeStyle"
        ></div>
      </div>
    </div>
  </transition>
</template>
<script>
export default {
  name: "modalDialog",
  props: {
    visible: {
      type: Boolean,
      default: false
    },
    width: {
      type: String,
      default: "6.52rem"
    },
    bgSrc: {
      type: String,
      required: true
    },
    height: {
      type: String,
      default: "6.83rem"
    },
    hasClose: {
      type: Boolean,
      default: true
    },
    wrapClass: {
      type: String,
      default: ""
    },
    wrapStyle: {
      type: Object,
      default: function() {
        return {};
      }
    },
    closeStyle: {
      type: Object,
      default: function() {
        return {};
      }
    }
  },
  watch: {
    visible(val) {
      <!-- 同步visible的变化 -->
      this.isShow = val;
    }
  },
  data() {
    return {
      <!-- vue单向数据流,因此我们重新定义一个属性isShow -->
      isShow: this.visible,
      classObject: {
        [this.wrapClass]: true
      }
    };
  },
  created() {},
  methods: {
    closeDialog() {
      this.isShow = false;
      <!-- 通知父组件 visible的变化   父组件使用.sync修饰符即可 -->
      this.$emit("update:visible", false);
    }
  }
};
</script>
<style lang="less" scoped>
@imgPath: "../assets/img/dialog";
.translateXCenter {
  left: 50%;
  transform: translateX(-50%) !important;
  -webkit-transform: translateX(-50%) !important;
}
.translateCenter {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) !important;
  -webkit-transform: translate(-50%, -50%) !important;
}
.translateY(@y) {
  left: 50%;
  top: 50%;
  transform: translate(-50%, @y);
  -webkit-transform: translate(-50%, @y);
}
.background(@imgName) {
  background: url("@{imgPath}/@{imgName}") no-repeat;
  background-size: 100% auto;
}

.mask {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(10, 13, 24, 0.8);
  z-index: 100;
}

.dialog {
  // background-repeat: no-repeat
  background-size: 100% auto;
  position: absolute;
  z-index: 100;
  // .translateXCenter;
  .translateCenter;
}

.close {
  width: 0.56rem;
  height: 0.56rem;
  position: absolute;
  top: -0.4rem;
  right: 0rem;
  .background("btn_close.png");
}
.fade-enter-active,
.fade-leave-active {
  transition: all 0.2s ease-in;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}
.fade-enter-to,
.fade-leave {
  opacity: 1;
}
</style>

弹框的基本功能实现后,我们就用它来建一个常用的通知组件 alert-box。文件中的图片大家自行替换呀。样式方面也请自行调整呀。

<template>
  <modal-dialog
  <!-- 这里一定要加sync修饰符 -->
    :visible.sync="isShow"
    width="6.7rem"
    height="5.51rem"
    :bgSrc="require('@img/dialog/tuan_bg.png')"
  >
    <div class="content">
      <ul>
        <template v-if="textArr.length > 0">
          <li v-for="(text, index) in textArr" :key="index">{{ text }}</li>
        </template>
      </ul>
      <div class="btn-wrap">
        <div class="konw" @click="hideBox"></div>
      </div>
    </div>
  </modal-dialog>
</template>
<script>
import ModalDialog from "@/components/modalDialog.vue";

export default {
  name: "AlertBox",
  props: {
    <!-- 显示隐藏 -->
    visible: {
      type: Boolean,
      default: false
    },
    <!-- 消息的内容 -->
    textArr: {
      type: Array,
      default() {
        return [];
      }
    }
  },
  components: {
    ModalDialog
  },
  data: function() {
    return {
      <!-- props单向数据流,组件内部用isShow记录组件状态,最后同步给父级 -->
      isShow: this.visible,
    };
  },
  methods: {
    hideBox() {
      this.isShow = false;
    }
  },
  watch: {
    visible(newval) {
      this.isShow = newval;
    },
    isShow(newval) {
      <!-- 通知父组件,同步组件显示隐藏状态 -->
      this.$emit("update:visible", newval);
    }
  }
};
</script>
<style lang="less" scoped>
@imgPath: "../assets/img/dialog";
.background(@imgName) {
  background: url("@{imgPath}/@{imgName}") no-repeat;
  background-size: 100% auto;
}
.translateCenter {
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) !important;
  -webkit-transform: translate(-50%, -50%) !important;
}
.content {
  width: 100%;
  height: 100%;
  position: relative;
  ul {
    position: absolute;
    // background-color: skyblue;
    width: 5.24rem;
    height: 2.83rem;
    color: #914e4c;
    font-size: 0.32rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    top: 0.9rem;
    left: 0.76rem;
    // font-weight: 600;
    li {
      text-align: justify;
    }
  }
  .btn-wrap {
    position: absolute;
    top: 4.2rem;
    width: 100%;
    .konw {
      width: 2.66rem;
      height: 1.05rem;
      margin: 0 auto;
      display: block;
      .background("btn_know.png");
    }
  }
}
</style>

最后,是重头戏,如何将这个alertBox的实例挂载在vue的原型上呢?下面我们用new Vue来实现 alert.js

import Vue from "vue";
import AlertBox from "./alertBox.vue";

let contain = document.body,
  messageInstance = null,
  component = null;

AlertBox.newInstance = properties => {
  const props = properties || {};
  //  props  : visible ; textType 传入props visible,我们还可以传入很多的props 详情查看官方文档
  const Instance = new Vue({
    render(h) {
      return h(AlertBox, {
        props,
        nativeOn: {
          "update:visible": function(val) {
            // 监听alerBox isShow变化的事件,如果组件隐藏,我们移除挂载节点,并销毁实例
            if (!val) {
              contain.removeChild(component.$el);
              Instance.$destroy();
            }
          }
        }
      });
    }
  });
  // 组件渲染
  component = Instance.$mount();
  // 组件挂载
  contain.appendChild(component.$el);
};

// 保证单一实例
function getMessageInstance(props) {
  messageInstance = messageInstance || AlertBox.newInstance(props);
}

export default {
  // 暴露info方法,后面可以增加新的方法
  info(props) {
    return getMessageInstance(props);
  }
};

最后我们在项目的main.js文件引入alert.js

import MyAlert from "./components/alert";
Vue.prototype.$myAlert = MyAlert;

就可以在vue文件中,用 this.$myAlert.info() 实现通知弹框了。

this.$myAlert.info({
  visible: true,
  textArr: ["团队解散失败,请重试"]
});