# toast 方法
import { createVNode, nextTick, render } from 'vue'
import ToastComponent from './index.vue'
let toastInstance = null
let container = null
let autoCloseTimer = null
export function showToast(options = {}) {
// 清除之前的自动关闭定时器
if (autoCloseTimer) {
clearTimeout(autoCloseTimer)
autoCloseTimer = null
}
// 销毁已有Toast
if (toastInstance) {
// 直接调用关闭方法清理
closeToast()
}
const {
text = '',
duration = 2000,
overColor = 'transparent',
maskColor = 'rgba(0, 0, 0, 0.7)'
} = options
// 创建容器
container = document.createElement('div')
document.body.appendChild(container)
// 创建虚拟节点 - 使用普通对象而非 ref
toastInstance = createVNode(ToastComponent, {
text,
overColor,
maskColor,
showWrap: true,
showContent: true
})
// 渲染到容器
render(toastInstance, container)
// // 设置自动关闭定时器
if (duration > 0) {
autoCloseTimer = setTimeout(closeToast, duration)
}
}
// 统一的关闭方法
function closeToast() {
if (!toastInstance || !container) return
// 触发组件淡出动画
toastInstance.component.props.showContent = false
// 延迟执行清理操作
nextTick(() => {
// 隐藏整个组件
toastInstance.component.props.showWrap = false
// 等待动画完成后再移除DOM
setTimeout(() => {
if (container) {
// 卸载组件
render(null, container)
document.body.removeChild(container)
// 重置变量
container = null
toastInstance = null
}
}, 250) // 与CSS动画时间一致
})
}
export const toast = {
show: showToast,
clear: closeToast
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# toast 组件
<template>
<div
class="m-toast"
:style="{ backgroundColor: overColor }"
:class="showContent ? 'fadein' : 'fadeout'"
v-if="showWrap"
>
<div class="mask" :style="{ backgroundColor: maskColor }">
<div v-if="text" class="toast-text">{{ text }}</div>
</div>
</div>
</template>
<script setup>
// 只需定义 props,不需要额外逻辑
defineProps({
text: String,
maskColor: String,
overColor: String,
showWrap: Boolean,
showContent: Boolean
})
</script>
<style lang="less" scoped>
.m-toast {
position: fixed;
top: 0;
left: 0;
width: 100vw; /* 修复:改为100vw确保全宽 */
min-height: 100vh;
min-height: 100dvh;
z-index: 9999;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
touch-action: none;
.mask {
position: fixed;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
max-width: 80vw;
gap: 10px;
z-index: 100;
left: 50%;
top: 50%;
font-size: 28px;
padding: 20px;
border-radius: 15px;
transform: translate(-50%, -50%);
color: #fff;
.toast-text {
text-align: center;
word-break: break-word;
}
}
}
.fadein {
animation: animate_in 0.25s forwards;
}
.fadeout {
animation: animate_out 0.25s forwards;
}
@keyframes animate_in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes animate_out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87