开发者问题收集

如何使用 Vuetify 显示多个通知?

2023-03-02
2627

我有一个使用 Vuetify 的 Vue 3 应用。在组件内部,我正在监听流式事件。对于每个新事件,我希望显示一个在 x 秒后淡出的通知。我认为 Snackbar 组件是正确的选择,但不幸的是,不支持 snackbar 列表( 也许在不久的将来

我从一个 NotificationList 组件开始,该组件期望将通知作为 prop,并尝试将它们显示在堆叠列表中

复制链接

<template>
  <v-snackbar 
              v-for="[notificationId, notificationMessage] in notifications"
              :ref="snackbarElement => notificationReferences.set(notificationId, snackbarElement)"
              :key="notificationId" 
              :model-value="true"
              location="top right"
              @update:model-value="emit('removeNotification', notificationId)"
  >
    {{ notificationMessage }}
  </v-snackbar>
</template>

<script setup lang="ts">
import { ref, watch, watchEffect } from 'vue'

const props = defineProps<{ notifications: Map<string, string> }>();

const emit = defineEmits<{
  (e: 'removeNotification', id: string): void
}>()

const notificationReferences = ref(new Map<string, unknown>());

watch(props.notifications, () => {
  // clear all references to avoid dead ones
  // notificationReferences.value.clear();
}, { immediate: true });

watchEffect(() => {
  let notificationIndex = 0;
  
  for (const [notificationId, snackbarElement] of notificationReferences.value) {
    // const snackbarElementHeight = ??? // get height from Vuetify component
    const marginTop = notificationIndex * 60; // snackbarElementHeight;
    
    // snackbarElement.style.marginTop = marginTop + "px";
    // snackbarElement.style.setProperty('margin-top', marginTop + "px");
    
    notificationIndex++;
  }
});
</script>

因此其他组件,例如 App 组件可以像这样使用它

<template>
  <v-app>
    <v-main>
      <v-btn @click="addNotification">New notification</v-btn>
      <notification-list :notifications="notifications" @removeNotification="removeNotification" />
    </v-main>
  </v-app>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import NotificationList from './NotificationList.vue'

const notifications = ref<Map<string, string>>(new Map())

function addNotification() {
  const notificationId = self.crypto.randomUUID();
  const notificationMessage = (new Date()).toString();
  
  notifications.value.set(notificationId, notificationMessage);
}

function removeNotification(notificationId) {
  notifications.value.delete(notificationId);
}
</script>

我认为代码几乎没问题,唯一缺少的是垂直偏移量,目前所有 snackbar 都具有相同的位置。

问题是,当我尝试将边距应用于 snackbarElement 时,我收到错误

TypeError: Cannot set properties of undefined (setting 'marginTop')

但是当我尝试调试它时,例如记录变量,我收到错误

TypeError: Cannot convert object to primitive value

我认为这与 Vuetify 有关,因为我无法使用 Vue 和普通 div 元素重现它, 示例

您有什么想法可以解决此问题吗?也许我的方法可以简化?


旁注:

我发现 https://vuetifyjs.com/en/api/v-snackbar/#props-offset ,所以也许我根本不需要处理 notificationReferences ,但 offset prop 没有影响, 示例


“糟糕”的临时解决方法

我可以添加一个计算属性来计算每个 snackbar 的边距, 假设 60px 的边距就足够了 ,所以这个解决方案非常简单,但 对于不同的 snackbar 高度不起作用

解决方案

2个回答

恐怕你得不到规范的答案, 至于 Material Design 规范

When multiple snackbar updates are necessary, they should appear one at a time.

[…] Avoid stacking snackbars on top of one another.

如果你无论如何都想这样做,我建议使用 v-alert 组件。你需要一些样式、过渡和超时才能得到你想要的。这不是完美的解决方案,但在我看来,比尝试使用 snackbars 做类似的事情要少得多。

<div class="notificationContainer">
  <v-slide-y-transition group>
    <v-alert
      v-for="notification in notifications"
      theme="dark"
    >{{ notification[1] }}</v-alert>
  </v-slide-y-transition>
</div>
<v-btn @click="addNotification">New notification</v-btn>
.notificationContainer {
  position: fixed;
  top: 10px;
  right: 10px;
  display: grid;
  grid-gap: .5em;
  z-index: 99;
}

在这里 您可以看到一个有效示例。

etuardu
2023-03-15

使用过滤方法代替拼接

Ahsan Ali
2023-03-07