You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解决Vuetify中多条Snackbar通知堆叠偏移的问题?

Vue 3 + Vuetify 多通知Snackbar重叠问题解决方案

核心问题分析

你遇到的本质问题是Vuetify的v-snackbar默认采用fixed定位,多个实例会共享同一坐标原点导致重叠;手动操作DOM元素报错是因为没有正确获取到组件的真实DOM节点,且fixed定位下的margin偏移逻辑本身不适合多元素排列场景。


方案1:Flex容器自动适配(推荐)

不需要手动计算高度,通过CSS布局让通知自动排列,完美适配不同高度的Snackbar:

NotificationList组件代码

<template>
  <div class="notification-stack">
    <v-snackbar
      v-for="item in notifications"
      :key="item.id"
      v-model="item.show"
      :timeout="item.timeout || 3000"
      @update:model-value="handleClose(item.id)"
    >
      {{ item.message }}
      <template v-slot:actions>
        <v-btn text @click="item.show = false">关闭</v-btn>
      </template>
    </v-snackbar>
  </div>
</template>

<script setup>
const props = defineProps({
  notifications: {
    type: Array,
    required: true,
    validator: arr => arr.every(n => n.id && n.message)
  }
})

const emit = defineEmits(['remove-notification'])

const handleClose = (id) => {
  emit('remove-notification', id)
}
</script>

<style scoped>
.notification-stack {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 9999;
  display: flex;
  flex-direction: column; /* 新通知在下方,换成column-reverse可让新通知在上方 */
  gap: 8px; /* 通知间的固定间距 */
}

/* 覆盖Vuetify默认的fixed定位,让Snackbar跟随容器布局 */
.notification-stack :deep(.v-snackbar) {
  position: static !important;
  max-width: 400px; /* 限制通知最大宽度,可选 */
}
</style>

方案2:动态计算偏移量(兼容复杂场景)

如果需要更精细的位置控制,可以通过获取DOM元素的实际高度动态计算偏移:

组件代码示例

<template>
  <div class="notification-container">
    <v-snackbar
      v-for="(item, idx) in notifications"
      :key="item.id"
      v-model="item.show"
      :timeout="item.timeout || 3000"
      :ref="el => el && (snackbarRefs[idx] = el)"
      @update:model-value="reCalculateOffsets"
    >
      {{ item.message }}
      <template v-slot:actions>
        <v-btn text @click="item.show = false">关闭</v-btn>
      </template>
    </v-snackbar>
  </div>
</template>

<script setup>
import { ref, watch, nextTick } from 'vue'

const props = defineProps({
  notifications: {
    type: Array,
    required: true
  }
})

const snackbarRefs = ref([])

// 监听通知列表变化,实时计算偏移
watch(() => props.notifications, reCalculateOffsets, { deep: true, immediate: true })

async function reCalculateOffsets() {
  await nextTick() // 等待DOM更新完成
  let totalOffset = 0
  snackbarRefs.value.forEach(el => {
    if (!el) return
    el.style.bottom = `${totalOffset + 24}px` // 24px是初始底部间距
    totalOffset += el.offsetHeight + 8 // 累加当前通知高度+间距
  })
}
</script>

<style scoped>
.notification-container {
  position: fixed;
  right: 24px;
  z-index: 9999;
}

.v-snackbar {
  position: fixed !important;
  right: 24px;
}
</style>

简化实现思路:全局通知管理器

避免在各个组件中维护通知列表,封装全局通知工具:

1. 创建Pinia状态管理(或简单响应式对象)

// stores/notification.js
import { ref } from 'vue'

export const useNotificationStore = () => {
  const notifications = ref([])
  let idCounter = 0

  const add = (options) => {
    const id = `notify-${idCounter++}`
    const notify = {
      id,
      message: options.message,
      timeout: options.timeout || 3000,
      show: true
    }
    notifications.value.push(notify)
    
    // 自动关闭并清理
    setTimeout(() => {
      close(id)
    }, notify.timeout)
  }

  const close = (id) => {
    const idx = notifications.value.findIndex(n => n.id === id)
    if (idx === -1) return
    notifications.value[idx].show = false
    // 等待关闭动画结束后移除
    setTimeout(() => notifications.value.splice(idx, 1), 300)
  }

  return { notifications, add, close }
}

2. 在App.vue中挂载通知组件

<template>
  <v-app>
    <!-- 其他页面内容 -->
    <NotificationList 
      :notifications="notificationStore.notifications"
      @remove-notification="notificationStore.close"
    />
  </v-app>
</template>

<script setup>
import { useNotificationStore } from '@/stores/notification'
import NotificationList from '@/components/NotificationList.vue'

const notificationStore = useNotificationStore()
</script>

3. 在任意组件中调用

<script setup>
import { useNotificationStore } from '@/stores/notification'

const notify = useNotificationStore()

// 监听流式事件时触发
const handleStreamEvent = (event) => {
  notify.add({
    message: event.content,
    timeout: 4000
  })
}
</script>

内容的提问来源于stack exchange,提问作者baitendbidz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 19:20:47