ToastContainer.vue
<div class="toast-container">
<transition-group name="van-slide-left" tag="div">
<div
v-for="(toast, index) in toasts"
:key="toast.id"
class="toast-wrap"
:style="{ top: `${index * 80}px` }"
>
<UseToast v-bind="toast.props" :on-close="() => removeToast(toast.id)" />
</div>
</transition-group>
</div>
</template>
<script setup lang="ts">
import { toasts, removeToast } from '@/hooks/toastManager'
import UseToast from '@/components/Dialog/Toast.vue'
</script>
<style scoped>
.toast-container {
position: fixed;
left: 0;
width: 300px;
top: 50px;
display: flex;
flex-direction: column; /* 弹窗从上到下排列 */
}
.toast-wrap {
position: absolute; /* 改为 absolute 以便更好地控制位置 */
width: 100%;
transition:
top 0.5s ease-in-out,
opacity 0.5s ease-in-out; /* 过渡效果 */
}
.van-slide-left-enter-active {
animation: slide-in-left 0.5s forwards;
}
.van-slide-left-leave-active {
animation: fade-out 0.5s forwards;
}
@keyframes slide-in-left {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
</style>
Toast.vue
<template>
<transition name="notification-fade" @leave="handleLeave">
<div class="toast-wrap" :style="{ bottom: `${index * 60}px` }">
<div class="toast">
<img v-if="type == 1" src="@/assets/images/grift.png" alt="" />
<div class="content">
<div class="span">
{{ lang == 'english' ? 'Congratulations on winning' : 'Parabéns pela vitória' }}
<strong>{{ currencySymbol }}{{ content }}</strong>
</div>
<div class="next">
o WinGo 1Min. <img src="@/assets/images/next.png" alt="" srcset="" class="next" />
</div>
</div>
<button class="close-btn" @click="closeToast">
<img src="@/assets/images/closeToast.png" alt="" srcset="" />
</button>
</div>
</div>
</transition>
</template>
<script setup lang="ts">
import { getStorage } from '@/utils/local'
import { ref, onMounted } from 'vue'
const currencySymbol = getStorage('currencySymbol')
const lang = getStorage('lang')
const props = defineProps({
title: {
type: String
},
type: {
type: Number
},
content: {
type: String
},
duration: {
type: Number,
required: true
},
index: {
type: Number,
required: true
},
onClose: {
type: Function,
required: true
}
})
const currentRate = ref(100)
const rate = ref(100)
const speed = ref(100)
onMounted(() => {
rate.value = 0
speed.value = (100 / props.duration) * 1000
})
const closeToast = () => props.onClose()
const handleLeave = () => {
// 触发自定义事件通知父组件
const event = new CustomEvent('toast-leave', { detail: props.index })
window.dispatchEvent(event)
}
</script>
<style lang="less" scoped>
.notification-fade-enter-active,
.notification-fade-leave-active {
transition: opacity 0.5s;
}
.close-btn {
position: absolute;
top: 5px;
right: 10px;
background: none;
border: none;
font-size: 16px;
cursor: pointer;
color: #535353;
}
.notification-fade-enter,
.notification-fade-leave-to .notification-fade-leave-active {
opacity: 0;
}
.toast-wrap {
z-index: 999;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
margin: 0 auto;
right: 0;
}
.toast {
position: relative;
width: 325px;
height: 61px;
background: #ffffff;
box-shadow: 0px 0px 10px 1px #999;
border-radius: 4px;
margin: 10px auto 0px;
display: flex;
padding: 9px;
overflow: hidden;
transition: opacity 0.5s ease-in-out;
.content {
margin-left: 10px;
height: 61px;
.span {
font-size: 14px;
color: #535353;
}
strong {
font-weight: bold;
font-size: 15px;
line-height: 20px;
color: #ffa235;
display: inline-block;
margin-left: 10px;
}
.next {
img {
width: 14px;
height: 14px;
vertical-align: middle;
object-fit: scale-down;
}
font-size: 14px;
color: #000;
}
}
img {
width: 20px;
height: 25px;
object-fit: scale-down;
}
p {
font-weight: 400;
font-size: 14px;
color: #d6d6d6;
line-height: 38px;
}
}
</style>
useToast.ts
type ToastOptions =
| string
| {
duration?: number
text: string
type?: number
content?: string
}
export function useToast(options: ToastOptions) {
let title: string, duration: number, type: number, content: string
if (typeof options === 'string') {
title = options
duration = 1500
type = 1
content = ''
} else {
title = options.text
duration = options.duration || 1500
type = options.type || 1
content = options.content || ''
}
const id = Date.now() + Math.random() // 生成唯一ID
const toast = {
id,
props: { title, type, duration, content, index: 0, onClose: () => removeToast(id) }
}
addToast(toast)
setTimeout(() => {
removeToast(id)
}, duration)
}
使用 在app。vue中引入<ToastContainer />

使用方式
useToast({
text: '',
duration: 3000,
type: 1,
content: _data.winAmount
})