Vue3实现纯前端语音输入成文字显示

复制代码
<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,需要收费

相关推荐
ohyeah2 小时前
原子化 CSS 与 Fragment:现代前端开发的高效实践
前端
鱼鱼块2 小时前
告别重复传参!用柯里化提升代码优雅度
前端·javascript·面试
chilavert3182 小时前
技术演进中的开发沉思-274 AJax :Button
前端·javascript·ajax·交互
Robet2 小时前
static 和 lib/assets资源区别
前端·svelte
名字被你们想完了2 小时前
Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(九)
前端·flutter
千寻girling2 小时前
面试官: “ 说一下你对 Cookie 的理解 ? ”
前端·后端
RedHeartWWW2 小时前
nextjs中,关于Layout组件和Page组件的认知
前端·react.js
大明二代2 小时前
基于 Microsoft Graph API 与 React Email 构建现代化邮件发送系统
前端·react.js·microsoft
sujiu2 小时前
eslint匹配规则速通
前端