@bams-app/message-voice
消息通知语音播报通用模块(WebSocket 实时推送 + Web Speech API 语音合成 + 消息面板)
3,070
| Install | |
|---|---|
npm install @bams-app/message-voice |
|
| Latest Version: | 0.2.1 |
| Vue: | 3 |
| Last Updated: | Oct 10, 2026 |
| Links: | npm |
Maintainer: liuzewei1990
@bams-app/message-voice
消息通知语音播报通用模块(Vue3 / 框架无关)。
由 app-cockpit 大屏主页(qhome.vue)的「消息通知 + 语音告警播报」功能封装而来,包含:
- WebSocket 实时推送(VOICE / POPUP 双通道,自动重连 + 心跳)
- 语音播报(基于浏览器原生 Web Speech API 的排队播报,支持 once / twice / loop)
- 消息面板组件(右下角悬浮面板,展示消息通知 / 告警通知)
特性
- 框架无关的
createNotifier实例 + Vue3 组合式useNotifier两种使用方式 speak语音队列独立封装为createSpeaker,确认逻辑可配置注入(不再硬编码window.mainRequest)- WebSocket 地址支持
/ws、ws://、wss://、http(s)://自动转换;连接状态变化自动重连 + 心跳 - 列表合并去重:保留历史推送,最新消息插入顶部,最多保留最新 20 条
- 共享单例
useSharedNotifier:多个组件/页面可同时挂载,共用一条 WebSocket 连接与同一份消息数据(引用计数,最后一个组件卸载才断开) - 消息面板(
MessagePanel):- 双 Tab(消息通知 / 告警通知),序号倒序,未读高亮
- 收起为小圆图标(SVG 信封 + 消息数角标 + 新消息抖动提醒)
- 面板头部支持鼠标 + 触摸拖拽移动(复用
useDraggable) Teleport挂载到 body,不受父级 stacking context / z-index 遮挡- 样式接入 UI 规范(
var(--bams-*)语义化主题变量)
useDraggable可复用拖拽 composable,任意 fixed 元素可直接复用- 组件卸载自动清理连接与语音队列
安装
yarn add @bams-app/message-voice
快速开始
1. 组合式 API(推荐,Vue3)
<template>
<div class="home">
<MessagePanel :play-list="playList" :message-list="messageList" @close="handleClose" />
</div>
</template>
<script setup>
import { onMounted } from "vue";
import { useNotifier, MessagePanel } from "@bams-app/message-voice";
const { playList, messageList, start, destroy } = useNotifier({
url: "/ws", // WebSocket 地址,默认 "/ws"(自动补全为当前 host 的 ws 地址)
accountId: userInfo.accountId,
voiceField: "VOICE", // 语音播报通道,默认 "VOICE"
popupField: "POPUP", // 消息通知通道,默认 "POPUP"
confirmNotify: async item => {
// 语音播报完成后的确认回调,返回 true 表示确认成功并从播报队列移除
const res = await request.post("/admin-api/pub/notifyMessage/confirmNotifyMessage", {
id: item.id,
redisKey: "VOICE",
});
return String(res?.code) === "200";
},
});
onMounted(() => {
start();
});
</script>
1.5 多页面共享连接(useSharedNotifier)
当消息面板需要在多个页面/组件中同时挂载(如每个页面各渲染一个面板)时,使用 useSharedNotifier 替代 useNotifier:所有实例共用同一条 WebSocket 连接、同一份消息列表与语音队列,避免重复连接、重复播报。第一个实例挂载时建立连接,最后一个实例卸载时才真正断开。
<script setup>
import { useSharedNotifier, MessagePanel } from "@bams-app/message-voice";
// 多个组件可同时调用,连接与数据全局共享一份
const { playList, messageList, start, stop } = useSharedNotifier({
url: "/ws",
accountId, // 可以是 Ref / getter
confirmNotify: async item => {
/* ... */
},
});
</script>
注意:
start()幂等(只建立一次连接);配置以首个实例的 options 为准,多页面建议保持统一;不暴露destroy,生命周期由内部引用计数自动管理。
2. 类实例(框架无关)
import { createNotifier } from "@bams-app/message-voice";
const notifier = createNotifier({
url: "wss://example.com/ws",
accountId: "123456",
confirmNotify: async item => {
/* ... */
},
});
notifier.onChange(state => {
console.log("语音告警:", state.playList);
console.log("消息通知:", state.messageList);
});
notifier.start();
// ... 页面销毁时
notifier.destroy();
3. 仅使用语音播报
import { createSpeaker } from "@bams-app/message-voice";
const speaker = createSpeaker({
confirmNotify: async item => {
/* ... */
},
});
speaker.speak([
{ id: "1", text: "一号窑设备温度过高,请及时处理", playCount: 1 },
{ id: "2", text: "二号线有新的待办消息", playType: "twice" },
{ id: "3", text: "循环告警", playType: "loop", timestamp: Date.now() + 60000 },
]);
speaker.reset(); // 清空队列
4. 全局接入(封装组件,以 touch-startr 为例)
将消息面板 + 连接管理封装为独立组件,挂在根组件 App.vue 即可全系统生效:
<!-- MessageVoice.vue -->
<template>
<MessagePanel :play-list="playList" :message-list="messageList" />
</template>
<script setup>
import { watch } from "vue";
import { useNotifier, MessagePanel } from "@bams-app/message-voice";
import { useUserStore } from "@/stores/user";
const userStore = useUserStore();
const { playList, messageList, start, stop } = useNotifier({
url: "/ws",
accountId: userStore.accountId, // 传 Ref,响应式
confirmNotify: async item => {
const res = await workAxios.post("/admin-api/pub/notifyMessage/confirmNotifyMessage", {
id: item.id,
redisKey: "VOICE",
});
const data = res.data;
return !data || data.code === undefined || data.code === null ? true : String(data.code).trim() === "200";
},
});
// 登录后启动 / 登出停止,再登录可重新启动
watch(
() => userStore.accountId,
id => (id ? start() : stop())
);
</script>
<!-- App.vue -->
<template>
<MessageVoice />
</template>
API 说明
createNotifier(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| url | string | Ref | Func | "/ws" | WebSocket 地址,支持 /ws、ws://、wss://、http(s)://;传 Ref/函数时动态取值 |
| accountId | string | Ref | Func | - | 用户ID(订阅 keys,必传),支持 Ref/函数动态取值 |
| voiceField | string | "VOICE" | 语音播报通道 field |
| popupField | string | "POPUP" | 消息通知通道 field |
| confirmNotify | Function | null | 播报完成确认回调 async(item) => boolean,返回 false 时消息保留队列继续重播 |
返回实例:
| 方法/属性 | 说明 |
|---|---|
start() |
建立 VOICE / POPUP 两条 WebSocket 连接 |
stop() |
关闭连接并清空语音队列,保留状态监听,可再次 start() |
destroy() |
关闭连接并清理状态监听(不可再次 start) |
onChange(cb) |
订阅状态变化,返回取消订阅函数 |
playList |
语音告警列表(getter,最多 20 条) |
messageList |
消息通知列表(getter,最多 20 条) |
speaker |
内部 speaker 实例 |
useNotifier(options)
Vue3 组合式封装,参数同 createNotifier,返回:
| 返回值 | 类型 | 说明 |
|---|---|---|
notifier |
Object | 底层 createNotifier 实例 |
playList |
Ref | 语音告警列表(响应式,最多 20 条) |
messageList |
Ref | 消息通知列表(响应式,最多 20 条) |
start |
Func | 建立连接 |
stop |
Func | 关闭连接,保留监听,可再次 start |
destroy |
Func | 关闭连接(卸载时自动调用) |
MessagePanel
| Props | 类型 | 默认值 | 说明 |
|---|---|---|---|
| playList | Array | [] | 语音告警列表 |
| messageList | Array | [] | 消息通知列表 |
| Events | 说明 |
|---|---|
| close | 面板被关闭时触发 |
面板行为:
- 新消息到达时自动弹出(收起状态下小圆图标抖动提醒)
- 点 × 关闭后面板隐藏,下一批新消息到达时自动重新弹出
- 收起按钮将面板收为右下角小圆图标(
66px,SVG 信封 + 消息总数角标) - 面板头部支持鼠标 / 触摸拖拽移动,位置记忆(收起图标保持默认右下角,不可拖拽)
- 挂载于
document.body(<Teleport>),z-index: 9999,不受父级元素遮挡
useDraggable(options)
让 fixed 定位元素支持鼠标 + 触摸拖拽的可复用 composable。
<template>
<div ref="elementRef" :style="style" @mousedown="startDrag" @touchstart="startDrag">…</div>
</template>
<script setup>
import { useDraggable } from "@bams-app/message-voice";
const { elementRef, style, startDrag } = useDraggable({ defaultGap: 24 });
</script>
| 返回值 | 说明 |
|---|---|
elementRef |
绑定到可拖拽元素根节点 |
style |
拖拽后的 left/top 定位样式(未拖拽时为默认 right/bottom) |
startDrag |
拖拽起始处理函数,绑定 @mousedown / @touchstart |
pos |
当前拖拽位置 {x, y}(null 表示未拖拽) |
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
defaultGap |
number | 24 | 未拖拽时距视口右/下边缘间距 |
行为:触摸位移超过 5px 才判定为拖拽(不误吞点击);拖拽中自动阻止页面滚动;位置钳制在视口内;触摸点落在 button 上时自动跳过;组件卸载自动清理监听。
createSpeaker(options)
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| confirmNotify | Function | null | 播报完成确认回调,null 时播完即移除 |
返回 { speak(list), reset() }。
消息数据格式
服务端 WebSocket 推送消息结构(与 app-cockpit 一致):
{
"valMap": {
"123456": [{ "id": "1", "content": "一号窑设备温度过高", "playCount": 1 }]
}
}
- VOICE 通道:取
valMap[accountId]后逐个语音播报,同时更新playList - POPUP 通道:取
valMap[accountId]后更新messageList,供面板展示 - 两个列表均保留历史、最新插顶、按 id 去重,最多保留最新 20 条(常量
MAX_LIST_ITEMS)
注意事项
- 语音播报依赖浏览器原生
window.speechSynthesis,仅支持在支持 Web Speech API 的浏览器中使用 @bams-app/websocket的消息分发依赖服务端回传的 uid 字段定位组件,与本模块服务端的{ valMap: { [id]: [...] } }格式不兼容,故本模块内置了轻量 WebSocket 封装- 同一页面请勿创建多个 notifier 实例(语音队列共享浏览器语音引擎,会互相抢占)
- 面板通过
<Teleport to="body">挂载,z-index: 9999;若宿主页面有更高 z-index 的自定义全屏遮罩,需自行评估层级 - 本地联调可运行
mock-server(WS 9090 / HTTP 9091,自动推送与手动POST /push),详见mock-server/README.md
Related Packages
Version History
| Version | Released | License |
|---|---|---|
| 0.2.1 | ||
| 0.2.0 | ||
| 0.1.3 | ||
| 0.1.2 | ||
| 0.1.0 | ||
| 0.0.8 | ||
| 0.0.7 | ||
| 0.0.2 | ||
| 0.0.1 |
Pre-releases (1)
| Version | Released | License |
|---|---|---|
| 0.2.0-beta.1 |