建材商城系统20241014
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

629 lines
17 KiB

<template>
<view class="hand-top">
<navbar
title="快捷下单"
leftClick
@leftClick="$utils.navigateBack"
/>
<view class="voice-top">
<view class="left-icon"></view>
<text>录制语音下单</text>
</view>
<view class="voice-upload">
<view class="long-speak"
@touchstart="startRecord"
@touchend="stopRecord"
@touchcancel="cancelRecord"
:class="{'recording': isRecording}">
<uv-icon name="mic" size="45rpx" color="#DC2828"></uv-icon>
<text>{{isRecording ? '松开结束录音' : '长按可说话'}}</text>
</view>
<view class="recording-status" v-if="isRecording">
<text>正在录音中...</text>
<view class="recording-time">{{recordingTime}}s</view>
</view>
<!-- 录音波形效果 -->
<view class="voice-wave" v-if="isRecording">
<view class="wave-item" v-for="(item, index) in waveItems" :key="index"
:style="{height: item + 'rpx'}"></view>
</view>
<view class="recording-file" v-if="audioPath">
<view class="file">
<image src="../static/order/1.png" mode="" class="record"></image>
<image src="../static/order/2.png" mode="" class="file-start" @click="playVoice"></image>
<image src="../static/order/3.png" mode="" class="file-delete" @click="deleteVoice"></image>
<view class="file-top">
<p>{{audioName}}</p>
<p style="color: #A6ADBA;">{{audioSize}}</p>
</view>
<view class="file-bottom">
<view class="schedule" :style="{width: uploadProgress + '%'}"></view>
<text>{{uploadProgress}}%</text>
</view>
</view>
</view>
<view class="text-upload">
<text>(录音上传你所需要识别的产品语音)</text>
</view>
</view>
<view class="fast-order">
<view class="voice-button" @click="submitVoiceOrder" :style="audioPath ? '' : 'opacity: 0.5;'">
<text>快捷下单</text>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
recorderManager: null, // 录音管理器
innerAudioContext: null, // 音频播放器
isRecording: false, // 是否正在录音
isPlaying: false, // 是否正在播放
audioPath: '', // 录音文件路径
audioName: '录音文件01.mp3', // 录音文件名称
audioSize: '0KB', // 录音文件大小
uploadProgress: 0, // 上传进度
recordStartTime: 0, // 录音开始时间
recordTimeout: null, // 录音超时计时器
recordMaxDuration: 60000, // 最大录音时长(毫秒)
recordingTime: 0, // 当前录音时长(秒)
recordTimer: null, // 录音计时器
isUploading: false, // 是否正在上传
recognitionResult: null, // 语音识别结果
waveTimer: null, // 波形动画计时器
waveItems: [], // 波形数据
audioUrl: '', // 上传后的音频URL
};
},
onLoad() {
// 初始化录音管理器
this.recorderManager = uni.getRecorderManager();
// 初始化波形数据
this.initWaveItems();
// 监听录音开始事件
this.recorderManager.onStart(() => {
console.log('录音开始');
this.isRecording = true;
this.recordStartTime = Date.now();
this.recordingTime = 0;
// 开始计时
this.recordTimer = setInterval(() => {
this.recordingTime = Math.floor((Date.now() - this.recordStartTime) / 1000);
}, 1000);
// 开始波形动画
this.startWaveAnimation();
// 设置最大录音时长
this.recordTimeout = setTimeout(() => {
if (this.isRecording) {
this.stopRecord();
}
}, this.recordMaxDuration);
});
// 监听录音结束事件
this.recorderManager.onStop((res) => {
console.log('录音结束', res);
this.isRecording = false;
if (this.recordTimeout) {
clearTimeout(this.recordTimeout);
this.recordTimeout = null;
}
if (this.recordTimer) {
clearInterval(this.recordTimer);
this.recordTimer = null;
}
// 停止波形动画
this.stopWaveAnimation();
if (res.tempFilePath) {
this.audioPath = res.tempFilePath;
// 计算录音文件大小并格式化
this.formatFileSize(res.fileSize || 0);
// 生成录音文件名
this.audioName = '录音文件' + this.formatDate(new Date()) + '.mp3';
// 如果时长过短,提示用户
if (this.recordingTime < 1) {
uni.showToast({
title: '录音时间太短,请重新录制',
icon: 'none'
});
this.audioPath = '';
}
}
});
// 监听录音错误事件
this.recorderManager.onError((err) => {
console.error('录音错误', err);
uni.showToast({
title: '录音失败: ' + err.errMsg,
icon: 'none'
});
this.isRecording = false;
if (this.recordTimeout) {
clearTimeout(this.recordTimeout);
this.recordTimeout = null;
}
if (this.recordTimer) {
clearInterval(this.recordTimer);
this.recordTimer = null;
}
// 停止波形动画
this.stopWaveAnimation();
});
// 初始化音频播放器
this.innerAudioContext = uni.createInnerAudioContext();
// 监听播放结束事件
this.innerAudioContext.onEnded(() => {
console.log('播放结束');
this.isPlaying = false;
});
// 监听播放错误事件
this.innerAudioContext.onError((err) => {
console.error('播放错误', err);
uni.showToast({
title: '播放失败',
icon: 'none'
});
this.isPlaying = false;
});
},
onUnload() {
// 销毁录音管理器和音频播放器
if (this.innerAudioContext) {
this.innerAudioContext.destroy();
}
if (this.recordTimeout) {
clearTimeout(this.recordTimeout);
this.recordTimeout = null;
}
if (this.recordTimer) {
clearInterval(this.recordTimer);
this.recordTimer = null;
}
// 停止波形动画
this.stopWaveAnimation();
},
methods: {
// 初始化波形数据
initWaveItems() {
const itemCount = 16; // 波形柱状图数量
this.waveItems = Array(itemCount).fill(10); // 初始高度10rpx
},
// 开始波形动画
startWaveAnimation() {
// 停止可能已存在的动画
this.stopWaveAnimation();
// 随机生成波形高度
this.waveTimer = setInterval(() => {
const newWaveItems = [];
for (let i = 0; i < this.waveItems.length; i++) {
// 生成10-60之间的随机高度
newWaveItems.push(Math.floor(Math.random() * 50) + 10);
}
this.waveItems = newWaveItems;
}, 100);
},
// 停止波形动画
stopWaveAnimation() {
if (this.waveTimer) {
clearInterval(this.waveTimer);
this.waveTimer = null;
}
this.initWaveItems(); // 重置波形
},
// 开始录音
startRecord() {
if (this.isRecording) return;
const options = {
duration: this.recordMaxDuration, // 最大录音时长
sampleRate: 44100, // 采样率
numberOfChannels: 1, // 录音通道数
encodeBitRate: 192000, // 编码码率
format: 'mp3', // 音频格式
frameSize: 50 // 指定帧大小
};
// 开始录音
this.recorderManager.start(options);
// 震动反馈
uni.vibrateShort({
success: function () {
console.log('震动成功');
}
});
},
// 停止录音
stopRecord() {
if (!this.isRecording) return;
this.recorderManager.stop();
// 震动反馈
uni.vibrateShort({
success: function () {
console.log('震动成功');
}
});
},
// 取消录音
cancelRecord() {
if (!this.isRecording) return;
this.recorderManager.stop();
this.audioPath = ''; // 清空录音路径
uni.showToast({
title: '录音已取消',
icon: 'none'
});
},
// 播放录音
playVoice() {
if (!this.audioPath) {
uni.showToast({
title: '没有可播放的录音',
icon: 'none'
});
return;
}
if (this.isPlaying) {
// 如果正在播放,则停止播放
this.innerAudioContext.stop();
this.isPlaying = false;
return;
}
// 设置音频源
this.innerAudioContext.src = this.audioPath;
this.innerAudioContext.play();
this.isPlaying = true;
// 播放完成后自动停止
setTimeout(() => {
if (this.isPlaying) {
this.isPlaying = false;
}
}, this.recordingTime * 1000 + 500); // 增加500ms缓冲时间
},
// 删除录音
deleteVoice() {
if (!this.audioPath) return;
uni.showModal({
title: '提示',
content: '确定要删除这段录音吗?',
success: (res) => {
if (res.confirm) {
// 如果正在播放,先停止播放
if (this.isPlaying) {
this.innerAudioContext.stop();
this.isPlaying = false;
}
this.audioPath = '';
this.audioName = '录音文件01.mp3';
this.audioSize = '0KB';
this.recordingTime = 0;
this.audioUrl = '';
uni.showToast({
title: '录音已删除',
icon: 'none'
});
}
}
});
},
// 提交语音订单
submitVoiceOrder() {
if (!this.audioPath) {
uni.showToast({
title: '请先录制语音',
icon: 'none'
});
return;
}
if (this.isUploading) {
uni.showToast({
title: '正在上传中,请稍候',
icon: 'none'
});
return;
}
// 显示加载提示
uni.showLoading({
title: '上传中...'
});
this.isUploading = true;
this.uploadProgress = 0;
// 上传音频文件
this.uploadAudioFile();
},
// 上传音频文件
uploadAudioFile() {
// 模拟上传进度
const simulateProgress = () => {
this.uploadProgress = 0;
const interval = setInterval(() => {
this.uploadProgress += 5;
if (this.uploadProgress >= 90) {
clearInterval(interval);
}
}, 100);
return interval;
};
const progressInterval = simulateProgress();
// 使用OSS上传服务上传音频文件
this.$Oss.ossUpload(this.audioPath).then(url => {
// 上传成功
clearInterval(progressInterval);
this.uploadProgress = 100;
this.audioUrl = url;
console.log('音频上传成功', url);
// 调用语音下单接口
this.createVoiceOrder(url);
}).catch(err => {
// 上传失败
clearInterval(progressInterval);
console.error('音频上传失败', err);
this.handleUploadFailed('音频上传失败,请重试');
});
},
// 创建语音订单
createVoiceOrder(audioUrl) {
this.$api('index.addOrder', {
voiceUrl: audioUrl,
type: '2', // 2表示语音下单
userId: uni.getStorageSync('userId') || ''
}, res => {
uni.hideLoading();
this.isUploading = false;
if (res.code === 200) {
// 下单成功
uni.showToast({
title: '下单成功',
icon: 'success',
duration: 1500,
success: () => {
setTimeout(() => {
// 跳转到订单列表页
this.$utils.redirectTo('/pages_order/order/orderList');
}, 1500);
}
});
} else {
uni.showModal({
title: '提示',
content: res.message || '下单失败',
showCancel: false
});
}
}, err => {
// 错误处理
uni.hideLoading();
this.isUploading = false;
console.error('下单请求失败', err);
this.handleUploadFailed('网络请求失败,请检查网络连接');
});
},
// 处理上传失败情况
handleUploadFailed(message) {
uni.hideLoading();
this.isUploading = false;
this.uploadProgress = 0;
uni.showModal({
title: '上传失败',
content: message,
showCancel: false
});
},
// 格式化文件大小
formatFileSize(size) {
if (size < 1024) {
this.audioSize = size + 'B';
} else if (size < 1024 * 1024) {
this.audioSize = (size / 1024).toFixed(2) + 'KB';
} else {
this.audioSize = (size / (1024 * 1024)).toFixed(2) + 'MB';
}
},
// 格式化日期为字符串
formatDate(date) {
const pad = (n) => n < 10 ? '0' + n : n;
return pad(date.getMonth() + 1) + pad(date.getDate());
}
}
}
</script>
<style scoped lang="scss">
.hand-top{
background-color: #ffffff;
.voice-top{
color: #333333;
height: 100rpx;
display: flex;
align-items: center;
background-color: #ffffff;
.left-icon{
background-color: #D03F25;
display: inline-block;
width: 10rpx;
height: 30rpx;
border-radius: 100rpx;
margin-left: 50rpx;
margin-right: 20rpx;
padding-bottom: 5rpx;
}
}
.voice-upload{
.long-speak{
color: #DC2828;
border: 1rpx solid #DC2828;
width: 85%;
height: 80rpx;
margin: auto;
margin-top: 60rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100rpx;
&.recording {
background-color: rgba(220, 40, 40, 0.1);
border: 1rpx solid #DC2828;
}
}
.recording-status {
width: 85%;
margin: auto;
margin-top: 20rpx;
text-align: center;
color: #DC2828;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
.recording-time {
margin-left: 10rpx;
font-weight: bold;
}
}
.voice-wave {
width: 85%;
height: 120rpx;
margin: 20rpx auto;
display: flex;
justify-content: space-between;
align-items: flex-end;
.wave-item {
width: 10rpx;
background-color: #DC2828;
border-radius: 10rpx;
transition: height 0.1s ease-in-out;
}
}
.recording-file{
height: 250rpx;
display: flex;
align-items: center;
justify-content: center;
position: relative;
margin-top: 30rpx;
.file{
background-color: #F4F4F4;
width: 95%;
height: 200rpx;
border-radius: 20rpx;
.record{
position: absolute;
height: 80rpx;
width: 80rpx;
top: 65rpx;
left: 60rpx;
}
.file-start{
position: absolute;
height: 30rpx;
width: 30rpx;
top: 60rpx;
right: 100rpx;
}
.file-delete{
position: absolute;
height: 30rpx;
width: 30rpx;
top: 60rpx;
right: 50rpx;
}
.file-top{
position: absolute;
width: 280rpx;
top: 60rpx;
left: 160rpx;
}
.file-bottom{
position: absolute;
width: 88%;
height: 30rpx;
display: flex;
align-items: center;
top: 160rpx;
left: 60rpx;
.schedule{
width: 80%;
height: 10rpx;
border-radius: 30rpx;
background-color: #D03F25;
margin-right: 15rpx;
}
}
}
}
.text-upload{
height: 100rpx;
text-align: center;
line-height: 100rpx;
color: #666666;
margin-bottom: 100rpx;
}
}
.voice-button{
color: #ffffff;
background-color: #DC2828;
width: 85%;
height: 100rpx;
margin: auto;
display: flex;
align-items: center;
justify-content: center;
border-radius: 100rpx;
}
}
</style>