Added toast notification function and optimized related component styles

This commit is contained in:
2026-01-02 01:27:51 +08:00
parent 009274e4ad
commit 533f732c53
14 changed files with 909 additions and 199 deletions

View File

@@ -0,0 +1,168 @@
<template>
<Teleport to="body">
<TransitionGroup
v-for="position in positions"
:key="position"
:class="['toast-container', `toast-container-${position}`]"
name="toast-list"
tag="div"
>
<ToastItem
v-for="toast in getToastsByPosition(position)"
:key="toast.id"
:toast="toast"
@close="removeToast"
/>
</TransitionGroup>
</Teleport>
</template>
<script setup lang="ts">
import ToastItem from './Toast.vue';
import { useToastStore } from './toastStore';
import type { ToastPosition } from './types';
const toastStore = useToastStore();
const positions: ToastPosition[] = [
'top-left',
'top-center',
'top-right',
'bottom-left',
'bottom-center',
'bottom-right',
];
const getToastsByPosition = (position: ToastPosition) => {
return toastStore.toasts.filter(toast => toast.position === position);
};
const removeToast = (id: string) => {
toastStore.remove(id);
};
</script>
<style scoped lang="scss">
.toast-container {
position: fixed;
z-index: 9999;
pointer-events: none;
display: flex;
flex-direction: column;
> * {
pointer-events: auto;
}
}
// 顶部位置 - 增加间距避免覆盖标题栏
.toast-container-top-left {
top: 35px;
left: 20px;
align-items: flex-start;
}
.toast-container-top-center {
top: 35px;
left: 50%;
transform: translateX(-50%);
align-items: center;
}
.toast-container-top-right {
top: 35px;
right: 20px;
align-items: flex-end;
}
// 底部位置
.toast-container-bottom-left {
bottom: 20px;
left: 20px;
align-items: flex-start;
flex-direction: column-reverse;
}
.toast-container-bottom-center {
bottom: 20px;
left: 50%;
transform: translateX(-50%);
align-items: center;
flex-direction: column-reverse;
}
.toast-container-bottom-right {
bottom: 20px;
right: 20px;
align-items: flex-end;
flex-direction: column-reverse;
}
// TransitionGroup 列表动画 - 从哪来回哪去,收缩滑出
.toast-list-move {
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.toast-list-enter-active {
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.toast-list-leave-active {
transition: all 0.3s cubic-bezier(0.6, 0, 0.8, 0.4);
position: absolute !important;
}
// 右侧位置:从右滑入,收缩向右滑出
.toast-container-top-right,
.toast-container-bottom-right {
.toast-list-enter-from {
opacity: 0;
transform: translateX(100%) scale(0.8);
}
.toast-list-leave-to {
opacity: 0;
transform: translateX(100%) scale(0.8);
}
}
// 左侧位置:从左滑入,收缩向左滑出
.toast-container-top-left,
.toast-container-bottom-left {
.toast-list-enter-from {
opacity: 0;
transform: translateX(-100%) scale(0.8);
}
.toast-list-leave-to {
opacity: 0;
transform: translateX(-100%) scale(0.8);
}
}
// 居中位置:从上/下滑入,收缩向上/下滑出
.toast-container-top-center {
.toast-list-enter-from {
opacity: 0;
transform: translateY(-100%) scale(0.8);
}
.toast-list-leave-to {
opacity: 0;
transform: translateY(-100%) scale(0.8);
}
}
.toast-container-bottom-center {
.toast-list-enter-from {
opacity: 0;
transform: translateY(100%) scale(0.8);
}
.toast-list-leave-to {
opacity: 0;
transform: translateY(100%) scale(0.8);
}
}
</style>