优化站内信模块

This commit is contained in:
2026-06-17 23:07:39 +08:00
parent d3886e2475
commit 22e0e75c35
25 changed files with 2538 additions and 39 deletions
+278 -10
View File
@@ -24,18 +24,48 @@
:title="currentTheme === 'dark' ? '切换到亮色模式' : '切换到暗色模式'" />
<!-- 消息中心 -->
<el-dropdown trigger="click">
<el-dropdown trigger="click" @visible-change="handleDropdownVisibleChange">
<span class="el-dropdown-link" style="cursor: pointer;">
<el-button circle class="message-btn" title="消息中心">
<el-icon>
<Bell />
</el-icon>
</el-button>
<el-badge :value="unreadCount" :max="99" :hidden="unreadCount === 0" class="message-badge">
<el-button circle class="message-btn" title="消息中心">
<el-icon>
<Bell />
</el-icon>
</el-button>
</el-badge>
</span>
<template #dropdown>
<el-dropdown-menu class="message-menu" style="width: 260px;">
<el-dropdown-item disabled>暂无新消息</el-dropdown-item>
</el-dropdown-menu>
<div class="message-dropdown-container">
<div class="message-dropdown-header">
<span class="title">站内通知 ({{ unreadCount }}条未读)</span>
<el-link type="primary" :underline="false" class="mark-all-btn" v-if="unreadCount > 0" @click="handleMarkAllRead">全部已读</el-link>
</div>
<el-scrollbar max-height="300px">
<div v-if="loadingMessages" class="loading-state">
<el-icon class="is-loading"><Loading /></el-icon>
<span>加载中...</span>
</div>
<div v-else-if="messages.length === 0" class="empty-state">
<el-icon><Message /></el-icon>
<span>暂无新消息</span>
</div>
<div v-else class="message-list">
<div
v-for="item in messages"
:key="item.id"
class="message-item"
:class="{ unread: item.is_read === 0 }"
@click="handleMessageClick(item)"
>
<div class="message-item-header">
<span class="message-title">{{ item.title }}</span>
<span class="message-time">{{ formatTime(item.create_time) }}</span>
</div>
<div class="message-item-brief">{{ item.content }}</div>
</div>
</div>
</el-scrollbar>
</div>
</template>
</el-dropdown>
@@ -66,6 +96,12 @@
</div>
<div class="message-center">
<!-- 详情对话框 -->
<MessageDetailDialog
v-model="detailVisible"
:reminder="currentReminder"
@read-success="handleReadSuccess"
/>
</div>
</template>
@@ -77,8 +113,10 @@ const emit = defineEmits(['collapse']);
import { useAllDataStore, useMenuStore, useTabsStore } from "@/stores";
import { useAuthStore } from "@/stores/auth";
import { logout, getCurrentUser } from "@/api/login";
import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Expand } from '@element-plus/icons-vue';
import { User, SwitchButton, Sunny, Moon, Refresh, Bell, HomeFilled, Expand, Loading, Message } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { getMySiteReminders, readAllSiteReminders } from "@/api/sitereminder";
import MessageDetailDialog from "@/views/basicSettings/sitereminder/components/detail.vue";
const router = useRouter();
const route = useRoute();
@@ -133,6 +171,87 @@ async function refreshCache() {
}
}
// 站内信消息中心逻辑
const unreadCount = ref(0);
const messages = ref<any[]>([]);
const loadingMessages = ref(false);
const detailVisible = ref(false);
const currentReminder = ref<any>({});
const formatTime = (timeStr: any) => {
if (!timeStr) return "-";
const date = new Date(timeStr);
return date.toLocaleString();
};
const fetchUnreadCount = async () => {
if (!authStore.token) return;
try {
const res = await getMySiteReminders({ page: 1, pageSize: 1, isRead: 0 });
if (res.code === 200) {
unreadCount.value = res.data.total || 0;
}
} catch (err) {
console.error("fetchUnreadCount failed", err);
}
};
const fetchMessages = async () => {
if (!authStore.token) return;
loadingMessages.value = true;
try {
const res = await getMySiteReminders({ page: 1, pageSize: 5 });
if (res.code === 200) {
messages.value = res.data.list || [];
}
} catch (err) {
console.error("fetchMessages failed", err);
} finally {
loadingMessages.value = false;
}
};
const handleDropdownVisibleChange = (visible: boolean) => {
if (visible) {
fetchMessages();
fetchUnreadCount();
}
};
const handleMessageClick = (item: any) => {
currentReminder.value = item;
detailVisible.value = true;
};
const handleReadSuccess = (id: number) => {
const msg = messages.value.find(m => m.id === id);
if (msg && msg.is_read === 0) {
msg.is_read = 1;
unreadCount.value = Math.max(0, unreadCount.value - 1);
}
fetchUnreadCount();
};
const handleMarkAllRead = async () => {
try {
const res = await readAllSiteReminders();
if (res.code === 200) {
ElMessage.success("全部已读标记成功");
unreadCount.value = 0;
messages.value.forEach(m => m.is_read = 1);
}
} catch (err) {
console.error(err);
}
};
let timer: any = null;
const handleMessagesChanged = () => {
fetchUnreadCount();
fetchMessages();
};
onMounted(async () => {
await loadMenu();
if (!authStore.token) return;
@@ -144,6 +263,10 @@ onMounted(async () => {
} catch (e) {
console.error("getCurrentUser failed", e);
}
fetchUnreadCount();
timer = setInterval(fetchUnreadCount, 60000);
window.addEventListener('site-messages-changed', handleMessagesChanged);
});
// 根据菜单列表和当前路径计算出的面包屑导航
@@ -346,6 +469,10 @@ onUnmounted(() => {
if (mediaQuery && handleChange) {
mediaQuery.removeEventListener('change', handleChange);
}
if (timer) {
clearInterval(timer);
}
window.removeEventListener('site-messages-changed', handleMessagesChanged);
});
</script>
@@ -565,4 +692,145 @@ onUnmounted(() => {
:deep(.el-button) {
margin-left: 0 !important;
}
/* 消息中心下拉列表样式 */
.message-badge {
display: inline-flex;
align-items: center;
justify-content: center;
}
.message-dropdown-container {
width: 320px;
background-color: var(--el-bg-color-overlay);
border: 1px solid var(--el-border-color-light);
border-radius: 8px;
box-shadow: var(--el-box-shadow-light);
overflow: hidden;
display: flex;
flex-direction: column;
}
.message-dropdown-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--el-border-color-lighter);
background-color: var(--el-fill-color-blank);
.title {
font-size: 14px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.mark-all-btn {
font-size: 12px;
}
}
.loading-state,
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 30px 0;
color: var(--el-text-color-secondary);
font-size: 13px;
gap: 8px;
.el-icon {
font-size: 20px;
}
}
.message-list {
display: flex;
flex-direction: column;
}
.message-item {
padding: 12px 16px;
cursor: pointer;
transition: background-color 0.2s;
border-bottom: 1px solid var(--el-border-color-extra-light);
text-align: left; /* Ensure it is left-aligned */
&:hover {
background-color: var(--el-fill-color-light);
}
&.unread {
background-color: var(--el-color-primary-light-9);
&:hover {
background-color: var(--el-color-primary-light-8);
}
.message-title {
font-weight: 600;
color: var(--el-text-color-primary);
position: relative;
padding-left: 10px;
&::before {
content: '';
position: absolute;
left: 0;
top: 6px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--el-color-danger);
}
}
}
}
.message-item-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 4px;
gap: 8px;
.message-title {
font-size: 13px;
color: var(--el-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
text-align: left;
}
.message-time {
font-size: 11px;
color: var(--el-text-color-secondary);
white-space: nowrap;
}
}
.message-item-brief {
font-size: 12px;
color: var(--el-text-color-regular);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.message-dropdown-footer {
padding: 8px;
border-top: 1px solid var(--el-border-color-lighter);
text-align: center;
background-color: var(--el-fill-color-blank);
.el-button {
font-size: 13px;
width: 100%;
}
}
</style>