export class MessageContainer { container; id = 'for-message'; root = document.body; constructor(opts) { const { id } = opts || {}; if (id) { this.id = id; } this.initContainer(); } initContainer() { const id = this.id; const root = this.root; let forModal = document.querySelector('#' + id); if (!forModal) { forModal = document.createElement('div'); forModal.id = id; forModal.style = ``; // 点击穿透 root.appendChild(forModal); } this.initStyle(); this.container = forModal; } initStyle(force) { const id = this.id; const styleId = id + '-style'; const _style = document.querySelector('#' + styleId); if (force && _style) { _style.remove(); } if (!force && _style) { return; } const style = document.createElement('style'); style.id = styleId; style.innerHTML = ` #${id} { position: fixed; top: 0; left: 0; z-index: 1000; width: 100vw;height: 100vh;pointer-events: none; display: flex; flex-direction: column; gap: 10px; } .message-wrapper { display: flex; transition: transform 2s ease-in-out, opacity 1.2s ease-in-out; /* 缩小并淡出 */ } .message-wrapper:first-child { margin-top: 20px; } .message { display: flex; gap: 10px; padding: 6px 10px; margin: 0 auto; border-radius: 4px; background-color: white; box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); justify-content: center; align-item: center; animation: message-slide-down 0.3s ease-out forwards; /* 应用动画 */ } /* 添加消失类时 */ .message-wrapper.message-hide { transform: scale(0); opacity: 0; pointer-events: none; /* 防止交互 */ } .message-success { } @keyframes message-slide-down { 0% { transform: translateY(-100px); /* 从上方开始 */ opacity: 0; /* 从不可见状态开始 */ } 50% { opacity: 0.5; /* 渐渐变为半透明 */ } 100% { transform: translateY(0); /* 移动到初始位置 */ opacity: 1; /* 最终完全可见 */ } } .message-icon { position: relative; width: 24px; height: 24px; box-sizing: border-box; } .message-icon::before { content: ""; position: absolute; left: 7px; top: 3px; width: 5px; height: 10px; } .icon-success { border: 2px solid green; /* 外圆圈 */ border-radius: 50%; /* 使其为圆形 */ } .icon-success::before { border-right: 2px solid green; /* 打勾的右边部分 */ border-bottom: 2px solid green; /* 打勾的下边部分 */ transform: rotate(45deg); } .icon-info { border: 2px solid blue; /* 外圆圈 */ border-radius: 50%; /* 使其为圆形 */ } .icon-info::before { content: "i"; position: absolute; top: 0px; color: blue; font-weight: bold; font-size: 16px; left: 8px; } .icon-error::before, .icon-error::after { content: ""; position: absolute; top: 4px; left: 50%; width: 2px; height: 12px; background-color: red; transform-origin: center; } .icon-error { border: 2px solid red; /* 外圆圈 */ border-radius: 50%; /* 使其为圆形 */ } .icon-error::before { transform: translateX(-50%) rotate(45deg); /* 旋转形成叉号的一部分 */ } .icon-error::after { transform: translateX(-50%) rotate(-45deg); /* 旋转形成叉号的另一部分 */ } .icon-warning { position: relative; width: 0; height: 0; border-left: 12px solid transparent; border-right: 12px solid transparent; border-bottom: 24px solid orange; /* 三角形 */ display: inline-block; transform: scale(0.8); /* 缩小三角形 */ } .icon-warning::before { content: "!"; position: absolute; top: 5px; left: 50%; transform: translateX(-50%); color: white; font-weight: bold; font-size: 16px; } .icon-loading { width: 24px; height: 24px; border: 3px solid #f3f3f3; /* 边框颜色,用于加载圈的背景 */ border-top: 3px solid #3498db; /* 顶部边框的颜色,用于显示加载进度 */ border-radius: 50%; /* 圆形 */ animation: spin 1s linear infinite; /* 旋转动画 */ } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `; document.head.appendChild(style); } setRoot(root) { if (root instanceof HTMLElement) { this.root = root; root.appendChild(this.container); } } } const controller = new MessageContainer(); export const createMessage = (content, opts) => { let { icon, key, style, className, type } = opts || {}; const div = document.createElement('div'); div.className = 'message-wrapper' + (className ? ' ' + className : ''); if (style) div.style = style; if (key) div.setAttribute('data-key', key); const contentDiv = document.createElement('div'); contentDiv.className = 'message'; if (icon) { const i = document.createElement('i'); i.className = icon; i.classList.add('message-icon'); contentDiv.appendChild(i); } else if (type) { const i = document.createElement('div'); i.className = 'icon-' + type; i.classList.add('message-icon'); contentDiv.appendChild(i); } if (content instanceof HTMLElement) { contentDiv.appendChild(content); } else { const text = document.createElement('span'); text.innerText = content; contentDiv.appendChild(text); } div.appendChild(contentDiv); return div; }; const methods = ['success', 'info', 'warning', 'error', 'loading']; export class Message { controller = controller; constructor() { this.controller = controller; } open = (message, timeout = 3000, onClose, opts) => { const controller = this.controller; const div = createMessage(message, opts); const remove = () => { div.classList.add('message-hide'); setTimeout(() => { if (div?.isConnected) { div.remove(); } else { console.log('not connected'); controller.container.removeChild(div); } }, 1000); onClose && onClose(); }; controller.container.appendChild(div); controller.initStyle(true); if (timeout === 0) { return () => { remove(); }; } const time = setTimeout(() => { remove(); }, timeout); return () => { clearTimeout(time); remove(); }; }; success = (message, timeout = 1000, onClose = () => {}) => { return this.open(message, timeout, onClose, { type: 'success' }); }; info = (message, timeout = 1500, onClose = () => {}) => { return this.open(message, timeout, onClose, { type: 'info' }); }; warning = (message, timeout = 3000, onClose = () => {}) => { return this.open(message, timeout, onClose, { type: 'warning' }); }; error = (message, timeout = 3000, onClose = () => {}) => { return this.open(message, timeout, onClose, { type: 'error' }); }; loading = (message, timeout = 0, onClose = () => {}) => { return this.open(message, timeout, onClose, { type: 'loading' }); }; } export const message = new Message();