<template>
<div class="speech-container" style="max-width: 500px; margin: 30px auto; padding: 0 15px;">
<el-button
type="primary"
:loading="isLoading"
@click="toggleRecognition"
icon="el-icon-microphone"
style="margin-bottom: 20px;"
>
<!-- 按钮文字根据识别状态切换 -->
<span v-if="!isListening">开始语音识别</span>
<span v-else>停止语音识别</span>
</el-button>
<!-- 识别结果:追加模式 -->
<el-input
v-model="resultText"
@input="handleTextManualEdit"
type="textarea"
:rows="6"
placeholder="说话内容会自动追加,再次点击按钮可停止识别"
style="margin-bottom: 10px;"
/>
<!-- 简洁状态提示 -->
<el-text type="info">{{ statusText }}</el-text>
</div>
</template>
<script setup>
import { ref } from 'vue';
// 核心状态
const isLoading = ref(false); // 按钮 loading 状态(Element Plus 自带)
const isListening = ref(false); // 是否正在识别
const resultText = ref(''); // 最终显示结果
const statusText = ref('就绪'); // 状态提示
// 拆分最终/临时结果(保证追加不重复)
const finalTranscript = ref(''); // 已确认的最终结果
const interimTranscript = ref(''); // 实时临时结果
let recognitionInstance = null; // 语音识别实例
// 初始化浏览器原生语音识别对象(兼容前缀)
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
/**
* 处理用户手动编辑文本框:同步核心数据源,避免旧内容复现
* @param {string} newValue 用户手动输入的新内容
*/
function handleTextManualEdit(newValue) {
// 手动修改时,将最终结果同步为用户输入的新值
finalTranscript.value = newValue;
// 清空临时结果(避免和手动内容冲突)
interimTranscript.value = '';
// 保险:确保显示值和核心数据源一致
resultText.value = finalTranscript.value;
}
/**
* 初始化语音识别实例
*/
function initRecognition() {
if (!SpeechRecognition) {
statusText.value = '浏览器不支持语音识别(推荐 Chrome/Edge/Safari)';
isLoading.value = false;
return null;
}
const recognition = new SpeechRecognition();
// 核心配置
recognition.lang = 'zh-CN'; // 中文普通话
recognition.continuous = true; // 持续识别
recognition.interimResults = true; // 实时返回临时结果
// 识别结果回调:追加逻辑
recognition.onresult = (event) => {
interimTranscript.value = '';
// 遍历结果,区分「最终结果」和「临时结果」
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) {
finalTranscript.value += transcript; // 最终结果永久追加
} else {
interimTranscript.value = transcript; // 临时结果实时更新
}
}
// 合并显示最终结果 + 临时结果
resultText.value = finalTranscript.value + interimTranscript.value;
};
// 识别启动回调
recognition.onstart = () => {
isLoading.value = false; // 初始化完成,关闭 loading
isListening.value = true; // 标记为「识别中」
statusText.value = '正在识别,请说话(内容会自动追加)';
};
// 识别中断/结束回调
recognition.onend = () => {
if (isListening.value) {
// 识别中意外断开:自动重启(保留结果)
recognition.start();
statusText.value = '正在识别(已自动重启)';
} else {
// 手动停止:合并最后未确认的临时结果
finalTranscript.value += interimTranscript.value;
resultText.value = finalTranscript.value;
interimTranscript.value = '';
statusText.value = '识别已停止,再次点击可继续追加内容';
}
};
// 错误处理
recognition.onerror = (error) => {
statusText.value = `识别错误:${error.error}`;
isLoading.value = false;
isListening.value = false;
interimTranscript.value = '';
};
return recognition;
}
/**
* 单按钮切换:开始/停止识别
*/
function toggleRecognition() {
// 1. 当前未识别 → 启动识别
if (!isListening.value) {
isLoading.value = true; // 按钮进入 loading 状态
statusText.value = '正在初始化麦克风...';
// 销毁旧实例(如果有)
if (recognitionInstance) recognitionInstance.abort();
// 初始化新实例并启动
recognitionInstance = initRecognition();
if (!recognitionInstance) return;
try {
recognitionInstance.start(); // 启动语音识别
} catch (err) {
statusText.value = `启动失败:${err.message}`;
isLoading.value = false;
}
// 2. 当前识别中 → 停止识别
} else {
isLoading.value = true; // 按钮进入 loading 状态
statusText.value = '正在停止识别...';
// 终止识别实例
if (recognitionInstance) {
recognitionInstance.abort();
}
isListening.value = false; // 标记为「未识别」
isLoading.value = false; // 关闭 loading
}
}
</script>
<style scoped>
/* 可选:微调样式,保持和 Element Plus 风格统一 */
.el-text {
font-size: 14px;
line-height: 1.5;
}
</style>
注意:
上述使用的是浏览器原生API实现window.SpeechRecognition,有兼容性问题,目前在edge浏览器正常使用,谷歌浏览器没反应.......
如果要实现优秀的浏览器兼容性,可以使用各大云厂商的语音输入SDK,需要收费