构建Toast组件,分为两部分
- Toast.vue 文件
创建一个具有 message/type/location入参的Toast组件。
<script setup lang="ts">
import { computed, ref } from 'vue';
interface Props {
message?: String
type?: "Default" | "Success" | "Waring" | "Error"
location?: "Center" | "TopCenter" | "BottomCenter"
}
const props = withDefaults(defineProps<Props>(), {
type: () => "Default",
location: () => "Center",
});
const isVisible = ref(false);
const locationValue = computed(() => {
if (props.location == 'Center') {
return 'center';
} else if (props.location == 'TopCenter') {
return 'top-center';
} else {
return 'bottom-center';
}
});
const timerId = ref<number | null>(null);
const show = () => {
if (timerId.value) {
clearTimeout(timerId.value);
timerId.value = null;
}
isVisible.value = true;
timerId.value = setTimeout(() => dismiss(), 3000);
};
const dismiss = () => {
isVisible.value = false;
if (timerId.value) {
clearTimeout(timerId.value);
timerId.value = null;
}
};
defineExpose({ show, dismiss }); //导出Toast组件的show和dismiss方法
</script>
<template lang="pug">
div.wrapper-toast
div(:class="[`container-${locationValue}`, { [`container-${locationValue}-visible`]: isVisible }]")
span.message(:class="`message-${props.type.toLocaleLowerCase()}`")
| {{ props.message }}
</template>
<style lang="scss" scoped>
@use '../../styles/layout.scss' as *;
@mixin visibility_for_location($location) {
@if $location =="Center" {
opacity: 1.0;
}
@else if $location =="TopCenter" {
opacity: 1.0;
top: 60px;
transform: translate(-50%, 50%);
}
@else {
opacity: 1.0;
bottom: 80px;
transform: translate(-50%, -150%);
}
}
@mixin container_for_location($location) {
position: fixed;
transition: all 0.5s ease;
@include flexLayout(column, true);
@if $location =="Center" {
opacity: 0.0;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
@else if $location =="TopCenter" {
opacity: 0.0;
left: 50%;
top: 0px;
transform: translate(-50%, -150%);
}
@else {
opacity: 0.0;
left: 50%;
bottom: 0px;
transform: translate(-50%, 150%);
}
}
.wrapper-toast {
top: 0px;
left: 0px;
position: relative;
.container-center {
@include container_for_location(Center);
&-visible {
@include visibility_for_location(Center);
}
}
.container-top-center {
@include container_for_location(TopCenter);
&-visible {
@include visibility_for_location(TopCenter);
}
}
.container-bottom-center {
@include container_for_location(BottomCenter);
&-visible {
@include visibility_for_location(BottomCenter);
}
}
.message {
max-width: 80%;
min-width: 120px;
padding: 8px 20px;
border-radius: 12px;
@include flexLayout(column, true);
&-default {
color: white;
border: 1px solid black;
background-color: rgba(0, 0, 0, 0.706);
}
&-error {
color: red;
border: 1px solid red;
background-color: rgba(247, 194, 194, 0.706);
}
&-success {
color: green;
border: 1px solid green;
background-color: rgba(192, 246, 175, 0.706);
}
&-waring {
color: orange;
border: 1px solid orange;
background-color: rgba(250, 230, 173, 0.706);
}
}
}
</style>
- Toast.ts 文件
在window.body中创建并挂载这个组件,并提供导出方法
import { createVNode, type App, type VNode, render } from "vue";
import ToastView from "./Toast.vue";
var toastVNode: VNode | null = null;
var toastDomNode: Element | null = null;
interface Params {
message?: String;
type?: "Default" | "Success" | "Waring" | "Error";
location?: "Center" | "TopCenter" | "BottomCenter";
}
let timerId: number | null;
const Toast = {
show(params: Params | String) {
if (toastVNode) {
if (toastDomNode) {
render(null, toastDomNode);
}
toastVNode = null;
}
let args: Record<string, any> = {};
if (typeof params == "string") {
args.message = params;
} else if (typeof params == "object") {
let obj = params as Params;
args.type = obj.type;
args.message = obj.message;
args.location = obj.location;
}
toastVNode = createVNode(ToastView, args);
if (toastDomNode == null) {
toastDomNode = document.createElement("div");
document.body.appendChild(toastDomNode);
}
render(toastVNode, toastDomNode);
timerId = setTimeout(() => {
toastVNode?.component?.exposed?.show();
}, 0);
},
dismiss() {
toastVNode?.component?.exposed?.dismiss();
},
dispose() {
if (toastVNode) {
if (toastDomNode) {
render(null, toastDomNode);
}
toastVNode = null;
if (toastDomNode) {
document.body.removeChild(toastDomNode);
toastDomNode = null;
}
}
},
};
export default {
install(app: App) {
app.config.globalProperties.$toast = Toast;
},
};
- 为防止TS文件不提示toast的使用,需要增加ts-types。在项目目录 types/vue.d.ts 中声明
import { ComponentCustomProperties } from "vue";
declare module "@vue/runtime-core" {
interface ToastParams {
message?: String;
type?: "Default" | "Success" | "Waring" | "Error";
location?: "Center" | "TopCenter" | "BottomCenter";
}
interface ComponentCustomProperties {
$toast: {
show: (params: string | ToastParams) => void;
// 如果 $toast 还有其他方法,可以在这里继续添加
dismiss: () => void;
dispose: () => void;
};
}
}
- 项目中应用这个Toast组件
首先需要在APP中注册这个Toast组件
...
import Toast from "./widgets/toast/Toast";
...
createApp(App).use(router).use(Toast).use(createPinia()).mount("#app");
在视图界面中使用全局$toast
...
import { ref, nextTick, getCurrentInstance } from 'vue';
...
const instance = getCurrentInstance();
const showToast = (params: {
message?: String;
type?: "Default" | "Success" | "Waring" | "Error";
location?: "Center" | "TopCenter" | "BottomCenter";
} | string) => {
instance?.proxy?.$toast?.show(params);
};
const showToastCenter = () => {
showToast({ message: 'Hello', type: 'Error', location: 'Center' });
};
const showToastTopCenter = () => {
showToast({ message: 'Hello', type: 'Success', location: 'TopCenter' })
}
const showToastBottomCenter = () => {
showToast({ message: 'Hello', type: 'Waring', location: 'BottomCenter' });
};
...
恭喜你,你已经创建了一个全局Toast组件

显示效果图

在项目中的节点信息