feat(msg): SSE 实时通知体系 (#1-#6) + dict 接口权限分层

- BizNotifyService 门面: 5 个语义方法 (expertAuditResult / planAuditResult /
  meetingInvitation / agreementAwaitingSign / projectAssignedToExecutor)
- #3 项目分配执行方: 跨调用对比旧 (sessions, amount) 差集去重
- #5 会议邀请: BizMeetingController.add 全量通知, edit 走 userIds 差集去重
- #6 协议待签: updateLaborProtocol 仅在旧 URL 空 → 新 URL 非空时推
- 前端: AdminLayout 全局 bell 角标 + 全局事件总线, doctor/Home 实时刷新,
  Messages 全部已读, Login.vue 角色统一跳首页, Toast 弹窗上移到 layout
- dict 权限分层 (读开放 / 写 admin-only): BizDictController + SysDictDataController
  避免 dropdown 403, 但写操作仍需 admin 防止误删
This commit is contained in:
郭庆泰
2026-08-22 15:53:01 +08:00
parent b6ac5b7d41
commit 86e85d02cb
11 changed files with 321 additions and 44 deletions
+15 -3
View File
@@ -68,9 +68,10 @@ import { computed, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
import { logout as logoutApi } from '@/api/auth'
import { sseStart, sseStop, on as sseOn } from '@/utils/sseClient'
import { sseStart, sseStop, onGlobal } from '@/utils/sseClient'
import { listMyMessages } from '@/api/public'
import { getMyExpertProfile } from '@/api/business/expert'
import { ElNotification } from 'element-plus'
import { House, Document, Calendar, User, List, OfficeBuilding, Setting, Bell, EditPen, DataAnalysis, Tickets, CaretBottom, Folder, Medal, UserFilled, Connection, Box, Star, Grid, Files, Compass, Collection } from '@element-plus/icons-vue'
const route = useRoute()
@@ -124,9 +125,20 @@ onMounted(() => {
sseStart()
loadInitialUnread()
loadExpertAuditStatus()
// SSE 推送触发 navbar 角标实时更新 + 医生审核状态刷新 (审核通过事件 = user 立刻看到完整菜单/首页)
unsubscribeNewMessage = sseOn('new_message', ({ unread }) => {
// SSE 推送触发 navbar 角标实时更新 + 全局 ElNotification 弹窗 + 医生审核状态刷新
// (审核通过事件 = user 立刻看到完整菜单/首页; 业务事件 = 弹右上角通知)
// 弹/不弹由后端 silent flag 决定: false=业务事件触发 (insert) → 弹; true=用户主动操作 (markRead/markAllRead) → 静默
unsubscribeNewMessage = onGlobal(({ unread, silent }) => {
unreadCount.value = unread
if (!silent) {
ElNotification({
title: '您有新的通知',
message: unread > 1 ? `${unread} 条未读` : '点击右上角铃铛查看详情',
type: 'success',
duration: 5000,
position: 'top-right'
})
}
if (store.role === 'doctor') loadExpertAuditStatus()
})
// 路由切换时重拉一次 (用户从消息页点列表已读后回首页, 角标要同步减; 医生进入新页面也刷新 audit 状态)