如何在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);
  }
};

效果展示

​编辑

​编辑

相关推荐
Setsuna_F_Seiei1 小时前
前端转型 Agent 开发 05 之 Agent Hooks 与 Checkpointer(让 Agent 从全自动转变人为可掌控)
前端·agent·ai编程
Rain的Java大神之路3 小时前
如何快速上传10G文件
java·spring boot·redis·后端·mysql·spring cloud·面试
百万蹄蹄向前冲3 小时前
风扇转了一晚上MVP专家团翻车事故
前端·人工智能
默_笙3 小时前
🏛 给 AI 配一间办公室:Harness Engineering 六大模块与它的实现
前端·javascript
淡海水3 小时前
13-04-面试-源码级深度追问链
数据结构·unity·面试·c#·游戏引擎·源码·il2cpp
linux_cfan5 小时前
videojs v10 源代码系列解读:14 · 谓词守卫:在运行时安全地调用能力
前端·javascript·音视频
kyriewen5 小时前
我扒了 10,221 条 JD:腾讯技术岗 75% 在要 AI
前端·人工智能·ai编程
郑州光合科技余经理6 小时前
同城外卖小程序开发:下单成功后,后台导出能不能对上用户端状态
开发语言·前端·git·后端·uni-app·php·ai编程
IT_陈寒6 小时前
Vue的响应式让我熬到凌晨三点,原来漏了这个小细节
前端·人工智能·后端
Blanche15007 小时前
利用 RAG 为答疑机器人扩展知识范围
前端