欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

利用 Vue.extend写一个全局toast提示

程序员文章站 2022-03-11 21:41:31
...

此人很懒,就不写文字描述了,这篇文章是基于饿了么的message提示框做的一个修改,请悉知

 

效果:

利用 Vue.extend写一个全局toast提示

 

话不多说,上代码

toast.vue

<!--
 @description toast提示框
 @author: hruomei
 @update 
 @date: 2020-12-03 17:41:04
-->

<template>
  <transition name="ilink-toast-fade" @after-leave="handleAfterLeave">
    <div
      :class="[
        'ilink-toast',
        type && !iconClass ? `ilink-toast--${type}` : '',
        customClass,
      ]"
      :style="positionStyle"
      v-show="visible"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
      role="toast"
    >
      <i :class="iconClass" v-if="iconClass"></i>
      <i :class="typeClass" v-else></i>
      <slot>
        <p v-if="!dangerouslyUseHTMLString" class="ilink-toast__content">
          {{ message }}
        </p>
        <p v-else v-html="message" class="ilink-toast__content"></p>
      </slot>
      <i v-if="showClose" class="ilink-toast__closeBtn el-icon-close" @click="close"></i>
    </div>
  </transition>
</template>

<script type="text/babel">
const typeMap = {
    success: "success",
    info: "info",
    warning: "warning",
    error: "error",
};

export default {
    data() {
        return {
            visible: false,
            message: "",
            duration: 3000,
            type: "info",
            iconClass: "",
            customClass: "",
            onClose: null,
            showClose: false,
            closed: false,
            verticalOffset: 20,
            timer: null,
            dangerouslyUseHTMLString: false
        };
    },

    computed: {
        typeClass() {
            return this.type && !this.iconClass
                ? `ilink-toast__icon el-icon-${typeMap[this.type]}`
                : "";
        },
        positionStyle() {
            return {
                top: `${this.verticalOffset}px`,
            };
        },
    },

    watch: {
        closed(newVal) {
            if (newVal) {
                this.visible = false;
            }
        },
    },

    methods: {
        handleAfterLeave() {
            this.$destroy(true);
            this.$el.parentNode.removeChild(this.$el);
        },

        close() {
            this.closed = true;
            if (typeof this.onClose === "function") {
                this.onClose(this);
            }
        },

        clearTimer() {
            clearTimeout(this.timer);
        },

        startTimer() {
            if (this.duration > 0) {
                this.timer = setTimeout(() => {
                    if (!this.closed) {
                        this.close();
                    }
                }, this.duration);
            }
        },

        keydown(e) {
            if (e.keyCode === 27) { // esc关闭消息
                if (!this.closed) {
                    this.close();
                }
            }
        },
    },

    mounted() {
        this.startTimer();
        document.addEventListener("keydown", this.keydown);
    },

    beforeDestroy() {
        document.removeEventListener("keydown", this.keydown);
    },
};
</script>

<style lang="scss">
.ilink-toast {
    $color-info: #0D76FF;
    $color-waring: #E6A23C;
    $color-error: #F56C6C;
    $color-success: #67C23A;

    max-width: 380px;
    box-sizing: border-box;
    position: fixed;
    left: 50%;
    top: 20px;
    transform: translateX(-50%);
    background: #E5F1FF;
    opacity: 0.9;
    border-radius: 4px;
    transition: opacity .1s, transform .2s, top .2s;
    transition-timing-function: cubic-bezier(.45,.48,.49,.98);
    overflow: hidden;
    padding: 12px 17px;
    display: flex;
    align-items: center;

    p {
        margin: 0;
    }

    &__icon {
        margin-right: 8px;
        font-size: 14px;
    }

    &__content {
        padding: 0;
        font-size: 12px;
        line-height: 1;
        color: #18191A;
    }

    &__closeBtn {
        cursor: pointer;
        color: #9dabc7;
        font-size: 14px;
        margin-left: 10px;

        &:hover {
            color: #909399;
        }
    }

    .el-icon-success {
        color: $color-success
    }

    .el-icon-error {
        color: $color-error
    }

    .el-icon-info {
        color: $color-info;
    }

    .el-icon-warning {
        color: $color-waring;
    }
}

.ilink-toast-fade-enter,
.ilink-toast-fade-leave-active {
    opacity: 0;
    transform: translate(-50%, -100%);
}
</style>

 

toast.js

/**
 * @description toast 提示
 * @author: hruomei
 * @update 
 * @date: 2020-12-03 17:42:08
 */

import Vue from 'vue';
import ToastComponent from './index.vue';
let ToastConstructor = Vue.extend(ToastComponent);

function isVNode(node) {
  return node !== null && typeof node === 'object' && Object.prototype.hasOwnProperty.call(node, 'componentOptions');
};

let instance;
let instances = [];
let seed = 1;

const Toast = function (options) {
    options = options || {};
    if (typeof options === 'string') {
        options = {
            message: options
        };
    }
    let userOnClose = options.onClose;
    let id = 'toast_' + seed++;

    options.onClose = function () {
        Toast.close(id, userOnClose);
    };
    instance = new ToastConstructor({
        data: options
    });
    instance.id = id;
    if (isVNode(instance.message)) {
        instance.$slots.default = [instance.message];
        instance.message = null;
    }
    instance.$mount();
    document.body.appendChild(instance.$el);

    let verticalOffset = options.offset || 20;
    instances.forEach(item => {
        verticalOffset += item.$el.offsetHeight + 16;
    });
    instance.verticalOffset = verticalOffset;
    instance.visible = true;
    instance.$el.style.zIndex = 2000;
    instances.push(instance);
    return instance;
};

['success', 'warning', 'info', 'error'].forEach(type => {
    Toast[type] = options => {
        if (typeof options === 'string') {
            options = {
                message: options
            };
        }
        options.type = type;
        return Toast(options);
    };
});

Toast.close = function (id, userOnClose) {
    let len = instances.length;
    let index = -1;
    let removedHeight;

    for (let i = 0; i < len; i++) {
        if (id === instances[i].id) {
            removedHeight = instances[i].$el.offsetHeight;
            index = i;
            if (typeof userOnClose === 'function') {
                userOnClose(instances[i]);
            }
            instances.splice(i, 1);
            break;
        }
    }

    if (len <= 1 || index === -1 || index > instances.length - 1) return;
    
    for (let i = index; i < len - 1; i++) {
        let dom = instances[i].$el;
        dom.style['top'] = parseInt(dom.style['top'], 10) - removedHeight - 16 + 'px';
    }
};

Toast.closeAll = function () {
    for (let i = instances.length - 1; i >= 0; i--) {
        instances[i].close();
    }
};

export default Toast;

 

挂载

import Vue from 'vue'
import Toast from '@/components/Toast/toast.js'

Vue.prototype.$toast = Toast;

 

.vue组件内使用

this.$toast('添加参会人异常');

// 或

this.$toast({
    type: 'error',
    message: '添加参会人异常'
});

 

.js文件内使用

import Toast from '@/components/Toast/toast.js';

Toast('提示消息');

// 或

Toast({
    type: 'info',
    message: '提示消息',
    onClose: () => { // do... }
});

// 或

Toast.success('提示信息');

// 或

Toast.success({
    message: '提示消息',
    onClose: () => { // do... }
});