Toast

写一个普通的vue组件大家应该都很熟悉,定义、注册、使用一顿行云流水的操作就出来了。今天来整一个可自定义的toast组件试试?

toast组件需要具备的功能:

1、一次注册满"世界"用

2、可自定义内容

直接开始撸起来

创建vue组件

首先在lib目录下创建toast文件,并且新建toast.vue和index.js

toast大小、尺寸、位置应该由调用方自行修改,因为我们这边在toast外层绑定一些style属性

<!-- toast.vue -->
<template>
  <div class="wrap"
       v-if="show"
       :class="{'icon-center': type===1, 'fadein': show, 'fadeout': !show}"
       :style="{left,top,bottom,right,width,fontSize}">
    <!-- 
        toast默认显示为水平垂直居中,即left,top,bottom,right,width,fontSize需要存在默认值,
    后面在index.js里设置.
    -->
    <!-- code code code -->
  </div>
</template>

icon图标,我们有些项目可能是在成功、失败、警告等情况下调用,这时候就需要使用下icon,这边也支持一下这功能

<!-- 
    toast.vue    
    这里引用了一个svg图标,来标识成功信息提示。
    hasIcon是一个计算属性,根据error的值来决定是否要显示icon
    error: -1(不显示)、0(成功)、1(错误)....
-->
<div class="toast-icon"
         v-if="hasIcon">
      <svg t="1568117267444"
           class="icon"
           viewBox="0 0 1024 1024"
           version="1.1"
           xmlns="http://www.w3.org/2000/svg"
           p-id="1990"
           width="32"
           height="32"
           v-if="error===0">
        <path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333z m-74.965333 550.4L346.453333 545.152a42.666667 42.666667 0 1 0-60.330666 60.330667l120.704 120.704a42.666667 42.666667 0 0 0 60.330666 0l301.653334-301.696a42.666667 42.666667 0 1 0-60.288-60.330667l-271.530667 271.488z"
              fill="#52C41A"
              p-id="1991"></path>
      </svg>
</div>

<script>
export default {
  computed: {
    hasIcon() {
      return [0, 1, 2].includes(this.error);
    }
  }
};
</script>

剩下的就是toast文字部分了,看起来很简单,但是这里的水还是有点深的,传递一个纯文本那是相当滴轻松直接...

<div class="tips-text">
    <div v-if="text">{{ text }}</div>
</div>

夸夸夸一行代码搞定,以为万事大吉。过两天产品改个需求,说提示框文字能不能改个颜色、下划线什么的,诶,简单,老子十秒钟给你解决

<div class="tips-text">
    <div v-if="text" v-html="text"></div>
</div>

这么一来我们这个toast支持html标签,完美,产品你有什么需求尽管改。自信过后产品说toast要能够支持点击跳转(。。。。。。。。。。。掏出二维码,扫码改需求)。那我们再优化一下

<div class="tips-text">
      <div v-html="text" v-if="text"></div>
        <!-- toast-template作为一个用户自定义节点 -->
      <div id="toast-template"></div>
        <!-- assist多行提示内容 -->
      <div class="assist"
           v-show="type === 2">{{ assist }}</div>
</div>

<script>
//index.js
// custom字段为用户传入的vue组件对象包含data、methods等等
let hvue = Vue.extend(custom);
// 挂载模板
let components = new hvue().$mount().$el;
this.$nextTick(() => {
  // 插入到toast-template节点里面
  document.getElementById('toast-template').appendChild(components);
})
</script>

这么一来这个toast组件就可以支持任何vue能干事情,高度可自定义

配置参数

function showToast({
  text,    // 提示文本
  duration = 1500,    // toast几秒后关闭时长
  error = -1,    // icon类型,默认不显示,0-成功。。。。
  container = 'body', // toast相对容器。toast是绝对定位,需要一个相对容器,默认body
  left = '50%',    // 距离相对容器左边距
  right = 'auto',    // 距离相对容器右边距
  top = '50%',    // 距离相对容器上边距
  bottom = 'auto',    // 距离相对容器下边距
  type = 1,    // 是否多行提示,默认单行,type=2为多行,第二行文字小一号
  width = 'auto',    // toast宽度
  assist = '',    // 多行提示第二行文本内容
  fontSize = '12px',
  custom = ''    // vue组件对象
} = {}) {
  // 实例化
  const toastDom = new ToatConatructor({
    el: document.createElement('div'),
    data() {
      return {
        show: true,
        text,
        error,
        left,
        bottom,
        right,
        top,
        width,
        type,
        assist,
        fontSize,
        custom
      };
    },
    mounted() {
      // 自定义模板
      if (custom) {
        let hvue = Vue.extend(custom);
        // 挂载模板
        let components = new hvue().$mount().$el;
        this.$nextTick(() => {
          document.getElementById('toast-template').appendChild(components);
        })
      }
    }
  });

  // 插入节点
  createDom(container, toastDom)

  // 过了duration后消失
  setTimeout(() => {
    toastDom.show = false;
    // 优化,销毁节点
    deleteDom(container, toastDom);
  }, duration);
}

创建和销毁节点

/**
 * 创建节点
 * @param {*} container 支持传入父容器class、id以及节点
 * @param {*} toastDom 需要插入的节点
 */
function createDom(container, toastDom) {
  if (typeof container == 'string') {
    document.querySelector(container) && document.querySelector(container).appendChild(toastDom.$el);
  } else {
    container.appendChild(toastDom.$el);
  }
}

/**
 * 销毁节点
 * @param {*} container 
 * @param {*} toastDom 
 */
function deleteDom(container, toastDom) {
  if (typeof container == 'string') {
    document.querySelector(container) && document.querySelector(container).removeChild(toastDom.$el);
  } else {
    container.removeChild(toastDom.$el);
  }
}

注册组件

import Vue from 'vue';
import toastComponent from './toast.vue';

const ToatConatructor = vue.extend(toastComponent);

/**
 * 全局挂载节点
 */
function registryToast() {
  Vue.prototype.$toast = showToast;
}
export default registryToast;

使用

首先在main.js里面引入toast

import toast from '@/lib/toast'    // 路径按照自己的来,我这里放在lib目录下
Vue.use(toast)

剩下就是调用,调用,调用

<template>
  <div class="home">
    <div @click="error"
         style="color: #fff;">唤起Toast</div>
  </div>
</template>

<script>

export default {
  name: 'home',
  methods: {
    error() {
      this.$toast({
           container: '.home',
        text: 'aaaaa',
        error: 0
      });
    }
  }
};
</script>

到这里toast已经撸完了,最后贴一波完整代码吧

<template>
  <div class="wrap"
       v-if="show"
       :class="{'icon-center': type===1, 'fadein': show, 'fadeout': !show}"
       :style="{left,top,bottom,right,width,fontSize}">
    <div class="toast-icon"
         v-if="hasIcon">
      <svg t="1568117267444"
           class="icon"
           viewBox="0 0 1024 1024"
           version="1.1"
           xmlns="http://www.w3.org/2000/svg"
           p-id="1990"
           width="32"
           height="32"
           v-if="error===0">
        <path d="M512 85.333333c235.648 0 426.666667 191.018667 426.666667 426.666667s-191.018667 426.666667-426.666667 426.666667S85.333333 747.648 85.333333 512 276.352 85.333333 512 85.333333z m-74.965333 550.4L346.453333 545.152a42.666667 42.666667 0 1 0-60.330666 60.330667l120.704 120.704a42.666667 42.666667 0 0 0 60.330666 0l301.653334-301.696a42.666667 42.666667 0 1 0-60.288-60.330667l-271.530667 271.488z"
              fill="#52C41A"
              p-id="1991"></path>
      </svg>
    </div>
    <div class="tips-text">
      <div v-html="text"
           v-if="text"></div>
      <div id="toast-template"></div>
      <div class="assist"
           v-show="type === 2">{{ assist }}</div>
    </div>
  </div>
</template>

<script>
export default {
  computed: {
    hasIcon() {
      return [0, 1, 2].includes(this.error);
    }
  }
};
</script>

<style scoped>
.wrap {
  position: absolute;
  background-color: #fff;
  padding: 10px 15px;
  border-radius: 3px;
  color: #000;
  transform: translate(-50%, -50%);
  box-shadow: 1px 1px 2px 2px rgba(204, 204, 204, 0.2);
  font-size: 12px;
  display: flex;
  z-index: 10019;
  justify-content: center;
  box-shadow: 1px 1px 1px #ddd;
  transform: translate(-50%, -50%);
}

.toast-icon {
  color: #fff;
  border-radius: 50%;
  width: 1.5em;
  height: 1.5em;
  line-height: 1.45em;
  text-align: center;
  flex-shrink: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  margin: 0 0.85em 0 0;
}

.icon-center {
  align-items: center;
}

.tips-text {
  font-size: 1em;
  color: #3a4f66;
}

.assist {
  font-size: 0.8em;
  margin: 0.3em 0 0 0;
  color: #8997a4;
}

.fadein {
  animation: animate_in 0.25s;
}

.fadeout {
  animation: animate_out 0.25s;
  opacity: 0;
}

@keyframes animate_in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}

@keyframes animate_out {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
  }
}
</style>

index.js

import toastComponent from './toast.vue';
import Vue from 'vue';

const ToatConatructor = Vue.extend(toastComponent);

function showToast({
  text,
  duration = 30000,
  error = -1,
  container = 'body',
  left = '50%',
  right = 'auto',
  top = '50%',
  bottom = 'auto',
  type = 1,
  width = 'auto',
  assist = '',
  fontSize = '12px',
  custom = ''
} = {}) {
  // 实例化
  const toastDom = new ToatConatructor({
    el: document.createElement('div'),
    data() {
      return {
        show: true,
        text,
        error,
        left,
        bottom,
        right,
        top,
        width,
        type,
        assist,
        fontSize,
        custom
      };
    },
    mounted() {
      // 自定义模板
      if (custom) {
        let hvue = Vue.extend(custom);
        // 挂载模板
        let components = new hvue().$mount().$el;
        this.$nextTick(() => {
          document.getElementById('toast-template').appendChild(components);
        })
      }
    }
  });

  // 插入节点
  createDom(container, toastDom)

  // 过了duration后消失
  setTimeout(() => {
    toastDom.show = false;
    // 优化,销毁节点
    deleteDom(container, toastDom);
  }, duration);
}

/**
 * 创建节点
 * @param {*} container 支持传入父容器class、id以及节点
 * @param {*} toastDom 需要插入的节点
 */
function createDom(container, toastDom) {
  // console.warn(container, toastDom, toastDom.$el, document.querySelector(container));
  if (typeof container == 'string') {
    document.querySelector(container) && document.querySelector(container).appendChild(toastDom.$el);
  } else {
    container.appendChild(toastDom.$el);
  }
}

/**
 * 销毁节点
 * @param {*} container 
 * @param {*} toastDom 
 */
function deleteDom(container, toastDom) {
  if (typeof container == 'string') {
    document.querySelector(container) && document.querySelector(container).removeChild(toastDom.$el);
  } else {
    container.removeChild(toastDom.$el);
  }
}

/**
 * 全局挂载节点
 */
function registryToast() {
  Vue.prototype.$toast = showToast;
}

export default registryToast;

一念之差
113 声望2 粉丝

day day no bug