<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>
|