返回 AiToEarn
toast.ts
根目录 / project / aitoearn-web / src / utils / ui / toast.ts
1 import type { CSSProperties, ReactNode } from 'react'
2 import { toast as sonnerToast } from 'sonner'
3
4 interface ToastOptions {
5 key?: string
6 id?: string
7 duration?: number
8 content?: ReactNode
9 className?: string
10 style?: CSSProperties
11 }
12
13 const legacySafeToastStyle: CSSProperties = {
14 display: 'flex',
15 alignItems: 'center',
16 gap: 6,
17 padding: 16,
18 boxSizing: 'border-box',
19 }
20
21 function getToastOptions(options?: ToastOptions, defaultDuration = 3000) {
22 return {
23 id: options?.key || options?.id,
24 duration: options?.duration ? options.duration * 1000 : defaultDuration,
25 className: ['app-toast', options?.className].filter(Boolean).join(' '),
26 style: {
27 ...legacySafeToastStyle,
28 ...options?.style,
29 },
30 }
31 }
32
33 /**
34 * Toast 消息工具
35 * 用于替代 antd 的 message 组件
36 */
37 export const toast = {
38 /**
39 * 成功消息
40 */
41 success(content: ReactNode | ToastOptions, options?: ToastOptions) {
42 if (typeof content === 'object' && content !== null && 'content' in content) {
43 const opts = content as ToastOptions
44 return sonnerToast.success(opts.content, getToastOptions(opts))
45 }
46 return sonnerToast.success(content as ReactNode, getToastOptions(options))
47 },
48
49 /**
50 * 错误消息
51 */
52 error(content: ReactNode | ToastOptions, options?: ToastOptions) {
53 if (typeof content === 'object' && content !== null && 'content' in content) {
54 const opts = content as ToastOptions
55 return sonnerToast.error(opts.content, getToastOptions(opts))
56 }
57 return sonnerToast.error(content as ReactNode, getToastOptions(options))
58 },
59
60 /**
61 * 警告消息
62 */
63 warning(content: ReactNode | ToastOptions, options?: ToastOptions) {
64 if (typeof content === 'object' && content !== null && 'content' in content) {
65 const opts = content as ToastOptions
66 return sonnerToast.warning(opts.content, getToastOptions(opts))
67 }
68 return sonnerToast.warning(content as ReactNode, getToastOptions(options))
69 },
70
71 /**
72 * 信息消息
73 */
74 info(content: ReactNode | ToastOptions, options?: ToastOptions) {
75 if (typeof content === 'object' && content !== null && 'content' in content) {
76 const opts = content as ToastOptions
77 return sonnerToast.info(opts.content, getToastOptions(opts))
78 }
79 return sonnerToast.info(content as ReactNode, getToastOptions(options))
80 },
81
82 /**
83 * 加载消息
84 */
85 loading(content: ReactNode | ToastOptions, options?: ToastOptions) {
86 if (typeof content === 'object' && content !== null && 'content' in content) {
87 const opts = content as ToastOptions
88 return sonnerToast.loading(opts.content, getToastOptions(opts, Infinity))
89 }
90 return sonnerToast.loading(content as ReactNode, getToastOptions(options, Infinity))
91 },
92
93 /**
94 * 打开自定义类型消息 (兼容 antd message.open)
95 */
96 open(
97 options: ToastOptions & {
98 type?: 'success' | 'error' | 'warning' | 'info' | 'loading'
99 content: ReactNode
100 },
101 ) {
102 const type = options.type || 'info'
103 const toastFn = type === 'loading' ? sonnerToast.loading : sonnerToast[type]
104 return toastFn(options.content, getToastOptions(options, type === 'loading' ? Infinity : 3000))
105 },
106
107 /**
108 * 关闭指定消息
109 */
110 destroy(key?: string) {
111 if (key) {
112 sonnerToast.dismiss(key)
113 }
114 else {
115 sonnerToast.dismiss()
116 }
117 },
118
119 /**
120 * 关闭指定消息 (dismiss 别名,兼容 sonner API)
121 */
122 dismiss(key?: string | number) {
123 if (key) {
124 sonnerToast.dismiss(key)
125 }
126 else {
127 sonnerToast.dismiss()
128 }
129 },
130
131 /**
132 * 关闭所有消息
133 */
134 dismissAll() {
135 sonnerToast.dismiss()
136 },
137 }
138
139 export default toast
140
140 lines TYPESCRIPT