结论:文字、语音、表情在uni-app中必须按端拆解实现,消息体需结构化为类型数组,禁止拼接字符串;H5语音播报须过滤emoji,小程序需管理音频实例生命周期,App端需手动维护光标位置,duration必须由SDK或后端提供。

直接说结论:文字、语音、表情三者在 uni-app 中不能靠单一方案统一处理,必须按端拆解——App 端用 plus.speech + 自定义表情组件,微信小程序用 wx.createInnerAudioContext + wx-plugin-speech 插件 + 表情键盘,H5 用 Web Speech API(有限)+ 纯前端 emoji 渲染。混合发送时,消息体必须结构化标记类型,后端不能只收字符串。
uni-app 发送文字+语音+表情混合消息的结构设计
用户输入框里可能同时有“你好”,插入一个 ? 表情,再点击录音按钮发一段语音,这三者不是并列关系,而是三种独立消息类型。强行拼成一个字符串(如 "你好?[voice:12345]")会导致解析错乱、端侧渲染失败、无障碍支持缺失。
- 正确做法是把每条消息抽象为对象:
{ type: 'text', content: '你好' }、{ type: 'emoji', code: '1f604' }、{ type: 'voice', url: '/upload/abc.amr', duration: 3200 } - 发送前合并为数组:
messageList = [ {type:'text',...}, {type:'emoji',...}, {type:'voice',...} ] - 渲染时按
type分支处理:文本走,emoji 走或(取决于是否用图片表情),语音走+ 播放按钮 - H5 端若需语音播报文字,不能依赖
speechSynthesis自动续播——它不感知 emoji,会把 ? 读成“冒号括号”,必须提前过滤或替换
微信小程序端语音与表情共存时的资源冲突
微信小程序里,wx.createInnerAudioContext() 和 wx-plugin-speech 的 TTS 实例共享底层音频通道。如果用户刚点完语音消息播放按钮,紧接着又触发 TTS 朗读一条带 emoji 的提示语(如“对方发送了 ?”),后者大概率静音或报错 errCode: 10001(系统音频被占用)。
- 必须手动管理音频生命周期:每次调用 TTS 前先
audioCtx.stop(),且确保audioCtx.destroy()在页面onUnload时执行 - emoji 不参与 TTS,但它的存在会影响语音合成文本清洗逻辑——比如把
"看这个 ?"直接喂给 TTS,iOS 微信会卡顿;得先正则替换掉所有 Unicode emoji:text.replace(/[\u{1F600}-\u{1F6FF}]/gu, '') - 自定义表情键盘若用
scroll-view实现左右滑动,在 iOS 微信中容易触发touchmove冒泡导致输入框失焦,建议加catchtouchmove阻断
App 端 plus.speech 与表情混排的光标定位问题
在 App 端,用户想在输入框光标处插入 emoji,再点语音按钮录制——此时 plus.speech.speak() 默认会抢焦点,导致 emoji 插入位置错乱,甚至把已输入文字吞掉。
- 必须在调用
plus.speech.speak()前主动保存光标位置:const selection = document.getSelection(); const range = selection.getRangeAt(0); - 语音播放结束后,用
range.insertNode()把语音占位符(如[语音])插回原位置,而不是用v-model覆盖整个输入框值 - Android 上
plus.speech的rate参数超过 2.0 容易失真,iOS 则对pitch敏感,测试时务必用真机,模拟器无效 - 表情键盘若用
nvue实现,在 Android 低端机上滑动卡顿明显,建议降级为view+swiper,避免嵌套过深
最常被忽略的一点:语音消息的 duration 字段不能靠前端 JS Date.now() 计算。用户按住录音、滑动取消、再重按——这些操作都会让时间戳不可靠。必须由录音 SDK 返回真实时长,或后端解析音频文件后回传。否则聊天界面里语音气泡的长度会严重失真。











