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
+10 -4
View File
@@ -86,11 +86,17 @@ export function sseStart() {
},
onmessage(msg) {
// msg.event: 'connected' | 'new_message' | 其它
// msg.data: 服务端 .data() 写入的字符串
// msg.data: 服务端 .data() 写入的字符串, 格式 {type:'new_message', unread:N, silent:bool}
// silent 由后端业务语义决定: false=业务事件触发 (manager 审核/分配/...), true=用户主动操作 (markRead)
// 前端按 flag 统一决策弹/不弹, 避免每个业务页面重复判断
if (msg.event === 'new_message') {
let unread = 0
try { unread = JSON.parse(msg.data).unread ?? 0 } catch {}
emit('new_message', { unread })
let unread = 0, silent = false
try {
const parsed = JSON.parse(msg.data)
unread = parsed.unread ?? 0
silent = !!parsed.silent
} catch {}
emit('new_message', { unread, silent })
} else if (msg.event === 'connected') {
emit('connected', {})
}