如何在vue项目中封装自己的全局message组件?一步教会你!

我都使用过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);
  }
};

效果展示

​编辑

​编辑

相关推荐
袁煦丞2 分钟前
家用NAS+云盘自由NanoPi R4S+iStoreOS:cpolar内网穿透实验室第460个成功挑战
前端·程序员·远程工作
浏览器API调用工程师_Taylor22 分钟前
日报自动化实战:告别手动复制粘贴
前端·javascript·node.js
晴殇i27 分钟前
JavaScript还能这样写?!ES2025新语法让代码优雅到极致
前端·javascript·程序员
浏览器API调用工程师_Taylor37 分钟前
我是如何将手动的日报自动化的☺️☺️☺️
前端·javascript·爬虫
前端Hardy1 小时前
HTML&CSS&JS:抖音爆火的满屏“关心弹幕”酷炫卡片,已经帮你打包好了,快来体验吧!
前端·javascript·css
江城开朗的豌豆1 小时前
我的Vue项目胖成球了!用Webpack给它狠狠瘦个身
前端·javascript
WebInfra1 小时前
Rspack 1.6 发布:让打包产物更小、更纯净
前端·javascript·前端框架
Mintopia1 小时前
⚙️ Next.js 接口限流与审计全攻略 —— 用 @upstash/ratelimit 打造优雅“闸门”
前端·javascript·全栈
Mintopia2 小时前
🌐 实时翻译 + AIGC:Web跨语言内容生成的技术闭环
前端·javascript·aigc
Cache技术分享2 小时前
225. Java 集合 - List接口 —— 记住顺序的集合
前端·后端