guoyu/Archive/fronted_uniapp_docs/语音评测-新方案使用示例.vue

224 lines
4.3 KiB
Vue
Raw Permalink Normal View History

2025-12-07 00:11:06 +08:00
<template>
<view class="speech-page">
<view class="title">语音评测新方案</view>
<!-- 题目显示 -->
<view class="question-box">
<text class="question-label">请朗读</text>
<text class="question-text">{{ questionText }}</text>
</view>
<!-- 录音按钮 -->
<view class="record-box">
<button
@touchstart="startRecord"
@touchend="stopRecord"
:disabled="isProcessing"
class="record-btn"
:class="{ recording: isRecording }"
>
{{ recordButtonText }}
</button>
<text class="tip">{{ isRecording ? '松开结束录音' : '按住开始录音' }}</text>
</view>
<!-- 结果显示 -->
<view v-if="result" class="result-box">
<view class="result-item">
<text class="label">识别文本</text>
<text class="value">{{ result.recognizedText || '-' }}</text>
</view>
<view class="result-item">
<text class="label">准确度</text>
<text class="value score">{{ result.score || 0 }}</text>
</view>
<view class="result-item">
<text class="label">评价</text>
<text class="value">{{ result.comment || '-' }}</text>
</view>
</view>
</view>
</template>
<script>
import speechRecorder from '@/utils/speech-recorder.js'
export default {
data() {
return {
questionText: '你好,欢迎使用语音评测系统',
isRecording: false,
isProcessing: false,
result: null
}
},
computed: {
recordButtonText() {
if (this.isProcessing) return '处理中...'
if (this.isRecording) return '录音中...'
return '按住录音'
}
},
onLoad() {
// 初始化录音器
speechRecorder.init()
},
methods: {
// 开始录音
startRecord() {
this.isRecording = true
this.result = null
speechRecorder.start({
duration: 60000, // 最长60秒
sampleRate: 16000,
format: 'mp3'
})
},
// 停止录音并评测
async stopRecord() {
if (!this.isRecording) return
this.isRecording = false
this.isProcessing = true
try {
// 停止录音
const filePath = await speechRecorder.stop()
// 上传并识别
const result = await speechRecorder.uploadAndRecognize(filePath, {
referenceText: this.questionText,
mode: 'evaluation' // 评测模式
})
this.result = result
uni.showToast({
title: '评测完成',
icon: 'success'
})
} catch (error) {
console.error('语音评测失败', error)
uni.showToast({
title: '评测失败:' + error.message,
icon: 'none'
})
} finally {
this.isProcessing = false
}
}
}
}
</script>
<style scoped>
.speech-page {
padding: 40rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
text-align: center;
margin-bottom: 40rpx;
}
.question-box {
background: #f5f5f5;
padding: 30rpx;
border-radius: 12rpx;
margin-bottom: 40rpx;
}
.question-label {
font-size: 28rpx;
color: #666;
display: block;
margin-bottom: 10rpx;
}
.question-text {
font-size: 32rpx;
color: #333;
line-height: 1.6;
}
.record-box {
text-align: center;
margin: 60rpx 0;
}
.record-btn {
width: 300rpx;
height: 300rpx;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
font-size: 32rpx;
border: none;
}
.record-btn.recording {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
animation: pulse 1s infinite;
}
.record-btn:active {
opacity: 0.8;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.tip {
display: block;
margin-top: 20rpx;
font-size: 24rpx;
color: #999;
}
.result-box {
background: #fff;
border: 1rpx solid #e0e0e0;
border-radius: 12rpx;
padding: 30rpx;
}
.result-item {
display: flex;
margin-bottom: 20rpx;
}
.result-item:last-child {
margin-bottom: 0;
}
.label {
font-size: 28rpx;
color: #666;
width: 160rpx;
}
.value {
flex: 1;
font-size: 28rpx;
color: #333;
}
.value.score {
color: #f5576c;
font-size: 36rpx;
font-weight: bold;
}
</style>