@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

@bams-app/websocket

websocket封装 - Vue3版本

3,053

Version History

Version Released License
0.2.1
0.2.0
0.1.3
0.1.2
0.1.0
Pre-releases (1)
Version Released License
0.2.0-beta.1