import type { VNode } from 'vue' import { createApp, h } from 'vue' import { getPrefixCls } from '../_utils/getPrefixCls' import MessageItem from './item' type Placement = | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' type NotificationType = 'default' | 'info' | 'success' | 'warning' | 'danger' | 'loading' | 'question' type NotificationTheme = 'normal' | 'light' interface NotificationOptions { message: string | VNode | (() => VNode) title: string subTitle?: string theme?: NotificationTheme type?: NotificationType placement?: Placement duration?: number showClose?: boolean } interface NotificationHandle { close: () => void } interface MessageFn { (msg: NotificationOptions): NotificationHandle open: (msg: NotificationOptions) => NotificationHandle info: (msg: NotificationOptions) => NotificationHandle success: (msg: NotificationOptions) => NotificationHandle warning: (msg: NotificationOptions) => NotificationHandle danger: (msg: NotificationOptions) => NotificationHandle closeAll: () => void } const prefixCls = getPrefixCls('notification') const containers: Partial> = {} const instances: NotificationHandle[] = [] function getContainer(placement: Placement = 'top-end') { if (!containers[placement]) { const wrapper = document.createElement('div') wrapper.className = `${prefixCls}-wrapper ${prefixCls}-placement-${placement}` document.body.appendChild(wrapper) containers[placement] = wrapper } return containers[placement]! } function getTransform(placement: Placement, hidden: boolean) { switch (placement) { case 'top-start': case 'bottom-start': return hidden ? 'translateX(-10px)' : 'translateX(0)' case 'top-end': case 'bottom-end': return hidden ? 'translateX(10px)' : 'translateX(0)' default: return hidden ? 'translateX(-10px)' : 'translateX(0)' } } export function useNotification(): MessageFn { const createNotification = ( opts: NotificationOptions, defaultType: NotificationType = 'default', ): NotificationHandle => { const { message, title, subTitle, type = defaultType, theme, placement = 'top-end', duration = 3000, showClose } = opts const container = getContainer(placement) let timer: number | undefined let handle: NotificationHandle const app = createApp({ render() { return h(MessageItem, { type, theme, title, subTitle, message, placement, showClose, style: { opacity: 0, transform: getTransform(placement, true), transition: 'all 0.3s ease-in-out', }, onClose: () => handle?.close?.(), }) }, }) const vm = app.mount(document.createElement('div')) container.appendChild(vm.$el) // 出现动画 requestAnimationFrame(() => { Object.assign(vm.$el.style, { opacity: '1', transform: getTransform(placement, false), }) }) handle = { close: () => { if (timer) clearTimeout(timer) Object.assign(vm.$el.style, { opacity: '0', transform: getTransform(placement, true), }) setTimeout(() => { if (container.contains(vm.$el)) container.removeChild(vm.$el) app.unmount() const index = instances.indexOf(handle) if (index !== -1) instances.splice(index, 1) }, 300) }, } instances.push(handle) if (duration > 0) { timer = window.setTimeout(() => { handle.close() }, duration) } return handle } const api = ((msg: NotificationOptions) => createNotification(msg)) as MessageFn api.open = msg => createNotification(msg, 'default') api.info = msg => createNotification(msg, 'info') api.success = msg => createNotification(msg, 'success') api.warning = msg => createNotification(msg, 'warning') api.danger = msg => createNotification(msg, 'danger') api.closeAll = () => { instances.slice().forEach(instance => instance.close()) } return api }