| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- <template>
- <div class="notification-center">
- <v-btn
- icon
- variant="text"
- @click="toggleNotifications"
- class="notification-btn"
- >
- <v-icon>mdi-bell</v-icon>
- <v-badge
- v-if="unreadCount > 0"
- :content="unreadCount"
- color="error"
- offset-x="12"
- offset-y="12"
- >
- </v-badge>
- </v-btn>
- <v-menu
- v-model="showNotifications"
- :close-on-content-click="false"
- location="bottom end"
- offset="8"
- >
- <template v-slot:activator="{ props }">
- <div v-bind="props"></div>
- </template>
- <v-card class="notification-menu" min-width="320" max-width="400">
- <v-card-title class="notification-header">
- <span>알림</span>
- <v-btn
- v-if="notifications.length > 0"
- variant="text"
- size="small"
- @click="markAllAsRead"
- >
- 모두 읽음
- </v-btn>
- </v-card-title>
-
- <v-divider></v-divider>
-
- <v-card-text class="notification-list" v-if="notifications.length > 0">
- <div
- v-for="notification in notifications"
- :key="notification.id"
- class="notification-item"
- :class="{ 'unread': !notification.read }"
- @click="markAsRead(notification.id)"
- >
- <div class="notification-content">
- <div class="notification-title">{{ notification.title }}</div>
- <div class="notification-message">{{ notification.message }}</div>
- <div class="notification-time">{{ formatTime(notification.createdAt) }}</div>
- </div>
- <v-btn
- icon="mdi-close"
- variant="text"
- size="x-small"
- @click.stop="removeNotification(notification.id)"
- ></v-btn>
- </div>
- </v-card-text>
-
- <v-card-text v-else class="text-center text-grey">
- 새로운 알림이 없습니다.
- </v-card-text>
- </v-card>
- </v-menu>
- </div>
- </template>
- <script setup>
- import { ref, computed, onMounted, onUnmounted } from 'vue'
- const { $eventBus } = useNuxtApp()
- const showNotifications = ref(false)
- const notifications = ref([])
- const unreadCount = computed(() => {
- return notifications.value.filter(n => !n.read).length
- })
- const toggleNotifications = () => {
- showNotifications.value = !showNotifications.value
- }
- const addNotification = (title, message, type = 'info') => {
- const notification = {
- id: Date.now() + Math.random(),
- title,
- message,
- type,
- read: false,
- createdAt: new Date()
- }
-
- notifications.value.unshift(notification)
-
- // 최대 50개까지만 보관
- if (notifications.value.length > 50) {
- notifications.value = notifications.value.slice(0, 50)
- }
- }
- const markAsRead = (id) => {
- const notification = notifications.value.find(n => n.id === id)
- if (notification) {
- notification.read = true
- }
- }
- const markAllAsRead = () => {
- notifications.value.forEach(n => n.read = true)
- }
- const removeNotification = (id) => {
- const index = notifications.value.findIndex(n => n.id === id)
- if (index > -1) {
- notifications.value.splice(index, 1)
- }
- }
- const formatTime = (date) => {
- const now = new Date()
- const diff = now - date
- const minutes = Math.floor(diff / 60000)
- const hours = Math.floor(minutes / 60)
- const days = Math.floor(hours / 24)
-
- if (minutes < 1) return '방금 전'
- if (minutes < 60) return `${minutes}분 전`
- if (hours < 24) return `${hours}시간 전`
- if (days < 7) return `${days}일 전`
-
- return date.toLocaleDateString()
- }
- const handleDeliveryStatusChange = (data) => {
- const statusText = {
- 'NEW': '신규',
- 'PENDING': '대기',
- 'COMPLETE': '완료'
- }[data.status] || data.status
-
- addNotification(
- '배송 상태 변경',
- `${data.itemName}의 상태가 "${statusText}"로 변경되었습니다.`,
- 'success'
- )
- }
- const handleNewOrderReceived = (data) => {
- addNotification(
- '새 주문 접수',
- `${data.itemName}에 새로운 주문이 접수되었습니다.`,
- 'info'
- )
- }
- onMounted(() => {
- $eventBus.on('DELIVERY_STATUS_CHANGED', handleDeliveryStatusChange)
- $eventBus.on('NEW_ORDER_RECEIVED', handleNewOrderReceived)
- })
- onUnmounted(() => {
- $eventBus.off('DELIVERY_STATUS_CHANGED', handleDeliveryStatusChange)
- $eventBus.off('NEW_ORDER_RECEIVED', handleNewOrderReceived)
- })
- </script>
- <style scoped>
- .notification-center {
- position: relative;
- }
- .notification-btn {
- position: relative;
- }
- .notification-menu {
- max-height: 500px;
- overflow-y: auto;
- }
- .notification-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 12px 16px;
- }
- .notification-list {
- padding: 0;
- max-height: 400px;
- overflow-y: auto;
- }
- .notification-item {
- display: flex;
- justify-content: space-between;
- align-items: flex-start;
- padding: 12px 16px;
- border-bottom: 1px solid #e0e0e0;
- cursor: pointer;
- transition: background-color 0.2s;
- }
- .notification-item:hover {
- background-color: #f5f5f5;
- }
- .notification-item.unread {
- background-color: #e3f2fd;
- }
- .notification-item.unread::before {
- content: '';
- position: absolute;
- left: 8px;
- top: 50%;
- transform: translateY(-50%);
- width: 6px;
- height: 6px;
- border-radius: 50%;
- background-color: #2196f3;
- }
- .notification-content {
- flex: 1;
- min-width: 0;
- }
- .notification-title {
- font-weight: 600;
- font-size: 14px;
- margin-bottom: 4px;
- }
- .notification-message {
- font-size: 13px;
- color: #666;
- margin-bottom: 4px;
- line-height: 1.3;
- }
- .notification-time {
- font-size: 11px;
- color: #999;
- }
- </style>
|