This commit is contained in:
2026-04-24 10:37:05 +08:00
parent 1a6be585ff
commit 554a91f78e
203 changed files with 16148 additions and 18160 deletions
@@ -1,101 +1,106 @@
<template>
<div>
<el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover">
<el-popover ref="noticePopover" placement="bottom-end" :width="320" trigger="manual" v-model:visible="noticeVisible" popper-class="notice-popover">
<!-- 弹出内容 -->
<div class="notice-header">
<span class="notice-title">通知公告</span>
<span class="notice-mark-all" @click="markAllRead">全部已读</span>
</div>
<div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div>
<div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div>
<div v-if="noticeLoading" class="notice-loading">
<el-icon class="is-loading"><Loading /></el-icon> 加载中...
</div>
<div v-else-if="noticeList.length === 0" class="notice-empty">
<el-icon style="font-size:24px;display:block;margin-bottom:6px;"><Postcard /></el-icon>
暂无公告
</div>
<div v-else>
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': item.isRead }" @click="previewNotice(item)">
<el-tag size="mini" :type="item.noticeType === '1' ? 'warning' : 'success'" class="notice-tag">
<el-tag size="small" :type="item.noticeType === '1' ? 'warning' : 'success'" class="notice-tag">
{{ item.noticeType === '1' ? '通知' : '公告' }}
</el-tag>
<span class="notice-item-title">{{ item.noticeTitle }}</span>
<span class="notice-item-date">{{ item.createTime }}</span>
</div>
</div>
<!-- 触发器 -->
<template #reference>
<div class="right-menu-item hover-effect notice-trigger" @mouseenter="onNoticeEnter" @mouseleave="onNoticeLeave">
<svg-icon icon-class="bell" />
<span v-if="unreadCount > 0" class="notice-badge">{{ unreadCount }}</span>
</div>
</template>
</el-popover>
<div v-popover:noticePopover class="right-menu-item hover-effect notice-trigger" @mouseenter="onNoticeEnter" @mouseleave="onNoticeLeave">
<svg-icon icon-class="bell" />
<span v-if="unreadCount > 0" class="notice-badge">{{ unreadCount }}</span>
</div>
<!-- 预览弹窗 -->
<notice-detail-view ref="noticeViewRef" />
</div>
</template>
<script>
<script setup>
import NoticeDetailView from './DetailView'
import { listNoticeTop, markNoticeRead, markNoticeReadAll } from '@/api/system/notice'
export default {
name: 'HeaderNotice',
components: { NoticeDetailView },
data() {
return {
noticeList: [], // 通知列表
unreadCount: 0, // 未读数量
noticeLoading: false, // 加载状态
noticeVisible: false, // 弹出层显示状态
noticeLeaveTimer: null // 鼠标离开计时器
}
},
mounted() {
this.loadNoticeTop()
},
methods: {
// 鼠标移入铃铛区域
onNoticeEnter() {
clearTimeout(this.noticeLeaveTimer)
this.noticeVisible = true
this.$nextTick(() => {
const popper = this.$refs.noticePopover.$refs.popper
if (popper && !popper._noticeBound) {
popper._noticeBound = true
popper.addEventListener('mouseenter', () => clearTimeout(this.noticeLeaveTimer))
popper.addEventListener('mouseleave', () => {
this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 100)
})
}
const noticePopover = ref(null)
const noticeList = ref([])
const unreadCount = ref(0)
const noticeLoading = ref(false)
const noticeVisible = ref(false)
const noticeLeaveTimer = ref(null)
const { proxy } = getCurrentInstance()
// 加载顶部公告列表
function loadNoticeTop() {
noticeLoading.value = true
listNoticeTop().then(res => {
noticeList.value = res.data || []
unreadCount.value = res.unreadCount !== undefined ? res.unreadCount : noticeList.value.filter(n => !n.isRead).length
}).finally(() => {
noticeLoading.value = false
})
}
onMounted(() => loadNoticeTop())
// 鼠标移入铃铛区域
function onNoticeEnter() {
clearTimeout(noticeLeaveTimer.value)
noticeVisible.value = true
nextTick(() => {
const popper = noticePopover.value?.popperRef?.contentRef
if (popper && !popper._noticeBound) {
popper._noticeBound = true
popper.addEventListener('mouseenter', () => clearTimeout(noticeLeaveTimer.value))
popper.addEventListener('mouseleave', () => {
noticeLeaveTimer.value = setTimeout(() => { noticeVisible.value = false }, 100)
})
},
// 鼠标离开铃铛区域
onNoticeLeave() {
this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 150)
},
// 加载顶部公告列表
loadNoticeTop() {
this.noticeLoading = true
listNoticeTop().then(res => {
this.noticeList = res.data || []
this.unreadCount = res.unreadCount !== undefined ? res.unreadCount : this.noticeList.filter(n => !n.isRead).length
}).finally(() => {
this.noticeLoading = false
})
},
// 预览公告详情
previewNotice(item) {
if (!item.isRead) {
markNoticeRead(item.noticeId).catch(() => {})
item.isRead = true
const idx = this.noticeList.indexOf(item)
if (idx !== -1) this.$set(this.noticeList, idx, { ...item, isRead: true })
this.unreadCount = Math.max(0, this.unreadCount - 1)
}
this.$refs.noticeViewRef.open(item.noticeId)
},
// 全部已读
markAllRead() {
const ids = this.noticeList.map(n => n.noticeId).join(',')
if (!ids) return
markNoticeReadAll(ids).catch(() => {})
this.noticeList = this.noticeList.map(n => ({ ...n, isRead: true }))
this.unreadCount = 0
}
})
}
// 鼠标离开铃铛区域
function onNoticeLeave() {
noticeLeaveTimer.value = setTimeout(() => { noticeVisible.value = false }, 150)
}
// 预览公告详情
function previewNotice(item) {
if (!item.isRead) {
markNoticeRead(item.noticeId).catch(() => {})
const idx = noticeList.value.indexOf(item)
if (idx !== -1) noticeList.value[idx] = { ...item, isRead: true }
unreadCount.value = Math.max(0, unreadCount.value - 1)
}
proxy.$refs["noticeViewRef"].open(item.noticeId)
}
// 全部已读
function markAllRead() {
const ids = noticeList.value.map(n => n.noticeId).join(',')
if (!ids) return
markNoticeReadAll(ids).catch(() => {})
noticeList.value = noticeList.value.map(n => ({ ...n, isRead: true }))
unreadCount.value = 0
}
</script>
@@ -121,9 +126,7 @@ export default {
pointer-events: none;
}
}
.notice-popover {
padding: 0 !important;
}
.notice-popover { padding: 0 !important; }
.notice-popover .notice-header {
display: flex;
align-items: center;
@@ -137,7 +140,7 @@ export default {
}
.notice-popover .notice-mark-all {
font-size: 12px;
color: #409EFF;
color: var(--el-color-primary);
font-weight: normal;
cursor: pointer;
}