| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="notification-popover-content"> |
| | | <div class="popover-header"> |
| | | <span class="popover-title">æ¶æ¯éç¥</span> |
| | | <el-button type="primary" size="small" @click="handleMarkAllAsRead" :disabled="unreadCount === 0"> |
| | | ä¸é®å·²è¯» |
| | | </el-button> |
| | | </div> |
| | | |
| | | <div class="notification-content"> |
| | | <el-tabs v-model="activeTab" @tab-change="handleTabChange"> |
| | | <el-tab-pane :label="`æªè¯»(${unreadCount})`" name="unread"> |
| | | <div v-if="unreadList.length === 0" class="empty-state"> |
| | | <el-empty description="ææ æªè¯»æ¶æ¯" /> |
| | | </div> |
| | | <div v-else class="notification-list"> |
| | | <div |
| | | v-for="item in unreadList" |
| | | :key="item.id" |
| | | class="notification-item" |
| | | > |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" color="#67C23A"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="notification-content-wrapper"> |
| | | <div class="notification-title">{{ item.title }}</div> |
| | | <div class="notification-detail">{{ item.content }}</div> |
| | | <div class="notification-time">{{ item.createTime }}</div> |
| | | </div> |
| | | <div class="notification-action"> |
| | | <el-button type="primary" size="small" @click="handleConfirm(item.id)"> |
| | | 确认 |
| | | </el-button> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | <el-tab-pane label="已读" name="read"> |
| | | <div v-if="readList.length === 0" class="empty-state"> |
| | | <el-empty description="ææ å·²è¯»æ¶æ¯" /> |
| | | </div> |
| | | <div v-else class="notification-list"> |
| | | <div |
| | | v-for="item in readList" |
| | | :key="item.id" |
| | | class="notification-item read" |
| | | > |
| | | <div class="notification-icon"> |
| | | <el-icon :size="24" color="#909399"> |
| | | <Bell /> |
| | | </el-icon> |
| | | </div> |
| | | <div class="notification-content-wrapper"> |
| | | <div class="notification-title">{{ item.title }}</div> |
| | | <div class="notification-detail">{{ item.content }}</div> |
| | | <div class="notification-time">{{ item.createTime }}</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | |
| | | <!-- å页 --> |
| | | <div class="pagination-wrapper" v-if="total > 0"> |
| | | <el-pagination |
| | | v-model:current-page="pageNum" |
| | | v-model:page-size="pageSize" |
| | | :page-sizes="[10, 20, 50, 100]" |
| | | :total="total" |
| | | layout="prev, pager, next, sizes" |
| | | @size-change="handleSizeChange" |
| | | @current-change="handlePageChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Bell } from '@element-plus/icons-vue' |
| | | import { listMessage, markAsRead, markAllAsRead, confirmMessage, getUnreadCount } from '@/api/system/message' |
| | | import { ElMessage } from 'element-plus' |
| | | |
| | | const emit = defineEmits(['unreadCountChange']) |
| | | |
| | | const activeTab = ref('unread') |
| | | const unreadList = ref([]) |
| | | const readList = ref([]) |
| | | const unreadCount = ref(0) |
| | | const total = ref(0) |
| | | const pageNum = ref(1) |
| | | const pageSize = ref(10) |
| | | |
| | | // å è½½æ¶æ¯å表 |
| | | const loadMessages = async () => { |
| | | try { |
| | | const params = { |
| | | pageNum: pageNum.value, |
| | | pageSize: pageSize.value, |
| | | isRead: activeTab.value === 'read' ? 1 : 0 |
| | | } |
| | | const res = await listMessage(params) |
| | | if (res.code === 200) { |
| | | if (activeTab.value === 'unread') { |
| | | unreadList.value = res.rows || [] |
| | | } else { |
| | | readList.value = res.rows || [] |
| | | } |
| | | total.value = res.total || 0 |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½æ¶æ¯å表失败:', error) |
| | | } |
| | | } |
| | | |
| | | // å è½½æªè¯»æ°é |
| | | const loadUnreadCount = async () => { |
| | | try { |
| | | const res = await getUnreadCount() |
| | | if (res.code === 200) { |
| | | unreadCount.value = res.data || 0 |
| | | emit('unreadCountChange', unreadCount.value) |
| | | } |
| | | } catch (error) { |
| | | console.error('å è½½æªè¯»æ°é失败:', error) |
| | | } |
| | | } |
| | | |
| | | // æ ç¾é¡µåæ¢ |
| | | const handleTabChange = (tab) => { |
| | | pageNum.value = 1 |
| | | loadMessages() |
| | | } |
| | | |
| | | // ç¡®è®¤æ¶æ¯ |
| | | const handleConfirm = async (messageId) => { |
| | | try { |
| | | const res = await confirmMessage(messageId) |
| | | if (res.code === 200) { |
| | | ElMessage.success('确认æå') |
| | | // æ 记为已读 |
| | | await markAsRead(messageId) |
| | | // éæ°å è½½æ°æ® |
| | | loadMessages() |
| | | loadUnreadCount() |
| | | } |
| | | } catch (error) { |
| | | console.error('ç¡®è®¤æ¶æ¯å¤±è´¥:', error) |
| | | ElMessage.error('确认失败') |
| | | } |
| | | } |
| | | |
| | | // ä¸é®å·²è¯» |
| | | const handleMarkAllAsRead = async () => { |
| | | try { |
| | | const res = await markAllAsRead() |
| | | if (res.code === 200) { |
| | | ElMessage.success('å·²å
¨é¨æ 记为已读') |
| | | loadMessages() |
| | | loadUnreadCount() |
| | | } |
| | | } catch (error) { |
| | | console.error('ä¸é®å·²è¯»å¤±è´¥:', error) |
| | | ElMessage.error('æä½å¤±è´¥') |
| | | } |
| | | } |
| | | |
| | | // å页大尿¹å |
| | | const handleSizeChange = (size) => { |
| | | pageSize.value = size |
| | | pageNum.value = 1 |
| | | loadMessages() |
| | | } |
| | | |
| | | // é¡µç æ¹å |
| | | const handlePageChange = (page) => { |
| | | pageNum.value = page |
| | | loadMessages() |
| | | } |
| | | |
| | | // ç»ä»¶æè½½æ¶å è½½æªè¯»æ°é |
| | | onMounted(() => { |
| | | loadUnreadCount() |
| | | }) |
| | | |
| | | // çå¬ç¶ç»ä»¶ä¼ éç visible ç¶æï¼éè¿ watch å¨ Navbar ä¸å¤çï¼ |
| | | // è¿éåªè´è´£æ°æ®å è½½ï¼ä¸æ§å¶æ¾ç¤º |
| | | |
| | | // æ´é²æ¹æ³ä¾å¤é¨è°ç¨ |
| | | defineExpose({ |
| | | loadUnreadCount, |
| | | loadMessages |
| | | }) |
| | | </script> |
| | | |
| | | <style lang="scss" scoped> |
| | | .notification-popover-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | width: 500px; |
| | | padding: 16px; |
| | | } |
| | | |
| | | .popover-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | width: 100%; |
| | | margin-bottom: 16px; |
| | | padding-bottom: 12px; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | |
| | | .popover-title { |
| | | font-size: 18px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | } |
| | | } |
| | | |
| | | .notification-content { |
| | | max-height: 60vh; |
| | | display: flex; |
| | | flex-direction: column; |
| | | |
| | | :deep(.el-tabs) { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | min-height: 0; |
| | | |
| | | .el-tabs__header { |
| | | margin-bottom: 0; |
| | | flex-shrink: 0; |
| | | padding: 0; |
| | | } |
| | | |
| | | .el-tabs__content { |
| | | flex: 1; |
| | | overflow-y: auto; |
| | | min-height: 0; |
| | | padding-top: 16px; |
| | | } |
| | | |
| | | .el-tab-pane { |
| | | height: 100%; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .empty-state { |
| | | display: flex; |
| | | justify-content: center; |
| | | align-items: center; |
| | | min-height: 300px; |
| | | padding: 40px 0; |
| | | } |
| | | |
| | | .notification-list { |
| | | .notification-item { |
| | | display: flex; |
| | | padding: 12px 0; |
| | | border-bottom: 1px solid #f0f0f0; |
| | | transition: background-color 0.3s; |
| | | |
| | | &:hover { |
| | | background-color: #f5f7fa; |
| | | } |
| | | |
| | | &.read { |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | .notification-icon { |
| | | flex-shrink: 0; |
| | | width: 40px; |
| | | height: 40px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #f0f9ff; |
| | | border-radius: 50%; |
| | | margin-right: 12px; |
| | | } |
| | | |
| | | .notification-content-wrapper { |
| | | flex: 1; |
| | | min-width: 0; |
| | | |
| | | .notification-title { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .notification-detail { |
| | | font-size: 13px; |
| | | color: #606266; |
| | | line-height: 1.5; |
| | | margin-bottom: 8px; |
| | | word-break: break-all; |
| | | } |
| | | |
| | | .notification-time { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | } |
| | | |
| | | .notification-action { |
| | | flex-shrink: 0; |
| | | margin-left: 12px; |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .pagination-wrapper { |
| | | margin-top: 16px; |
| | | padding-top: 16px; |
| | | border-top: 1px solid #f0f0f0; |
| | | display: flex; |
| | | justify-content: center; |
| | | padding-left: 0; |
| | | padding-right: 0; |
| | | } |
| | | </style> |
| | | |