Vue3 封装优雅的全局状态提示组件(StatusMessage)

前言

在前端项目开发中,全局状态提示(如操作成功、失败提示)是高频交互需求。市面上的 UI 库(Element Plus、Ant Design Vue)虽提供了 Message 组件,但为了适配项目视觉风格、实现更轻量的定制化,手动封装全局提示组件会更灵活。本文基于 Vue3 组合式 API,手把手教你封装一个支持自定义类型、自动关闭、消息堆叠的全局 StatusMessage 组件。

一、组件设计思路

核心功能需求

  • 支持「成功 / 失败」两种基础提示类型,可扩展更多类型
  • 自定义提示显示时长,支持鼠标悬浮暂停自动关闭
  • 多个提示消息自动堆叠,计算偏移量避免重叠
  • 全局调用(无需在组件内注册,直接方法调用)
  • 轻量无冗余,适配项目自定义样式

技术方案

  • 利用 Vue3 的createVNode+render手动创建组件实例,实现全局调用
  • 维护消息队列,管理多消息的显示、关闭与位置计算
  • 封装快捷方法(success/error),简化调用逻辑
  • 结合Teleport将组件挂载到body,避免样式隔离问题

二、实现步骤

1. 编写提示组件本体(StatusMessage.vue)

先实现提示组件的 UI 结构、样式和基础交互,包括类型样式、过渡动画、自动关闭逻辑。

javascript 复制代码
<template>
  <teleport to="body">
    <div 
      v-if="visible" 
      :id="id"
      :class="['status-message', `status-message--${type}`]"
      :style="{ top: `${top}px`, ...transitionStyles }"
      @mouseenter="clearTimer"
      @mouseleave="startTimer"
    >
      <!-- 图标可替换为项目自有图标库 -->
      <i :class="iconClass"></i>
      <span class="status-message__text">{{ text }}</span>
    </div>
  </teleport>
</template>

<script setup>
import { ref, onMounted, onUnmounted, computed } from 'vue'

// 接收外部传入的属性
const props = defineProps({
  id: {
    type: String,
    required: true
  },
  top: {
    type: Number,
    default: 20 // 基础顶部偏移
  }
})

// 触发关闭事件,通知队列移除当前消息
const emit = defineEmits(['close'])

// 组件内部状态
const visible = ref(false)
const text = ref('')
const type = ref('success')
const duration = ref(3000)
let timer = null

// 不同类型的图标类名(可替换为svg/iconfont)
const iconClass = computed(() => {
  return type.value === 'success' 
    ? 'iconfont icon-success' 
    : 'iconfont icon-error'
})

// 过渡动画样式
const transitionStyles = computed(() => {
  return {
    transition: 'all 0.3s ease',
    opacity: visible.value ? 1 : 0,
    transform: visible.value ? 'translateX(-50%)' : 'translateX(-50%) translateY(-10px)',
    zIndex: 9999
  }
})

/**
 * 外部调用的显示方法
 * @param {Object} config 提示配置
 */
const showStatusMessage = (config) => {
  text.value = config.text
  type.value = config.type || 'success'
  duration.value = config.duration || 3000
  visible.value = true
  // 启动自动关闭定时器
  startTimer()
}

// 启动自动关闭定时器
const startTimer = () => {
  clearTimer()
  if (duration.value > 0) {
    timer = setTimeout(() => close(), duration.value)
  }
}

// 清除定时器(鼠标悬浮时调用)
const clearTimer = () => {
  timer && clearTimeout(timer)
}

// 关闭提示(带动画过渡)
const close = () => {
  visible.value = false
  // 动画结束后通知队列移除当前消息
  setTimeout(() => emit('close', props.id), 300)
}

// 暴露方法给外部调用
defineExpose({ showStatusMessage })

// 组件卸载时清除定时器,避免内存泄漏
onUnmounted(() => clearTimer())
</script>

<style scoped>
.status-message {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  padding: 8px 16px;
  border-radius: 4px;
  color: #fff;
  display: flex;
  align-items: center;
  gap: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

/* 成功类型样式 */
.status-message--success {
  background-color: #67c23a;
}

/* 失败类型样式 */
.status-message--error {
  background-color: #f56c6c;
}

.status-message__text {
  font-size: 14px;
  line-height: 1.4;
}
</style>

2. 封装全局调用方法(StatusMessage.js)

核心逻辑:创建组件实例、管理消息队列、计算偏移量、封装快捷调用方法。

javascript 复制代码
import { createVNode, render, nextTick } from 'vue'
import StatusMessageVue from '@renderer/components/StatusMessage.vue'

// 创建挂载容器,避免重复挂载
const container = document.createElement('div')
document.body.appendChild(container)

// 消息队列:管理所有显示中的提示实例
let messageQueue = []
// 消息ID生成器:确保每个消息唯一
let messageId = 0

/**
 * 计算消息偏移量,实现堆叠效果
 * 每个消息高度30px + 间距16px,基础顶部偏移20px
 */
const calculateOffsets = () => {
  messageQueue.forEach(({ vm }, index) => {
    vm.props.top = index * 46 + 20 // 30(高度) + 16(间距) = 46
  })
}

/**
 * 显示提示消息
 * @param {Object|string} options - 消息配置或纯文本
 * @param {string} options.text - 消息内容
 * @param {string} [options.type='success'] - 类型:success/error
 * @param {number} [options.duration=3000] - 显示时长(ms)
 */
const showMessage = async (options) => {
  // 生成唯一ID
  const id = `message_${messageId++}`

  // 处理参数:支持字符串快捷调用
  const config = typeof options === 'string'
    ? { text: options, type: 'success' }
    : { type: 'success', duration: 3000, ...options }

  // 创建组件虚拟节点
  const vnode = createVNode(StatusMessageVue, {
    id,
    // 关闭时从队列移除并重新计算偏移
    onClose: (closedId) => {
      messageQueue = messageQueue.filter(item => item.id !== closedId)
      calculateOffsets() // 重新计算剩余消息的偏移
    }
  })

  // 渲染组件到容器
  render(vnode, container)
  // 等待DOM渲染完成
  await nextTick()

  // 将实例加入队列
  messageQueue.push({
    id,
    vm: vnode.component,
    close: () => vnode.component.emitter.emit('close', id)
  })

  // 计算当前消息的偏移量
  calculateOffsets()

  // 调用组件内部方法显示消息
  vnode.component.exposed.showStatusMessage(config)
}

// 封装快捷方法:成功提示
showMessage.success = (text, duration) => {
  showMessage({ text, type: 'success', duration })
}

// 封装快捷方法:失败提示
showMessage.error = (text, duration) => {
  showMessage({ text, type: 'error', duration })
}

// 扩展:关闭所有提示
showMessage.closeAll = () => {
  messageQueue.forEach(item => item.close())
  messageQueue = []
}

export default showMessage


//使用方法
// showMessage.success('操作成功')
// showMessage.error('操作失败')
// showMessage({ text: '操作成功', type: 'success', duration: 5000 })
// showMessage({ text: '操作失败', type: 'error', duration: 5000 })

三、使用方法

1. 全局挂载(可选)

main.js中挂载到 Vue 原型,方便所有组件调用:

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import showMessage from './utils/StatusMessage'

const app = createApp(App)
// 全局挂载
app.config.globalProperties.$message = showMessage
app.mount('#app')

2. 组件内调用

选项式 API
javascript 复制代码
// 成功提示
this.$message.success('操作成功!')

// 失败提示(自定义时长)
this.$message.error('操作失败,请重试', 5000)

// 自定义配置
this.$message({
  text: '自定义提示',
  type: 'error',
  duration: 10000
})
组合式 API
javascript 复制代码
import showMessage from '@/utils/StatusMessage'

// 成功提示
showMessage.success('操作成功!')

// 关闭所有提示
showMessage.closeAll()
相关推荐
北杳同学1 小时前
前端一些用得上的有意思网站
前端·javascript·vue.js·学习
夏小花花2 小时前
<editor> 组件设置样式不生效问题
java·前端·vue.js·xss
北辰alk3 小时前
Vue Router 组件内路由钩子全解析
前端·vue.js
JIngJaneIL3 小时前
基于Java二手交易管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot
一 乐3 小时前
宠物店管理|基于Java+vue的宠物猫店管理管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
渴望成为python大神的前端小菜鸟3 小时前
VUE 面试题
前端·javascript·vue.js·面试题
JIngJaneIL4 小时前
基于Java失物招领系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·vue
用户841794814564 小时前
如何使用 vxe-gantt table 甘特图来实现多个维度视图展示,支持切换年视图、月视图、周视图等
vue.js
by__csdn4 小时前
Vue 双向数据绑定深度解析:从原理到实践的全方位指南
前端·javascript·vue.js·typescript·前端框架·vue·ecmascript