224 lines
4.3 KiB
Vue
224 lines
4.3 KiB
Vue
<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>
|