<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.noticeTitle }}</div>
|
<div class="notification-detail">{{ item.noticeContent }}</div>
|
<div class="notification-time">{{ item.createTime }}</div>
|
</div>
|
<div class="notification-action">
|
<el-button type="primary"
|
size="small"
|
@click="handleConfirm(item)">
|
确认
|
</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.noticeTitle }}</div>
|
<div class="notification-detail">{{ item.noticeContent }}</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";
|
import useUserStore from "@/store/modules/user";
|
import { useRouter } from "vue-router";
|
|
const userStore = useUserStore();
|
const router = useRouter();
|
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 consigneeId = userStore.id;
|
if (!consigneeId) {
|
console.warn("未获取到当前登录用户ID");
|
return;
|
}
|
const params = {
|
consigneeId: consigneeId,
|
current: pageNum.value,
|
size: pageSize.value,
|
status: activeTab.value === "read" ? 1 : 0,
|
};
|
const res = await listMessage(params);
|
if (res.code === 200) {
|
if (activeTab.value === "unread") {
|
unreadList.value = res.data.records || [];
|
} else {
|
readList.value = res.data.records || [];
|
}
|
total.value = res.data.total || 0;
|
}
|
} catch (error) {
|
console.error("加载消息列表失败:", error);
|
}
|
};
|
|
// 加载未读数量
|
const loadUnreadCount = async () => {
|
try {
|
const consigneeId = userStore.id;
|
if (!consigneeId) {
|
console.warn("未获取到当前登录用户ID");
|
return;
|
}
|
const res = await getUnreadCount(consigneeId);
|
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 item => {
|
try {
|
console.log("item", item);
|
const res = await confirmMessage(item.noticeId, 1);
|
if (res.code === 200) {
|
ElMessage.success("确认成功");
|
// 重新加载数据
|
loadMessages();
|
loadUnreadCount();
|
|
// 根据 jumpPath 进行页面跳转
|
if (item.jumpPath) {
|
try {
|
// 解析 jumpPath,分离路径和查询参数
|
const [path, queryString] = item.jumpPath.split("?");
|
let query = {};
|
|
if (queryString) {
|
// 解析查询参数
|
queryString.split("&").forEach(param => {
|
const [key, value] = param.split("=");
|
if (key && value) {
|
query[key] = decodeURIComponent(value);
|
}
|
});
|
}
|
|
// 跳转到指定页面
|
router.push({
|
path: path,
|
query: query,
|
});
|
} catch (error) {
|
console.error("页面跳转失败:", error);
|
}
|
}
|
}
|
} 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>
|