Vue项目增加全局自定义组件(如Toast弹窗)

构建Toast组件,分为两部分

  1. 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>
  1. 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;
  },
};
  1. 为防止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;
    };
  }
}
  1. 项目中应用这个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组件

显示效果图
在项目中的节点信息
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容