| 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 |