import { createApp, h } from 'vue' import { getPrefixCls } from '../_utils/getPrefixCls' import Icon from '../icon/index' type Placement = | 'top' | 'bottom' | 'left' | 'right' | 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end' | 'center' interface MessageOptions { message: string type?: 'info' | 'success' | 'warning' | 'error' | 'loading' | 'question' placement?: Placement duration?: number showClose?: boolean } interface MessageHandle { close: () => void } interface MessageFn { (msg: string | MessageOptions): MessageHandle info: (msg: string | MessageOptions) => MessageHandle success: (msg: string | MessageOptions) => MessageHandle warning: (msg: string | MessageOptions) => MessageHandle error: (msg: string | MessageOptions) => MessageHandle closeAll: () => void } const prefixCls = getPrefixCls('message') const containers: Partial> = {} const instances: MessageHandle[] = [] function getContainer(placement: Placement = 'top') { 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) { if (placement.startsWith('top')) return hidden ? 'translateY(-10px)' : 'translateY(0)' if (placement.startsWith('bottom')) return hidden ? 'translateY(10px)' : 'translateY(0)' if (placement.startsWith('left')) return hidden ? 'translateX(-10px)' : 'translateX(0)' if (placement.startsWith('right')) return hidden ? 'translateX(10px)' : 'translateX(0)' if (placement === 'center') return hidden ? 'scale(0.8)' : 'scale(1)' return hidden ? 'translateY(-10px)' : 'translateY(0)' } export function useMessage(): MessageFn { const createMessage = ( msg: string | MessageOptions, defaultType: 'info' | 'success' | 'warning' | 'error' | 'question' | 'loading' = 'info', ): MessageHandle => { const opts: MessageOptions = typeof msg === 'string' ? { message: msg, type: defaultType } : { ...msg, type: msg.type || defaultType } const { message, type, placement = 'top', duration = 3000 } = opts const container = getContainer(placement) let timer: number | undefined const iconMap: Record = { success: 'ix:success-filled', error: 'ix:error-filled', warning: 'ix:warning-filled', info: 'ix:info-filled', question: 'ix:question-filled', loading: 'mingcute:loading-line', } let handle: MessageHandle const app = createApp({ render() { return h( 'div', { class: [`${prefixCls}-item`], style: { opacity: 0, transform: getTransform(placement, true), transition: 'all 0.3s ease-in-out', }, }, [ h(Icon, { icon: iconMap[type ?? 'info'], size: 20, class: `${prefixCls}-icon ${prefixCls}-${type ?? 'info'}`, spin: type === 'loading', }), h('span', { class: `${prefixCls}-content` }, message), opts.showClose ? h(Icon, { icon: 'ix:close', size: 14, class: `${prefixCls}-close`, onClick: () => handle?.close?.(), }) : null, ], ) }, }) 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: string | MessageOptions) => createMessage(msg)) as MessageFn api.info = msg => createMessage(msg, 'info') api.success = msg => createMessage(msg, 'success') api.warning = msg => createMessage(msg, 'warning') api.error = msg => createMessage(msg, 'error') api.closeAll = () => { instances.slice().forEach(instance => instance.close()) } return api }