@huatu-lcy/system-notification
v0.1.1
Published
Reusable Vue 2 SSE system notification component.
Downloads
20
Readme
SystemNotification
SystemNotification 是一个基于 SSE EventSource 的系统通知接入组件。组件本身不渲染可见入口,挂载后自动建立 SSE 连接,收到业务消息后通过 notify 或 Vue 实例上的 $notify 展示当前缓存的所有消息。
该目录已经把组件、SSE 连接工具、消息格式化工具集中导出,方便后续作为 npm 组件发布给其他系统复用。
npm 包构建
组件目录已经包含独立 npm 包配置,包名为 @huatu-lcy/system-notification。发布前如需使用公司 npm scope 或私有 registry,可先修改 package.json 中的 name、version、license 等字段。
# 从仓库根目录构建组件库产物
pnpm run build:system-notification
# 从仓库根目录生成本地 npm tarball
pnpm run pack:system-notification
# 确认 npm 登录态、registry、包名和版本后再发布
pnpm --dir src/common/components/SystemNotification publish --registry <npm-registry-url>构建产物会生成到 src/common/components/SystemNotification/dist,包含 ESM、CJS、UMD 和样式文件。接入项目应自行安装 vue@2;如果使用默认 $notify,还需要安装并注册 element-ui@2。它们作为 peer dependency 不会被打包进组件库。
完整发布和系统接入流程见 NPM_PUBLISH_AND_USAGE.md。
基本用法
npm 包使用
pnpm add @huatu-lcy/system-notificationimport Vue from 'vue'
import ElementUI from 'element-ui'
import SystemNotification from '@huatu-lcy/system-notification'
import '@huatu-lcy/system-notification/style.css'
Vue.use(ElementUI)
Vue.use(SystemNotification)<template>
<system-notification
user-id="10001"
sse-url="https://notice.example.com/stream/connect"
token="access-token"
group="crm"
:event-names="['group-message', 'private-message', 'broadcast-message']"
/>
</template>项目内直接使用
<template>
<system-notification
:user-id="userInfo.staffNo"
sse-url="/api/sse/connect"
token="access-token"
:event-names="['group-message', 'private-message', 'broadcast-message']"
/>
</template>
<script>
import SystemNotification from '@/common/components/SystemNotification/index.js'
export default {
components: {
SystemNotification
},
computed: {
userInfo() {
return this.$store.state.userInfo || {}
}
}
}
</script>作为插件注册
import Vue from 'vue'
import ElementUI from 'element-ui'
import SystemNotification from '@/common/components/SystemNotification/index.js'
Vue.use(ElementUI)
Vue.use(SystemNotification)<template>
<system-notification
user-id="10001"
sse-url="/api/sse/connect"
token="access-token"
group="crm"
:event-names="['group-message', 'private-message', 'broadcast-message']"
/>
</template>自定义连接地址和必要参数
<template>
<system-notification
sse-url="https://notice.example.com/stream/connect"
token-key="accessToken"
user-id-key="staffNo"
group-key="tenant"
token="access-token"
user-id="10001"
group="tenant-a"
:event-names="['tenant-message', 'private-message']"
:connection-params="{
appId: 'crm',
locale: 'zh-CN'
}"
:required-params="['userId', 'appId']"
/>
</template>上面的配置会连接到:
https://notice.example.com/stream/connect?accessToken=access-token&staffNo=10001&tenant=tenant-a&appId=crm&locale=zh-CN自定义监听事件
<template>
<system-notification
user-id="10001"
sse-url="https://notice.example.com/stream/connect"
token="access-token"
:event-names="['tenant-message', 'private-message']"
connected-event-name="ready"
heartbeat-event-name="ping"
:event-handlers="eventHandlers"
@notification="handleNotification"
@error="handleError"
/>
</template>
<script>
export default {
data() {
return {
eventHandlers: {
'custom-event': this.handleCustomEvent
}
}
},
methods: {
handleNotification(notification, messages, event) {
console.log(notification, messages, event)
},
handleCustomEvent(payload, event) {
console.log(payload, event)
},
handleError(error) {
console.error(error)
}
}
}
</script>自定义通知展示
<template>
<system-notification
user-id="10001"
sse-url="/api/sse/connect"
token="access-token"
:event-names="['group-message', 'private-message', 'broadcast-message']"
:notify-options="{
title: '待办提醒',
position: 'bottom-right',
duration: 5000
}"
:notification-formatter="formatNotification"
/>
</template>
<script>
export default {
methods: {
formatNotification(messages, defaultOptions) {
return {
...defaultOptions,
message: messages.map(item => item.content).join('<br />')
}
}
}
}
</script>使用插槽自定义通知内容
当调用方需要完全控制 $notify 内部展示时,可以使用 scoped slot。传入插槽后组件会把 $notify 的 message 改为 VNode,并关闭 dangerouslyUseHTMLString。
<template>
<system-notification
user-id="10001"
sse-url="/api/sse/connect"
token="access-token"
:event-names="['group-message', 'private-message', 'broadcast-message']"
title="待办提醒"
>
<template #content="{ messages, latestMessage, close }">
<div class="todo-notice">
<div class="todo-notice__header">
<span>最新消息:{{ latestMessage && latestMessage.content }}</span>
<button type="button" @click="close">关闭</button>
</div>
<div
v-for="message in messages"
:key="message.id || message.sendTime"
class="todo-notice__item"
>
<strong>{{ message.type }}</strong>
<p>{{ message.content }}</p>
</div>
</div>
</template>
</system-notification>
</template>只想替换单条消息的展示时,使用 item 插槽:
<template>
<system-notification
user-id="10001"
sse-url="/api/sse/connect"
token="access-token"
:event-names="['group-message', 'private-message', 'broadcast-message']"
>
<template #item="{ message, index }">
<div class="custom-notice-item">
<span>{{ index + 1 }}.</span>
<span>{{ message.fromUserId }}:{{ message.content }}</span>
</div>
</template>
<template #empty>
<span>暂无待处理消息</span>
</template>
</system-notification>
</template>如果接入项目不使用 Element UI,可以通过 notify 注入自己的通知函数:
<template>
<system-notification
user-id="10001"
sse-url="/api/sse/connect"
token="access-token"
:event-names="['group-message', 'private-message', 'broadcast-message']"
:notify="notify"
/>
</template>
<script>
export default {
methods: {
notify(options) {
const close = window.CustomNotice.open(options)
return {
close
}
}
}
}
</script>完整配置示例(所有 props)
下面示例刻意展开所有 props,实际接入时可以删除不需要覆盖默认值的配置。若要使用 slot 自定义 $notify 内容,notificationFormatter 请保持 null;如果传入 formatter,它会优先接管通知内容。
<template>
<system-notification
:user-id="notificationProps.userId"
:token="notificationProps.token"
:group="notificationProps.group"
:sse-url="notificationProps.sseUrl"
:token-key="notificationProps.tokenKey"
:user-id-key="notificationProps.userIdKey"
:group-key="notificationProps.groupKey"
:connection-params="notificationProps.connectionParams"
:required-params="notificationProps.requiredParams"
:event-source-options="notificationProps.eventSourceOptions"
:event-names="notificationProps.eventNames"
:connected-event-name="notificationProps.connectedEventName"
:heartbeat-event-name="notificationProps.heartbeatEventName"
:event-handlers="notificationProps.eventHandlers"
:title="notificationProps.title"
:max-messages="notificationProps.maxMessages"
:disabled="notificationProps.disabled"
:notify="showSystemNotify"
:notify-options="notificationProps.notifyOptions"
:notification-formatter="notificationProps.notificationFormatter"
:normalizer="normalizeNotificationPayload"
:sse-factory="createSseClient"
:on-open="handleOpen"
:on-connected="handleConnected"
:on-heartbeat="handleHeartbeat"
:on-message="handleMessage"
:on-notification="handleNotification"
:on-error="handleError"
:on-notify="handleNotify"
>
<template #content="{ messages, latestMessage, connected, close }">
<div class="custom-system-notification">
<div class="custom-system-notification__header">
<span>{{ connected ? '已连接' : '未连接' }}</span>
<strong>{{ latestMessage ? latestMessage.content : '暂无系统通知' }}</strong>
<button type="button" @click="close">关闭</button>
</div>
<div
v-for="message in messages"
:key="message.id || message.sendTime || message.timestamp"
class="custom-system-notification__item"
>
<div>{{ message.type || message.code || '系统消息' }}</div>
<div>{{ message.fromUserId || '--' }} -> {{ message.toUserId || '全部用户' }}</div>
<p>{{ message.content || message.message || '--' }}</p>
</div>
</div>
</template>
</system-notification>
</template>
<script>
import SystemNotification, {
createNotificationSse,
normalizeNotification
} from '@/common/components/SystemNotification/index.js'
export default {
components: {
SystemNotification
},
data() {
return {
notificationProps: {
userId: '10001',
token: 'access-token',
group: 'tenant-a',
sseUrl: 'https://notice.example.com/stream/connect',
tokenKey: 'accessToken',
userIdKey: 'staffNo',
groupKey: 'tenant',
connectionParams: {
appId: 'crm',
tenantId: 'tenant-a',
locale: 'zh-CN'
},
requiredParams: ['userId', 'appId', 'tenantId'],
eventSourceOptions: {
withCredentials: true
},
eventNames: ['tenant-message', 'private-message', 'broadcast-message'],
connectedEventName: 'ready',
heartbeatEventName: 'ping',
eventHandlers: {},
title: '系统通知',
maxMessages: 30,
disabled: false,
notifyOptions: {
position: 'bottom-right',
duration: 5000,
showClose: true,
customClass: 'system-notification-notify'
},
notificationFormatter: null
}
}
},
created() {
this.notificationProps.eventHandlers = {
'custom-event': this.handleCustomEvent
}
},
methods: {
showSystemNotify(options) {
return this.$notify(options)
},
normalizeNotificationPayload(payload) {
return normalizeNotification(payload)
},
createSseClient(options) {
return createNotificationSse(options)
},
handleOpen(payload, event) {
console.log('open', payload, event)
},
handleConnected(payload, event) {
console.log('connected', payload, event)
},
handleHeartbeat(payload, event) {
console.log('heartbeat', payload, event)
},
handleMessage(payload, event) {
console.log('raw message', payload, event)
},
handleNotification(notification, messages, event) {
console.log('notification', notification, messages, event)
},
handleError(error) {
console.error(error)
},
handleNotify({ messages, options, instance }) {
console.log('notify shown', messages, options, instance)
},
handleCustomEvent(payload, event) {
console.log('custom event', payload, event)
}
}
}
</script>Attributes
| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| userId | String \| Number | '' | 当前接收通知的用户标识,默认作为 userId query 参数发送。 |
| token | String | 必传 | SSE 服务鉴权 token,默认作为 token query 参数发送。公共组件不内置默认 token。 |
| group | String | '' | 分组或租户标识,默认作为 group query 参数发送。需要分组时由调用方传入。 |
| sseUrl | String | 必传 | SSE 完整连接地址,例如 /api/sse/connect 或 https://notice.example.com/stream/connect。公共组件不再拼接 origin/path。 |
| tokenKey | String | 'token' | token 在 query 中的参数名。传空字符串可不发送 token query,但 token prop 仍是必传字段。 |
| userIdKey | String | 'userId' | userId 在 query 中的参数名。传空字符串可不发送 userId。 |
| groupKey | String | 'group' | group 在 query 中的参数名。传空字符串可不发送 group。 |
| connectionParams | Object | {} | 额外 query 参数,例如 appId、tenantId、locale。同名字段会覆盖默认 query 参数。 |
| requiredParams | Array<String> | ['userId'] | 建立连接前必须存在的额外参数名。sseUrl 和 token 固定必填;该配置支持 userId、group,以及 connectionParams 中的字段。 |
| eventSourceOptions | Object | {} | 传给 new EventSource(url, options) 的配置。默认 { withCredentials: false },可覆盖为 { withCredentials: true }。 |
| eventNames | Array<String> | 必传 | 业务通知事件名列表,这些事件会触发通知入队和展示。公共组件不内置业务事件名;未监听到的命名事件不会进入通知列表。 |
| connectedEventName | String | 'connected' | 连接成功事件名。传空字符串可关闭该事件监听。 |
| heartbeatEventName | String | 'heartbeat' | 心跳事件名。传空字符串可关闭该事件监听。 |
| eventHandlers | Object | {} | 额外事件处理器,格式为 { eventName: handler }。handler 参数为 (payload, event)。 |
| title | String | '系统通知' | 默认 $notify 标题。 |
| maxMessages | Number | 20 | 本地缓存并展示的最大消息数量。 |
| disabled | Boolean | false | 是否禁用连接。切为 true 时会关闭 SSE 连接和当前通知实例。 |
| notify | Function | null | 自定义通知函数。未传时使用组件实例上的 $notify。函数参数为通知 options,返回值可包含 close()。 |
| notifyOptions | Object | {} | 合并进默认 $notify options 的配置。 |
| notificationFormatter | Function | null | 自定义通知内容格式化函数,参数为 (messages, defaultOptions)。可返回字符串或完整 notify options。 |
| normalizer | Function | normalizeNotification | 消息标准化函数,参数为原始 payload,返回标准消息对象。返回空值时忽略该消息。 |
| sseFactory | Function | createNotificationSse | 自定义 SSE 客户端工厂,适合单测或替换通信实现。 |
| onOpen | Function | null | open 回调,参数为 (payload, event)。 |
| onConnected | Function | null | connected 回调,参数为 (payload, event)。 |
| onHeartbeat | Function | null | heartbeat 回调,参数为 (payload, event)。 |
| onMessage | Function | null | 原始 onmessage 回调,参数为 (payload, event)。 |
| onNotification | Function | null | 标准化后的业务通知回调,参数为 (notification, messages, event)。 |
| onError | Function | null | 错误回调,参数为 (error)。 |
| onNotify | Function | null | 通知展示后的回调,参数为 ({ messages, options, instance })。 |
Events
组件会同时支持 Vue 事件和 onXxx 函数式回调。Vue 事件适合模板中监听,onXxx 更适合 npm 组件在不同项目中做配置化接入。
| 事件名 | 参数 | 说明 |
| --- | --- | --- |
| open | (payload, event) | SSE 连接打开时触发。 |
| connected | (payload, event) | 收到 connectedEventName 对应事件时触发。 |
| heartbeat | (payload, event) | 收到 heartbeatEventName 对应事件时触发。 |
| message | (notification, messages) | 收到业务消息并完成标准化后触发。 |
| notification | (notification, messages, event) | 收到业务消息并完成标准化后触发,比 message 多透出原始 SSE event。 |
| error | (error) | SSE 连接报错或浏览器不支持 EventSource 时触发。 |
| notify | ({ messages, options }) | 调用通知函数后触发。 |
Slots
组件本体仍是无界面的连接型组件,slot 不会直接渲染在页面中,只会作为 $notify 的消息内容渲染。
| 插槽名 | 参数 | 说明 |
| --- | --- | --- |
| content | { messages, latestMessage, title, connected, close, options } | 自定义整块 $notify 内容。存在该插槽时优先使用它,不再渲染默认列表和 item 插槽。 |
| item | { message, index, messages, latestMessage, title, connected, close, options } | 自定义每条消息内容,外层列表、空状态和单条消息容器仍由组件提供。 |
| empty | { messages, latestMessage, title, connected, close, options } | 自定义空消息展示。 |
插槽参数说明:
| 参数 | 类型 | 说明 |
| --- | --- | --- |
| messages | Array | 当前缓存并展示的所有业务消息,最新消息在数组第一项。 |
| latestMessage | Object \| null | 最新一条业务消息。 |
| message | Object | item 插槽当前渲染的单条消息。 |
| index | Number | item 插槽当前消息下标。 |
| title | String | 当前通知标题。 |
| connected | Boolean | 当前连接状态。 |
| close | Function | 关闭当前通知实例。 |
| options | Object | 本次 $notify 的默认 options。 |
notificationFormatter 的优先级高于 slot。若同时传入 notificationFormatter 和 slot,会使用 notificationFormatter 返回的通知内容。
导出项
import SystemNotification, {
install,
createNotificationSse,
normalizeNotification,
parseSseData,
buildSystemNotifyOptions,
buildSystemNotificationHtml,
prependNotification,
isSystemSignalNotification
} from '@/common/components/SystemNotification/index.js'| 导出项 | 说明 |
| --- | --- |
| SystemNotification | 默认导出的 Vue 组件。 |
| install | Vue 插件安装函数,内部注册 SystemNotification 组件。 |
| createNotificationSse | 创建 SSE 通知连接。 |
| normalizeNotification | 将后端通知 envelope 标准化为统一消息对象。 |
| parseSseData | 解析 SSE event 的 data 字段,优先按 JSON 解析。 |
| buildSystemNotifyOptions | 根据消息列表生成 $notify options。 |
| buildSystemNotificationHtml | 根据消息列表生成默认 HTML 内容。 |
| prependNotification | 将新通知加入消息列表并按最大数量截断。 |
| isSystemSignalNotification | 判断是否为连接或心跳类系统信号。 |
默认消息格式
normalizer 默认会输出以下结构,业务系统可以通过自定义 normalizer 适配自己的后端格式:
{
id: 'message-id',
type: 'PRIVATE',
fromUserId: 'admin',
toUserId: '10001',
content: '通知内容',
sendTime: '2026-07-14 10:00:00',
code: 'PRIVATE_MESSAGE',
message: '',
timestamp: ''
}CONNECTED 和 HEARTBEAT 这类连接状态消息不会进入通知列表。
注意事项
- 组件默认依赖当前 Vue 实例存在
$notify。如果接入项目没有 Element UI,请传入notify。 - 组件不内置 SSE 地址、token、group、连接 path 或业务事件名。
sseUrl需要传完整连接地址,token和eventNames必须由调用方传入。 - 默认通知使用
dangerouslyUseHTMLString: true,组件内部会转义消息内容。自定义notificationFormatter时需要自行保证内容安全。 - 默认
duration为0,通知不会自动关闭。可以通过notifyOptions.duration覆盖。 connectionParams会覆盖同名 query 参数,请谨慎处理token、userId、group等字段。
