From 68f2f010d1d2d36b5a1f5550fb4e8ae13601b241 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 19 Jun 2026 10:02:16 +0800 Subject: [PATCH] fix(notification): improve notification expansion handling and styling --- src/layouts/components/UserNotification.vue | 82 ++++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/src/layouts/components/UserNotification.vue b/src/layouts/components/UserNotification.vue index 5c232791..ab12dffb 100644 --- a/src/layouts/components/UserNotification.vue +++ b/src/layouts/components/UserNotification.vue @@ -14,8 +14,8 @@ const $toast = useToast() const createConfirm = useConfirm() const PAGE_SIZE = 20 -// 固定通知项高度,配合 VVirtualScroll 避免历史通知过多时一次性渲染全部 DOM。 -const NOTIFICATION_ITEM_HEIGHT = 104 +// 虚拟滚动的默认通知项高度,展开后的实际高度由 VVirtualScroll 的 itemRef 动态测量。 +const NOTIFICATION_ITEM_HEIGHT = 136 const CLEAR_NOTIFICATION_ENDPOINTS = ['message/notification', 'message/notification/clear'] const NOTIFICATION_CLEAR_BEFORE_STORAGE_KEY = 'moviepilot-notification-clear-before' @@ -28,6 +28,7 @@ const clearing = ref(false) const hasMore = ref(true) const notificationKeys = new Set() const notificationClearBefore = ref(readNotificationClearBefore()) +const expandedNotificationKeys = ref(new Set()) const hasUnreadNotifications = computed(() => notificationList.value.some(item => item.read === false)) @@ -101,6 +102,11 @@ function getNotificationKey(item: SystemNotification) { return item.id ? `id:${item.id}` : `content:${getNotificationContentKey(item)}` } +/** 获取通知正文展开状态使用的稳定 key。 */ +function getNotificationExpansionKey(item: SystemNotification) { + return getNotificationKey(item) +} + /** 将通知时间解析成时间戳,用于列表降序排序。 */ function parseNotificationTime(value: string) { if (!value) return 0 @@ -181,6 +187,7 @@ function mergeNotifications(items: SystemNotification[], options: { prepend?: bo function resetNotifications() { notificationList.value = [] notificationKeys.clear() + expandedNotificationKeys.value = new Set() page.value = 1 hasMore.value = true hasNewMessage.value = false @@ -335,12 +342,28 @@ function isMediaNotification(item: SystemNotification) { return Boolean(item.image) } -/** 打开通知链接,并在需要时同步清理未读角标。 */ -function openNotification(item: SystemNotification) { +/** 判断通知正文是否已经展开。 */ +function isNotificationExpanded(item: SystemNotification) { + return expandedNotificationKeys.value.has(getNotificationExpansionKey(item)) +} + +/** 标记单条通知为已读,并在全部已读时同步清理未读角标。 */ +function markNotificationAsRead(item: SystemNotification) { item.read = true hasNewMessage.value = hasUnreadNotifications.value if (!hasUnreadNotifications.value) void clearUnreadMessages() - if (item.link) window.open(item.link, '_blank') +} + +/** 切换通知正文展开状态。 */ +function toggleNotificationExpanded(item: SystemNotification) { + markNotificationAsRead(item) + if (!item.text) return + + const key = getNotificationExpansionKey(item) + const expandedKeys = new Set(expandedNotificationKeys.value) + if (expandedKeys.has(key)) expandedKeys.delete(key) + else expandedKeys.add(key) + expandedNotificationKeys.value = expandedKeys } useDelayedSSE( @@ -362,7 +385,7 @@ useDelayedSSE( width="420" max-width="calc(100vw - 24px)" transition="scale-transition" - close-on-content-click + :close-on-content-click="false" class="notification-menu" scrim > @@ -439,14 +462,18 @@ useDelayedSSE( > @@ -505,7 +536,6 @@ useDelayedSSE( } .notification-virtual-item { - block-size: 110px; padding-block: 4px; padding-inline: 8px; } @@ -518,7 +548,6 @@ useDelayedSSE( border: 0; border-radius: 8px; background: transparent; - block-size: 100%; color: inherit; cursor: pointer; gap: 12px; @@ -573,18 +602,22 @@ useDelayedSSE( .notification-title-row { display: flex; - align-items: center; + align-items: flex-start; gap: 8px; - min-block-size: 20px; + min-block-size: 24px; } .notification-title { + display: -webkit-box; overflow: hidden; + flex: 1 1 auto; + -webkit-box-orient: vertical; font-size: 0.925rem; font-weight: 600; line-height: 1.35; + -webkit-line-clamp: 2; text-overflow: ellipsis; - white-space: nowrap; + white-space: normal; } .notification-unread-dot { @@ -593,20 +626,31 @@ useDelayedSSE( background: rgb(var(--v-theme-error)); block-size: 7px; inline-size: 7px; + margin-block-start: 0.45rem; } .notification-text { - display: -webkit-box; + display: block; overflow: hidden; - -webkit-box-orient: vertical; + padding: 0; + border: 0; + background: transparent; + block-size: auto; color: rgba(var(--v-theme-on-surface), var(--v-medium-emphasis-opacity)); + cursor: pointer; font-size: 0.8125rem; - -webkit-line-clamp: 2; line-height: 1.45; margin-block-start: 4px; + max-block-size: calc(0.8125rem * 1.45 * 3); + text-align: start; white-space: pre-wrap; } +.notification-text--expanded { + max-block-size: none; + overflow: visible; +} + .notification-meta { display: flex; flex-wrap: wrap;