notification.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. <script lang="ts" setup>
  2. import type { NotificationItem } from './types';
  3. import { Bell, MailCheck } from '@vben/icons';
  4. import { $t } from '@vben/locales';
  5. import {
  6. VbenButton,
  7. VbenIconButton,
  8. VbenPopover,
  9. VbenScrollbar,
  10. } from '@vben-core/shadcn-ui';
  11. import { useToggle } from '@vueuse/core';
  12. interface Props {
  13. /**
  14. * 显示圆点
  15. */
  16. dot?: boolean;
  17. /**
  18. * 消息列表
  19. */
  20. notifications?: NotificationItem[];
  21. }
  22. defineOptions({ name: 'NotificationPopup' });
  23. withDefaults(defineProps<Props>(), {
  24. dot: false,
  25. notifications: () => [],
  26. });
  27. const emit = defineEmits<{
  28. clear: [];
  29. makeAll: [];
  30. read: [NotificationItem];
  31. viewAll: [];
  32. }>();
  33. const [open, toggle] = useToggle();
  34. function close() {
  35. open.value = false;
  36. }
  37. function handleViewAll() {
  38. emit('viewAll');
  39. close();
  40. }
  41. function handleMakeAll() {
  42. emit('makeAll');
  43. }
  44. function handleClear() {
  45. emit('clear');
  46. }
  47. function handleClick(item: NotificationItem) {
  48. emit('read', item);
  49. }
  50. </script>
  51. <template>
  52. <VbenPopover
  53. v-model:open="open"
  54. content-class="relative right-2 w-[360px] p-0"
  55. >
  56. <template #trigger>
  57. <div class="flex-center mr-2 h-full" @click.stop="toggle()">
  58. <VbenIconButton class="bell-button text-foreground relative">
  59. <span
  60. v-if="dot"
  61. class="bg-primary absolute right-0.5 top-0.5 h-2 w-2 rounded"
  62. ></span>
  63. <Bell class="size-4" />
  64. </VbenIconButton>
  65. </div>
  66. </template>
  67. <div class="relative">
  68. <div class="flex items-center justify-between p-4 py-3">
  69. <div class="text-foreground">{{ $t('ui.widgets.notifications') }}</div>
  70. <VbenIconButton
  71. :disabled="notifications.length <= 0"
  72. :tooltip="$t('ui.widgets.markAllAsRead')"
  73. @click="handleMakeAll"
  74. >
  75. <MailCheck class="size-4" />
  76. </VbenIconButton>
  77. </div>
  78. <VbenScrollbar v-if="notifications.length > 0">
  79. <ul class="!flex max-h-[360px] w-full flex-col">
  80. <template v-for="item in notifications" :key="item.title">
  81. <li
  82. class="hover:bg-accent border-border relative flex w-full cursor-pointer items-start gap-5 border-t px-3 py-3"
  83. @click="handleClick(item)"
  84. >
  85. <span
  86. v-if="!item.isRead"
  87. class="bg-primary absolute right-2 top-2 h-2 w-2 rounded"
  88. ></span>
  89. <span
  90. class="relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full"
  91. >
  92. <img
  93. :src="item.avatar"
  94. class="aspect-square h-full w-full object-cover"
  95. role="img"
  96. />
  97. </span>
  98. <div class="flex flex-col gap-1 leading-none">
  99. <p class="font-semibold">{{ item.title }}</p>
  100. <p class="text-muted-foreground my-1 line-clamp-2 text-xs">
  101. {{ item.message }}
  102. </p>
  103. <p class="text-muted-foreground line-clamp-2 text-xs">
  104. {{ item.date }}
  105. </p>
  106. </div>
  107. </li>
  108. </template>
  109. </ul>
  110. </VbenScrollbar>
  111. <template v-else>
  112. <div class="flex-center text-muted-foreground min-h-[150px] w-full">
  113. {{ $t('common.noData') }}
  114. </div>
  115. </template>
  116. <div
  117. class="border-border flex items-center justify-between border-t px-4 py-3"
  118. >
  119. <VbenButton
  120. :disabled="notifications.length <= 0"
  121. size="sm"
  122. variant="ghost"
  123. @click="handleClear"
  124. >
  125. {{ $t('ui.widgets.clearNotifications') }}
  126. </VbenButton>
  127. <VbenButton size="sm" @click="handleViewAll">
  128. {{ $t('ui.widgets.viewAll') }}
  129. </VbenButton>
  130. </div>
  131. </div>
  132. </VbenPopover>
  133. </template>
  134. <style scoped>
  135. :deep(.bell-button) {
  136. &:hover {
  137. svg {
  138. animation: bell-ring 1s both;
  139. }
  140. }
  141. }
  142. @keyframes bell-ring {
  143. 0%,
  144. 100% {
  145. transform-origin: top;
  146. }
  147. 15% {
  148. transform: rotateZ(10deg);
  149. }
  150. 30% {
  151. transform: rotateZ(-10deg);
  152. }
  153. 45% {
  154. transform: rotateZ(5deg);
  155. }
  156. 60% {
  157. transform: rotateZ(-5deg);
  158. }
  159. 75% {
  160. transform: rotateZ(2deg);
  161. }
  162. }
  163. </style>