|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, reactive, onBeforeUnmount } from "vue";
|
|
|
|
|
import AudioRecorder from "js-audio-recorder";
|
|
|
|
|
import { receiveVoiceFile } from "@/api/inquiry";
|
|
|
|
|
import tokeGifImg from "@/assets/inquiry/toke.gif";
|
|
|
|
|
import tokeImg from "@/assets/inquiry/toke.png";
|
|
|
|
|
import { onClickOutside } from "@vueuse/core";
|
|
|
|
|
import { onMounted } from "vue";
|
|
|
|
|
import { message } from "@/utils/message";
|
|
|
|
|
|
|
|
|
|
defineOptions({
|
|
|
|
|
name: "VoiceInquiry"
|
|
|
|
|
});
|
|
|
|
|
const defultText = "正在问诊......";
|
|
|
|
|
const text = ref(defultText);
|
|
|
|
|
const container = ref(null);
|
|
|
|
|
let audioRecorder: any = reactive({ undefined });
|
|
|
|
|
let intervalId: any = reactive({ undefined });
|
|
|
|
|
const recordType = ref("0"); // 0未开始 1 开始录制 2 暂停 3完成
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
recordType.value = "0";
|
|
|
|
|
// 使用js-audio-recorder录制音频
|
|
|
|
|
audioRecorder = new AudioRecorder({
|
|
|
|
|
sampleBits: 16, // 采样位数,支持 8 或 16,默认是16
|
|
|
|
|
sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
|
|
|
|
|
numChannels: 1 // 声道,支持 1 或 2, 默认是1
|
|
|
|
|
});
|
|
|
|
|
startRecording();
|
|
|
|
|
checkSpeaking();
|
|
|
|
|
});
|
|
|
|
|
function checkSpeaking() {
|
|
|
|
|
intervalId = setInterval(() => {
|
|
|
|
|
const dataArray = audioRecorder.getRecordAnalyseData();
|
|
|
|
|
const isSpeaking = Array.from(dataArray).some(value => value > 128);
|
|
|
|
|
if (isSpeaking) {
|
|
|
|
|
if (recordType.value === "0") {
|
|
|
|
|
recordType.value = "1";
|
|
|
|
|
// startRecording();
|
|
|
|
|
} else if (recordType.value === "2") {
|
|
|
|
|
audioRecorder.resume();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (recordType.value === "1") {
|
|
|
|
|
audioRecorder.pause();
|
|
|
|
|
recordType.value = "2";
|
|
|
|
|
} else if (recordType.value === "2") {
|
|
|
|
|
stopRecording();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
console.log("Is speaking:", isSpeaking);
|
|
|
|
|
}, 500); // 每 500 毫秒获取一次音量
|
|
|
|
|
}
|
|
|
|
|
const startRecording = () => {
|
|
|
|
|
audioRecorder.start();
|
|
|
|
|
};
|
|
|
|
|
async function stopRecording() {
|
|
|
|
|
recordType.value = "0";
|
|
|
|
|
clearInterval(intervalId);
|
|
|
|
|
audioRecorder.stop();
|
|
|
|
|
const blob = audioRecorder.getWAVBlob();
|
|
|
|
|
const params = new FormData();
|
|
|
|
|
params.append("file", blob);
|
|
|
|
|
const { data } = await receiveVoiceFile(params);
|
|
|
|
|
text.value = data;
|
|
|
|
|
recordType.value = "3";
|
|
|
|
|
sendVoiceOption();
|
|
|
|
|
}
|
|
|
|
|
const emit = defineEmits(["changeType", "save"]);
|
|
|
|
|
|
|
|
|
|
// const reset = () => {
|
|
|
|
|
// text.value = defultText;
|
|
|
|
|
// recordType.value = "0";
|
|
|
|
|
// startRecording();
|
|
|
|
|
// checkSpeaking();
|
|
|
|
|
// };
|
|
|
|
|
onClickOutside(container, () => emit("changeType", 0));
|
|
|
|
|
|
|
|
|
|
const sendVoiceOption = () => {
|
|
|
|
|
if (!text.value || text.value === defultText) {
|
|
|
|
|
message("问诊内容不能为空!", { type: "error" });
|
|
|
|
|
}
|
|
|
|
|
emit("changeType", 0);
|
|
|
|
|
emit("save", text.value);
|
|
|
|
|
};
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
audioRecorder.destroy();
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
<div ref="container" class="voiceInquiry">
|
|
|
|
|
<div class="voice_footer_cotent">
|
|
|
|
|
<img v-show="recordType === '3'" :src="tokeImg" alt="" />
|
|
|
|
|
<img v-show="recordType !== '3'" :src="tokeGifImg" alt="" />
|
|
|
|
|
<span>{{ text }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- <div v-show="recordType === '3'" class="btn_list">
|
|
|
|
|
<span @click="reset">重置</span>
|
|
|
|
|
<el-button
|
|
|
|
|
class="btn"
|
|
|
|
|
size="large"
|
|
|
|
|
@click="sendVoiceOption"
|
|
|
|
|
type="primary"
|
|
|
|
|
>发送</el-button
|
|
|
|
|
>
|
|
|
|
|
</div> -->
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
.voiceInquiry {
|
|
|
|
|
position: fixed;
|
|
|
|
|
bottom: 66px;
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
width: 500px;
|
|
|
|
|
height: 104px;
|
|
|
|
|
background-image: url("@/assets/inquiry/voice_bg.png");
|
|
|
|
|
background-size: 100% 100%;
|
|
|
|
|
box-shadow: 0 0 8px 0 rgb(0 0 0 / 15%);
|
|
|
|
|
|
|
|
|
|
.voice_footer_cotent {
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
width: 300px;
|
|
|
|
|
height: 40px;
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
margin-top: 6px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
color: #364c63;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.btn_list {
|
|
|
|
|
position: absolute;
|
|
|
|
|
top: 15px;
|
|
|
|
|
right: 16px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
height: 104px;
|
|
|
|
|
|
|
|
|
|
span {
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
font-weight: 400;
|
|
|
|
|
color: #4287ff;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
img {
|
|
|
|
|
width: 24px;
|
|
|
|
|
height: 24px;
|
|
|
|
|
margin-right: 16px;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|