我都使用过element plus 或者ant design Vue这些UI组件库,像element的全局message组件是如何通过一个ElMessage.success(),.warning(),.error()方法调用UI弹框显示,我们明明没有在template区域注入组件,这是为什么呢?答案就在下面!
这里因个人习惯,我使用的是typescript。
一、先定义message类型和实例类型。
typescript
import { createVNode } from 'vue';
import type { VNode } from 'vue'
import type { App } from 'vue';
// 定义通知类型
export type NotificationType = 'info' | 'success' | 'warning' | 'error';
// 定义通知选项
export interface NotificationOptions {
message: string;
type?: NotificationType;
duration?: number;
onClose?: () => void;
}
// 定义通知实例
interface NotificationInstance {
id: number;
vnode: VNode;
container: HTMLDivElement;
}
二、创建容器实例和通知列表、id计数器,每一个通知实例都有一个唯一的id。
ini
// 通知容器
let notificationContainer: HTMLElement | null = null;
// 通知实例列表
const notifications: NotificationInstance[] = [];
// ID计数器
let seed = 0;
三、创建容器,自定义样式模板
css
// 创建通知容器
const createNotificationContainer = () => {
if (notificationContainer) return;
notificationContainer = document.createElement('div');
notificationContainer.className = 'global-notification-container';
document.body.appendChild(notificationContainer);
// 添加样式
const style = document.createElement('style');
style.textContent = `
.global-notification-container {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
width: fit-content;
max-width: 1000px;
}
.global-notification {
margin-bottom: 16px;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
transform: translateY(-100%);
opacity: 0;
transition: all 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
min-width: 300px;
justify-content: center;
}
.global-notification.show {
transform: translateY(0);
opacity: 1;
}
.global-notification.info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #333;
}
.global-notification.success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #333;
}
.global-notification.warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
color: #333;
}
.global-notification.error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #333;
}
.global-notification-icon {
margin-right: 12px;
font-size: 16px;
line-height: 20px;
}
.global-notification-content {
flex: 1;
font-size: 14px;
line-height: 20px;
text-align: center;
}
.global-notification-close {
margin-left: 12px;
cursor: pointer;
font-size: 14px;
line-height: 20px;
color: #999;
}
.global-notification-close:hover {
color: #333;
}
.global-notification.info .global-notification-icon {
color: #1890ff;
}
.global-notification.success .global-notification-icon {
color: #52c41a;
}
.global-notification.warning .global-notification-icon {
color: #faad14;
}
.global-notification.error .global-notification-icon {
color: #ff4d4f;
}
`;
document.head.appendChild(style);
};
四、创建单个通知,自定义HTML模板
ini
// 创建单个通知
const createNotification = (options: NotificationOptions) => {
const id = seed++;
const type = options.type || 'info';
const duration = options.duration === undefined ? 4500 : options.duration;
// 创建容器元素
const container = document.createElement('div');
container.className = 'global-notification';
// 创建通知内容
const icons = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌'
};
container.innerHTML = `
<div class="global-notification-icon">${icons[type]}</div>
<div class="global-notification-content">${options.message}</div>
<div class="global-notification-close">×</div>
`;
container.classList.add(type);
// 添加到容器
notificationContainer?.appendChild(container);
// 触发进入动画
setTimeout(() => {
container.classList.add('show');
}, 10);
// 绑定关闭事件
const closeBtn = container.querySelector('.global-notification-close');
const close = () => {
container.classList.remove('show');
setTimeout(() => {
container.remove();
options.onClose?.();
}, 300);
};
closeBtn?.addEventListener('click', close);
// 自动关闭
if (duration > 0) {
setTimeout(close, duration);
}
// 保存实例
const instance: NotificationInstance = {
id,
vnode: createVNode('div'),
container
};
notifications.push(instance);
return instance;
};
五、导出方法
typescript
// 提供不同类型的通知方法
export const notification = {
open: (options: NotificationOptions) => {
createNotificationContainer();
return createNotification(options);
},
info: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'info',
...options
});
},
success: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'success',
...options
});
},
warning: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'warning',
...options
});
},
error: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'error',
...options
});
},
// 关闭所有通知
closeAll: () => {
notifications.forEach(instance => {
instance.container.remove();
});
notifications.length = 0;
}
};
六、导出自定义插件
javascript
// Vue插件安装方法
export default {
install(app: App) {
app.config.globalProperties.$notification = notification;
app.provide('notification', notification);
}
};
七、注册插件
javascript
import globalMessage from './utils/global-message'
app.use(globalMessage)
八、所有代码
css
// src/utils/global-message.ts
import { createVNode } from 'vue';
import type { VNode } from 'vue'
import type { App } from 'vue';
// 定义通知类型
export type NotificationType = 'info' | 'success' | 'warning' | 'error';
// 定义通知选项
export interface NotificationOptions {
message: string;
type?: NotificationType;
duration?: number;
onClose?: () => void;
}
// 定义通知实例
interface NotificationInstance {
id: number;
vnode: VNode;
container: HTMLDivElement;
}
// 通知容器
let notificationContainer: HTMLElement | null = null;
// 通知实例列表
const notifications: NotificationInstance[] = [];
// ID计数器
let seed = 0;
// 创建通知容器
const createNotificationContainer = () => {
if (notificationContainer) return;
notificationContainer = document.createElement('div');
notificationContainer.className = 'global-notification-container';
document.body.appendChild(notificationContainer);
// 添加样式
const style = document.createElement('style');
style.textContent = `
.global-notification-container {
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
width: fit-content;
max-width: 1000px;
}
.global-notification {
margin-bottom: 16px;
padding: 12px 20px;
border-radius: 4px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: flex;
align-items: center;
transform: translateY(-100%);
opacity: 0;
transition: all 0.3s ease;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
min-width: 300px;
justify-content: center;
}
.global-notification.show {
transform: translateY(0);
opacity: 1;
}
.global-notification.info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #333;
}
.global-notification.success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #333;
}
.global-notification.warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
color: #333;
}
.global-notification.error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #333;
}
.global-notification-icon {
margin-right: 12px;
font-size: 16px;
line-height: 20px;
}
.global-notification-content {
flex: 1;
font-size: 14px;
line-height: 20px;
text-align: center;
}
.global-notification-close {
margin-left: 12px;
cursor: pointer;
font-size: 14px;
line-height: 20px;
color: #999;
}
.global-notification-close:hover {
color: #333;
}
.global-notification.info .global-notification-icon {
color: #1890ff;
}
.global-notification.success .global-notification-icon {
color: #52c41a;
}
.global-notification.warning .global-notification-icon {
color: #faad14;
}
.global-notification.error .global-notification-icon {
color: #ff4d4f;
}
`;
document.head.appendChild(style);
};
// 创建单个通知
const createNotification = (options: NotificationOptions) => {
const id = seed++;
const type = options.type || 'info';
const duration = options.duration === undefined ? 4500 : options.duration;
// 创建容器元素
const container = document.createElement('div');
container.className = 'global-notification';
// 创建通知内容
const icons = {
info: 'ℹ️',
success: '✅',
warning: '⚠️',
error: '❌'
};
container.innerHTML = `
<div class="global-notification-icon">${icons[type]}</div>
<div class="global-notification-content">${options.message}</div>
<div class="global-notification-close">×</div>
`;
container.classList.add(type);
// 添加到容器
notificationContainer?.appendChild(container);
// 触发进入动画
setTimeout(() => {
container.classList.add('show');
}, 10);
// 绑定关闭事件
const closeBtn = container.querySelector('.global-notification-close');
const close = () => {
container.classList.remove('show');
setTimeout(() => {
container.remove();
options.onClose?.();
}, 300);
};
closeBtn?.addEventListener('click', close);
// 自动关闭
if (duration > 0) {
setTimeout(close, duration);
}
// 保存实例
const instance: NotificationInstance = {
id,
vnode: createVNode('div'),
container
};
notifications.push(instance);
return instance;
};
// 提供不同类型的通知方法
export const notification = {
open: (options: NotificationOptions) => {
createNotificationContainer();
return createNotification(options);
},
info: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'info',
...options
});
},
success: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'success',
...options
});
},
warning: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'warning',
...options
});
},
error: (message: string, options?: Omit<NotificationOptions, 'message' | 'type'>) => {
return notification.open({
message,
type: 'error',
...options
});
},
// 关闭所有通知
closeAll: () => {
notifications.forEach(instance => {
instance.container.remove();
});
notifications.length = 0;
}
};
// Vue插件安装方法
export default {
install(app: App) {
app.config.globalProperties.$notification = notification;
app.provide('notification', notification);
}
};
效果展示
编辑
编辑