|
|
<template>
|
|
|
<view>
|
|
|
<!-- #ifdef APP-PLUS ||MP-WEIXIN -->
|
|
|
|
|
|
<view class="record-btn" @longpress="startRecord" @touchend="endRecord" hover-class="record-btn-hover"
|
|
|
hover-start-time="200" hover-stay-time="150" :style="[
|
|
|
btnStyle,
|
|
|
{
|
|
|
'--btn-hover-fontcolor': btnHoverFontcolor,
|
|
|
'--btn-hover-bgcolor': btnHoverBgcolor
|
|
|
}
|
|
|
]">
|
|
|
{{ btnTextContent }}
|
|
|
</view>
|
|
|
<!-- #endif -->
|
|
|
|
|
|
<view class="record-popup" :style="{
|
|
|
'--popup-height': popupHeight,
|
|
|
'--popup-width': upx2px(popupMaxWidth),
|
|
|
'--popup-bottom': upx2px(popupFixBottom),
|
|
|
'--popup-bg-color': popupBgColor
|
|
|
}">
|
|
|
<view class="inner-content" v-if="recordPopupShow">
|
|
|
<view class="title">{{ popupTitle }}</view>
|
|
|
<view class="voice-line-wrap" v-if="recording" :style="{
|
|
|
'--line-height': upx2px(lineHeight),
|
|
|
'--line-start-color': lineStartColor,
|
|
|
'--line-end-color': lineEndColor
|
|
|
}">
|
|
|
<view class="voice-line one"></view>
|
|
|
<view class="voice-line two"></view>
|
|
|
<view class="voice-line three"></view>
|
|
|
<view class="voice-line four"></view>
|
|
|
<view class="voice-line five"></view>
|
|
|
<view class="voice-line six"></view>
|
|
|
<view class="voice-line seven"></view>
|
|
|
<view class="voice-line six"></view>
|
|
|
<view class="voice-line five"></view>
|
|
|
<view class="voice-line four"></view>
|
|
|
<view class="voice-line three"></view>
|
|
|
<view class="voice-line two"></view>
|
|
|
<view class="voice-line one"></view>
|
|
|
</view>
|
|
|
<view class="cancel-icon" v-if="!recording">+</view>
|
|
|
<view class="tips">{{ recording ? popupDefaultTips : popupCancelTips }}</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
<script>
|
|
|
var that;
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
const recorderManager = uni.getRecorderManager();
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef APP-PLUS
|
|
|
// 引入权限判断
|
|
|
import permision from '../../js_sdk/wa-permission/permission.js';
|
|
|
// #endif
|
|
|
export default {
|
|
|
name: 'nbVoiceRecord',
|
|
|
/**
|
|
|
* 录音交互动效组件
|
|
|
* @property {Object} recordOptions 录音配置
|
|
|
* @property {Object} btnStyle 按钮样式
|
|
|
* @property {Object} btnHoverFontcolor 按钮长按时字体颜色
|
|
|
* @property {String} btnHoverBgcolor 按钮长按时背景颜色
|
|
|
* @property {String} btnDefaultText 按钮初始文字
|
|
|
* @property {String} btnRecordingText 录制时按钮文字
|
|
|
* @property {Boolean} vibrate 弹窗时是否震动
|
|
|
* @property {String} popupTitle 弹窗顶部文字
|
|
|
* @property {String} popupDefaultTips 录制时弹窗底部提示
|
|
|
* @property {String} popupCancelTips 滑动取消时弹窗底部提示
|
|
|
* @property {String} popupMaxWidth 弹窗展开后宽度
|
|
|
* @property {String} popupMaxHeight 弹窗展开后高度
|
|
|
* @property {String} popupFixBottom 弹窗展开后距底部高度
|
|
|
* @property {String} popupBgColor 弹窗背景颜色
|
|
|
* @property {String} lineHeight 声波高度
|
|
|
* @property {String} lineStartColor 声波波谷时颜色色值
|
|
|
* @property {String} lineEndColor 声波波峰时颜色色值
|
|
|
* @event {Function} startRecord 开始录音回调
|
|
|
* @event {Function} endRecord 结束录音回调
|
|
|
* @event {Function} cancelRecord 滑动取消录音回调
|
|
|
* @event {Function} stopRecord 主动停止录音
|
|
|
*/
|
|
|
props: {
|
|
|
recordOptions: {
|
|
|
type: Object,
|
|
|
default () {
|
|
|
return {
|
|
|
duration: 600000
|
|
|
}; // 请自行查看各端的的支持情况,这里全部使用默认配置
|
|
|
}
|
|
|
},
|
|
|
btnStyle: {
|
|
|
type: Object,
|
|
|
default () {
|
|
|
return {
|
|
|
width: '300rpx',
|
|
|
height: '80rpx',
|
|
|
borderRadius: '20rpx',
|
|
|
backgroundColor: '#409EFF',
|
|
|
border: '1rpx solid whitesmoke',
|
|
|
permisionState: false
|
|
|
};
|
|
|
}
|
|
|
},
|
|
|
btnHoverFontcolor: {
|
|
|
type: String,
|
|
|
default: '#000' // 颜色名称或16进制色值
|
|
|
},
|
|
|
btnHoverBgcolor: {
|
|
|
type: String,
|
|
|
default: 'whitesmoke' // 颜色名称或16进制色值
|
|
|
},
|
|
|
btnDefaultText: {
|
|
|
type: String,
|
|
|
default: '长按开始录音'
|
|
|
},
|
|
|
btnRecordingText: {
|
|
|
type: String,
|
|
|
default: '录音中'
|
|
|
},
|
|
|
vibrate: {
|
|
|
type: Boolean,
|
|
|
default: false
|
|
|
},
|
|
|
// #ifdef APP-PLUS || MP-WEIXIN
|
|
|
popupTitle: {
|
|
|
type: String,
|
|
|
default: '正在录制音频'
|
|
|
},
|
|
|
popupDefaultTips: {
|
|
|
type: String,
|
|
|
default: '松手结束录音'
|
|
|
},
|
|
|
// #endif
|
|
|
popupCancelTips: {
|
|
|
type: String,
|
|
|
default: '松手取消录音'
|
|
|
},
|
|
|
popupMaxWidth: {
|
|
|
type: Number,
|
|
|
default: 600 // 单位为rpx
|
|
|
},
|
|
|
popupMaxHeight: {
|
|
|
type: Number,
|
|
|
default: 300 // 单位为rpx
|
|
|
},
|
|
|
popupFixBottom: {
|
|
|
type: Number,
|
|
|
default: 200 // 单位为rpx
|
|
|
},
|
|
|
popupBgColor: {
|
|
|
type: String,
|
|
|
default: 'whitesmoke'
|
|
|
},
|
|
|
lineHeight: {
|
|
|
type: Number,
|
|
|
default: 50 // 单位为rpx
|
|
|
},
|
|
|
lineStartColor: {
|
|
|
type: String,
|
|
|
default: 'royalblue' // 颜色名称或16进制色值
|
|
|
},
|
|
|
lineEndColor: {
|
|
|
type: String,
|
|
|
default: 'indianred' // 颜色名称或16进制色值
|
|
|
}
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
stopStatus: true, // 是否已被父页面通知主动结束录音
|
|
|
btnTextContent: this.btnDefaultText,
|
|
|
startTouchData: {},
|
|
|
popupHeight: '0px', // 这是初始的高度
|
|
|
recording: true, // 录音中
|
|
|
recordPopupShow: false,
|
|
|
recordTimeout: null, // 录音定时器
|
|
|
h5start: false
|
|
|
};
|
|
|
},
|
|
|
created() {
|
|
|
// 请求权限
|
|
|
this.checkPermission();
|
|
|
},
|
|
|
computed: {},
|
|
|
methods: {
|
|
|
upx2px(upx) {
|
|
|
return uni.upx2px(upx) + 'px';
|
|
|
},
|
|
|
async checkPermission() {
|
|
|
var that = this;
|
|
|
// #ifdef APP-PLUS
|
|
|
// 先判断os
|
|
|
let os = uni.getSystemInfoSync().osName;
|
|
|
if (os == 'ios') {
|
|
|
this.permisionState = await permision.judgeIosPermission('record');
|
|
|
} else {
|
|
|
this.permisionState = await permision.requestAndroidPermission('android.permission.RECORD_AUDIO');
|
|
|
}
|
|
|
if (this.permisionState !== true && this.permisionState !== 1) {
|
|
|
uni.showToast({
|
|
|
title: '请先授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
return;
|
|
|
}
|
|
|
// #endif
|
|
|
|
|
|
// #ifdef MP-WEIXIN
|
|
|
uni.authorize({
|
|
|
scope: 'scope.record',
|
|
|
success(e) {
|
|
|
that.permisionState = true;
|
|
|
// that.startRecord();
|
|
|
},
|
|
|
fail() {
|
|
|
uni.showToast({
|
|
|
title: '请授权使用录音',
|
|
|
icon: 'none'
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
// #endif
|
|
|
},
|
|
|
|
|
|
startRecord() {
|
|
|
if (!this.permisionState) {
|
|
|
this.checkPermission();
|
|
|
return;
|
|
|
}
|
|
|
this.stopStatus = false;
|
|
|
setTimeout(() => {
|
|
|
this.popupHeight = this.upx2px(this.popupMaxHeight);
|
|
|
setTimeout(() => {
|
|
|
this.recordPopupShow = true;
|
|
|
this.btnTextContent = this.btnRecordingText;
|
|
|
// 开始录音
|
|
|
// recorderManager.start(this.recordOptions);
|
|
|
// 设置定时器
|
|
|
this.recordTimeout = setTimeout(
|
|
|
() => {
|
|
|
// 如果定时器先结束,则说明此时录音时间超限
|
|
|
this.stopRecord(); // 结束录音动画(实际上录音的end回调已经先执行)
|
|
|
this.recordTimeout = null; // 重置
|
|
|
},
|
|
|
this.recordOptions.duration ? this.recordOptions.duration : 600000
|
|
|
);
|
|
|
|
|
|
this.$emit('startRecord');
|
|
|
}, 100);
|
|
|
}, 200);
|
|
|
},
|
|
|
endRecord() {
|
|
|
var that = this;
|
|
|
if (this.stopStatus) {
|
|
|
return;
|
|
|
}
|
|
|
this.popupHeight = '0px';
|
|
|
this.recordPopupShow = false;
|
|
|
this.btnTextContent = this.btnDefaultText;
|
|
|
this.$emit('endRecord');
|
|
|
},
|
|
|
stopRecord() {
|
|
|
// 用法如你录音限制了时间,那么将在结束时强制停止组件的显示
|
|
|
this.endRecord();
|
|
|
this.stopStatus = true;
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
<style lang="scss">
|
|
|
.record-btn {
|
|
|
color: #fff;
|
|
|
font-size: 24rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
transition: 0.25s all;
|
|
|
border: 1rpx solid whitesmoke;
|
|
|
}
|
|
|
|
|
|
.record-btn-hover {
|
|
|
color: var(--btn-hover-fontcolor) !important;
|
|
|
background-color: var(--btn-hover-bgcolor) !important;
|
|
|
}
|
|
|
|
|
|
.record-popup {
|
|
|
position: absolute;
|
|
|
bottom: var(--popup-bottom);
|
|
|
left: calc(50vw - calc(var(--popup-width) / 2));
|
|
|
z-index: 1;
|
|
|
width: var(--popup-width);
|
|
|
height: var(--popup-height);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 10rpx;
|
|
|
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
|
|
|
background: var(--popup-bg-color);
|
|
|
color: #000;
|
|
|
transition: 0.2s height;
|
|
|
|
|
|
.inner-content {
|
|
|
height: var(--popup-height);
|
|
|
font-size: 24rpx;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
|
|
|
.title {
|
|
|
font-weight: bold;
|
|
|
padding: 20rpx 0;
|
|
|
}
|
|
|
|
|
|
.tips {
|
|
|
color: #999;
|
|
|
padding: 20rpx 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.cancel-icon {
|
|
|
width: 100rpx;
|
|
|
height: 100rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
color: #fff;
|
|
|
font-size: 44rpx;
|
|
|
line-height: 44rpx;
|
|
|
background-color: pink;
|
|
|
border-radius: 50%;
|
|
|
transform: rotate(45deg);
|
|
|
}
|
|
|
|
|
|
.voice-line-wrap {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
|
|
|
.voice-line {
|
|
|
width: 5rpx;
|
|
|
height: var(--line-height);
|
|
|
border-radius: 3rpx;
|
|
|
margin: 0 5rpx;
|
|
|
}
|
|
|
|
|
|
.one {
|
|
|
animation: wave 0.4s 1s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.two {
|
|
|
animation: wave 0.4s 0.9s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.three {
|
|
|
animation: wave 0.4s 0.8s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.four {
|
|
|
animation: wave 0.4s 0.7s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.five {
|
|
|
animation: wave 0.4s 0.6s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.six {
|
|
|
animation: wave 0.4s 0.5s linear infinite alternate;
|
|
|
}
|
|
|
|
|
|
.seven {
|
|
|
animation: wave 0.4s linear infinite alternate;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@keyframes wave {
|
|
|
0% {
|
|
|
transform: scale(1, 1);
|
|
|
background-color: var(--line-start-color);
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
transform: scale(1, 0.2);
|
|
|
background-color: var(--line-end-color);
|
|
|
}
|
|
|
}
|
|
|
</style> |