Vue3封装消息提示组件

1,339 阅读3分钟

本文主要分享了如何使用vue3和TS来封装一个自定义的的message消息提示组件,通常消息提示框位于页面顶部居中,为可单击手动关闭和定时关闭以及设置消息提示框的类型。在本文中使用success、warning、info、error四种类型作为案例演示。

1️⃣ message.vue文件

使用vue3和ts实现的一个消息提示组件,类型为success、warning、info、error四种。 图标是使用的iconfont,iconfont是我在已经在项目中引入的资源。

✏️template部分

  • transition-group给提示组件增加自然淡入淡出的效果,可能同时出现几个提示组件,因此使用的是transition-group。
  • closeHandle为消息提示框关闭事件,当duration传参为0时,不显示关闭按钮且无法自动关闭。
  • getInnerClass设置消息提示框的样式
  • getIconName获取不同类型提示框的图标

image.png

<template>
    <transition-group tag="div" name="slide-fade">
        <div v-for="message in messageQueue" :key="message.id" :class="getInnerClass(message.type)">
            <div v-if="message.duration === 0" class="close-message" @click="closeHandle(message)">
                <i class="iconfont icon-cuowuguanbiquxiao"></i>
            </div>
            <div class="contents">
                <i :class="['iconfont', `${getIconName(message.type)}`]"></i>
                <span class="cloud-toast-content" v-html="message.content"></span>
            </div>
        </div>
    </transition-group>
</template>

✏️script部分

<script lang="ts" setup>
import { PropType } from "vue"
import type{ ToastOption, MessageType } from "./message"  //从message.ts文件中导入定义好的interface
import Message from "./message"
defineProps({
    messageQueue: {
        type: Array as PropType<ToastOption[]>,
        default: () => []
    }
})
const getInnerClass = (type: MessageType) => {    // 控制不同类型的提示组件的样式
    const MESSAGE_CLASS = "cloud-toast"
    return {
        [MESSAGE_CLASS]: true,
        [`${MESSAGE_CLASS}-${type}`]: true
    }
}
const getIconName = (type: MessageType) => {   //获取四种不同提示状态的图标名称,此处使用的是iconfont图标
    return `icon-${type}`
}
const closeHandle = (message: ToastOption) => Message.remove(message)
</script>

✏️样式部分

  • scss变量可以定义在公共样式文件中
<style lang="scss">
$success-font: #67c23a;
$info-font: #909399;
$warning-font: #e6a23c;
$error-font: #f56c6c;

.slide-fade-enter-active {
    transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
    transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
    transform: translateX(20px);
    opacity: 0;
}

.message-wrapper {
    position: fixed;
    top: 10px;
    z-index: 999;
    left: 50%;
    transform: translateX(-50%);
}

.cloud-toast {
    display: block;
    padding: 15px 15px 15px 20px;
    margin-top: 16px;
    min-width: 300px;
    background-color: $primary-color;
    border-radius: 8px;
    transition: all 0.3s;
    transition: opacity 0.3s, transform 0.4s, top 0.4s;
    box-shadow: 2px 1px 5px 2px rgba(0, 0, 0, 0.1);
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: center;

    &-contents {
        padding-right: 16px;
    }

    .close-message {
        position: absolute;
        top: 10px;
        right: 10px;
        cursor: pointer;

        i {
            font-size: 12px;
        }
    }

    &-success {
        background-color: #f0f9eb;
        border-color: #e1f3d8;

        .cc-message-content,
        i {
            color: $success-font;
        }
    }

    &-error {
        background-color: #fef0f0;
        border-color: #fde2e2;

        .cc-message-content,
        i {
            color: $error-font;
        }
    }

    &-info {
        background-color: #edf2fc;
        border-color: #ebeef5;

        .cc-message-content,
        i {
            color: $info-font;
        }
    }

    &-warning {
        background-color: #fdf6ec;
        border-color: #faecd8;

        .cc-message-content,
        i {
            color: $warning-font;
        }
    }
}
</style>

2️⃣ message.ts文件

  • 从vue中导入api和类型
  • MessageOption为提示框传参的基本信息;MessageType为不同的消息类型;IMessage为Message类中必须实现的属性;defaultMessageOptions为默认提示框传参
  • 创建一个uuid()的方法,为每一个创建的message组件的唯一id,以便匹配删除组件的功能
  • 创建一个Message类
    • 定义一个wrapper实例和messageQueue消息队列

    • 定义一个createWrapper()方法实现组件的添加和删除操作,在render函数中创建节点并使用teleport传送门将创建的组件放置于根节点下。将createWrapper创建的实例赋值给wrapper

    • appendMessage()方法调用wrapper下的append()方法创建消息提示组件,并判断传参中的duration值是否为0,不为0则正常实现定时删除的方法。

    • createMessage合并默认参数和形参

    • success方法合并参数,调用createWrapper()初始化wrapper,再使用appendMessage()添加组件到DOM节点中。其他的info、warning、error方法一样的操作,只是type不同。

import { h, Teleport, createApp } from "vue"
import type {ComponentPublicInstance, App} from 'vue'
import Message from "./index.vue"

export interface MessageOption {
    type: MessageType
    content: string
    duration: number
    closeVisible: boolean
    id: string
}
//定义消息类型的枚举常量
export enum MessageType {
    SUCCESS = "success",
    ERROR = "error",
    INFO = "info",
    WARNING = "warning"
}
type IHandleMessageFn = (message: string, option?: MessageOption) => void

// 定义Message类中的属性、方法的数据类型
export interface IMessage {
    messageQueue: MessageOption[]
    success: IHandleMessageFn
    info: IHandleMessageFn
    error: IHandleMessageFn
    warning: IHandleMessageFn
}
const defaultMessageOptions = {
    type: MessageType.INFO,
    duration: 3000,
    closeVisible: false
}
// 创建一个唯一标识符
function uuid(): string {
    const tempUrl = URL.createObjectURL(new Blob())
    const uuid = tempUrl.toString()
    URL.revokeObjectURL(uuid)
    return uuid.substr(uuid.lastIndexOf("/") + 1)
}
class Message implements IMessage {
    private wrapper: ComponentPublicInstance<any>

    get messageQueue() {
        return this.wrapper.messageQueue
    }
    private createWrapper(): App {
        if (this.wrapper) {
            return this.wrapper
        }
        this.wrapper = createApp({
            data() {
                return {
                    messageQueue: []
                }
            },
            methods: {
                remove(toastInfo: ToastOption) {
                    this.messageQueue = this.messageQueue.filter((item: ToastOption) => item.id !== toastInfo.id)
                },
                append(toastInfo: ToastOption) {
                    this.messageQueue.push(toastInfo)
                }
            },
            render() {
                return h(Teleport, { to: "body" }, [
                    h("div", { class: "message-wrapper" }, [h(Message, { messageQueue: this.messageQueue })])
                ])
            }
        }).mount(document.createElement("div"))
        return this.wrapper
    }
    private appendMessage(message: MessageOption) {
        this.wrapper.append(message)
        if (message.duration === 0) return

        setTimeout(() => {
            this.remove(message)
        }, message.duration)
    }
    remove(message: MessageOption) {
        this.wrapper.remove(message)
    }
    public createMessage(content: string, options: Partial<MessageOption>): MessageOption {
        const message = Object.assign(
            {},
            defaultMessageOptions,
            {
                content,
                id: uuid()
            },
            options
        )
        return message
    }
    success(content: string, option?: Partial<MessageOption>) {
        const message = this.createMessage(content, {
            type: MessageType.SUCCESS,
            ...option
        })
        this.createWrapper()
        this.appendMessage(message)
    }
    info(content: string, option?: Partial<MessageOption>) {
        const message = this.createMessage(content, {
            type: MessageType.INFO,
            ...option
        })
        this.createWrapper()
        this.appendMessage(message)
    }
    error(content: string, option?: Partial<MessageOption>) {
        const message = this.createMessage(content, {
            type: MessageType.ERROR,
            ...option
        })
        this.createWrapper()
        this.appendMessage(message)
    }
    warning(content: string, option?: Partial<MessageOption>) {
        const message = this.createMessage(content, {
            type: MessageType.WARNING,
            ...option
        })
        this.createWrapper()
        this.appendMessage(message)
    }
}
export default new Message()

3️⃣ 在其他vue文件中使用

Message.success("吴彦祖看我文章了!", { type: "success", duration: 0 })

image.png

注意:要将Message挂载到全局才能在任意的文件中使用。