如何使用 Vuetify 显示多个通知?
我有一个使用 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 高度不起作用
恐怕你得不到规范的答案, 至于 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;
}
在这里 您可以看到一个有效示例。
使用过滤方法代替拼接