notification.vue 4.1 KB

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