431 lines
9.5 KiB
Vue
431 lines
9.5 KiB
Vue
<template>
|
|
<view class="talk-btns" ref="talkBtnRef">
|
|
<view
|
|
class="touch-btn"
|
|
@touchstart="getRecordPermission"
|
|
@touchend="hideOverlayTalking"
|
|
@touchmove="handleMove"
|
|
v-if="!keyboardIsShow"
|
|
>按住说话
|
|
</view>
|
|
<view class="touch-btn" v-if="keyboardIsShow">
|
|
<uv-input class="input-box" ref="inputRef" v-model.trim="answerValue" maxlength="500"
|
|
placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入答案">
|
|
</uv-input>
|
|
</view>
|
|
<view class="talk-btn-box" @click.stop="showKeyboard" v-show="!onlyVoice">
|
|
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="/src/static/images/course/jianpan.png" mode=""></image>
|
|
<image class="talk-btn-icon" v-show="keyboardIsShow" src="/src/static/images/course/send.png" mode=""></image>
|
|
</view>
|
|
<uv-overlay :show="showTalkingModel" opacity=".9" @click="showTalkingModel = false">
|
|
<view class="overlay-box">
|
|
<view class="circle-bg-box">
|
|
<view class="icon-box">
|
|
<image src="/src/static/images/course/talk-icon.png" mode=""></image>
|
|
</view>
|
|
</view>
|
|
<view class="tips-box">松开发送</view>
|
|
<view class="btns-box">
|
|
<view :class="['close-btn',isOut?'is-out':'']" >取消
|
|
<image class="btn-box" v-if="!isOut" src="/src/static/images/course/close.png" mode=""></image>
|
|
<image class="btn-box" v-else src="/src/static/images/course/close-active.png" mode=""></image>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</uv-overlay>
|
|
</view>
|
|
|
|
</template>
|
|
|
|
<script setup>
|
|
import { toRefs, ref, computed, unref, onMounted, onUnmounted, nextTick, watch, getCurrentInstance } from 'vue';
|
|
import common from '../../../common/common';
|
|
import permission from '@/common/permission.js'
|
|
import { getPhoneEnvBool } from '@/common/common.js'
|
|
const proxy = getCurrentInstance()
|
|
const emit = defineEmits(['uploadVoice', 'submitAnswer','startRecord'])
|
|
const props = defineProps({
|
|
isDisabled:{
|
|
default: true,
|
|
type: Boolean
|
|
},
|
|
isLoading:{
|
|
default: false,
|
|
type: Boolean
|
|
},
|
|
loadingText:{
|
|
default: '发送回答中,请稍后再试!',
|
|
type: String
|
|
},
|
|
onlyVoice:{
|
|
default: false,
|
|
type: Boolean
|
|
},
|
|
canAnswer:{
|
|
default: true,
|
|
type: Boolean
|
|
},
|
|
canAnswerText:{
|
|
default: '当前状态不能发送内容!',
|
|
type: String
|
|
},
|
|
})
|
|
const { isDisabled, onlyVoice } = toRefs(props)
|
|
const isLoadingState = computed(() => props.isLoading)
|
|
watch(()=>props.isLoading,()=>{},{
|
|
deep:true,immediate:true
|
|
})
|
|
watch(()=>props.canAnswer,()=>{},{
|
|
deep:true,immediate:true
|
|
})
|
|
watch(()=>props.onlyVoice,()=>{},{
|
|
deep:true,immediate:true
|
|
})
|
|
const showTalkingModel = ref(false)
|
|
const keyboardIsShow = ref(false)
|
|
const answerValue = ref('')
|
|
const talkBtnRef = ref()
|
|
// 录音
|
|
const audioPath = ref('')
|
|
const audioObj = uni.createInnerAudioContext()
|
|
//#ifdef APP-PLUS
|
|
// app端
|
|
const recordObjCom = ref({})
|
|
const IsAndEnv = getPhoneEnvBool('AND')
|
|
const initRecord = () => {
|
|
recordObjCom.value = uni.getRecorderManager()
|
|
// startRecord()
|
|
// stopRecord()
|
|
unref(recordObjCom)?.onStop((res, duration)=>{
|
|
audioPath.value = res.tempFilePath
|
|
showTalkingModel.value = false
|
|
if(!isOut.value){
|
|
if(startFlag.value !== 2){
|
|
common.msg('说话时间太短,没有听清!')
|
|
return
|
|
}
|
|
emit('uploadVoice', audioPath.value)
|
|
}
|
|
})
|
|
unref(recordObjCom).onStart(() => {
|
|
startFlag.value = 1
|
|
setTimeout(()=>{
|
|
startFlag.value = 2
|
|
},1000)
|
|
let _recordPerm = uni.getAppAuthorizeSetting()
|
|
let _state = _recordPerm.microphoneAuthorized
|
|
if(_state === 'authorized'){
|
|
console.log('大大大大大');
|
|
showTalkingModel.value = true
|
|
}
|
|
})
|
|
}
|
|
const startFlag = ref(1)
|
|
const startRecord = () => {
|
|
unref(recordObjCom).start({format:'mp3'})
|
|
}
|
|
|
|
const stopRecord = () => {
|
|
if(unref(recordObjCom).stop){
|
|
try{
|
|
unref(recordObjCom)?.stop()
|
|
}catch{
|
|
showTalkingModel.value = false
|
|
}
|
|
}else{
|
|
showTalkingModel.value = false
|
|
}
|
|
}
|
|
// #endif
|
|
//#ifndef APP-PLUS
|
|
const recordObjCom = ref({})
|
|
const initRecord = () => {}
|
|
// #endif
|
|
onMounted(async ()=>{
|
|
initRecord?.()
|
|
//#ifdef APP-PLUS
|
|
// 安卓端
|
|
if(IsAndEnv){
|
|
let _recordPerm = await permission.requestAndroidPermission('android.permission.RECORD_AUDIO')
|
|
if(_recordPerm !== 1){
|
|
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?',true, '取消','确定').then((res) => {
|
|
if(res) {
|
|
// 去开启权限
|
|
permission.gotoAppPermissionSetting()
|
|
}else{
|
|
// 取消
|
|
common.navigateBack()
|
|
}
|
|
})
|
|
}
|
|
}else{
|
|
let _recordPerm = uni.getAppAuthorizeSetting()
|
|
let _state = _recordPerm.microphoneAuthorized
|
|
if(_state === 'denied'){
|
|
common.show('提示信息', '当前页面需要使用的录音权限,是否前往开启?',true, '取消','确定').then((res) => {
|
|
if(res) {
|
|
// 去开启权限
|
|
uni.openAppAuthorizeSetting()
|
|
}else{
|
|
// 取消
|
|
common.navigateBack()
|
|
}
|
|
})
|
|
}else if(_state === 'not determined'){
|
|
// startRecord()
|
|
}
|
|
}
|
|
// #endif
|
|
})
|
|
// 录音结束
|
|
|
|
// 记录 按下移动位置
|
|
const touchX = ref(0)
|
|
const touchY = ref(0)
|
|
const baseY = ref(0)
|
|
const windowInfo = uni.getWindowInfo()
|
|
// 计算是否超出按下盒子位置
|
|
const isOut = computed(() => {
|
|
let _btnHeight = windowInfo.screenWidth/750 * 234
|
|
let moveVal = (windowInfo.screenHeight - touchY.value) > _btnHeight
|
|
return moveVal
|
|
})
|
|
const outScreen = computed(()=>{
|
|
(windowInfo.screenHeight - touchY.value) <= 0
|
|
})
|
|
const recNum = ref(1)
|
|
const activeNum = ref(1)
|
|
|
|
const getRecordPermission = (obj) => {
|
|
showOverlayTalking(obj)
|
|
}
|
|
const isMoving = ref(false)
|
|
const showOverlayTalking = (obj) => {
|
|
if(isLoadingState.value){
|
|
uni.showToast({
|
|
title: props.loadingText,
|
|
icon: "none",
|
|
duration:1000
|
|
})
|
|
return
|
|
}
|
|
if(!props.canAnswer){
|
|
uni.showToast({
|
|
title: props.canAnswerText,
|
|
icon: "none",
|
|
duration:1000
|
|
})
|
|
return
|
|
}
|
|
isMoving.value = true
|
|
emit('startRecord')
|
|
recNum.value += 1
|
|
if(!isDisabled.value){
|
|
touchX.value = obj.changedTouches[0].pageX
|
|
touchY.value = obj.changedTouches[0].pageY
|
|
baseY.value = obj.currentTarget.offsetTop
|
|
//#ifdef APP-PLUS
|
|
startRecord()
|
|
// #endif
|
|
setTimeout(() => {
|
|
hideOverlayTalking()
|
|
},59 * 1010)
|
|
}else{
|
|
showTalkingModel.value = false
|
|
uni.showToast({
|
|
title: '暂无问题需要回答!',
|
|
icon: "none",
|
|
duration:1000
|
|
})
|
|
}
|
|
}
|
|
const hideOverlayTalking = (obj) => {
|
|
isMoving.value = false
|
|
if(!showTalkingModel.value) return
|
|
//#ifdef APP-PLUS
|
|
stopRecord()
|
|
// #endif
|
|
}
|
|
|
|
const handleMove = (obj) => {
|
|
if(isMoving.value){
|
|
touchX.value = obj.changedTouches[0].pageX
|
|
touchY.value = obj.changedTouches[0].pageY
|
|
if(outScreen.value){
|
|
hideOverlayTalking(obj)
|
|
}
|
|
}
|
|
}
|
|
// 发送文本
|
|
const showKeyboard = () => {
|
|
if(isLoadingState.value){
|
|
uni.showToast({
|
|
title: props.loadingText,
|
|
icon: "none",
|
|
duration:1000
|
|
})
|
|
return
|
|
}
|
|
if(!isDisabled.value){
|
|
if(keyboardIsShow.value){
|
|
if(answerValue.value){
|
|
emit(
|
|
'submitAnswer',
|
|
answerValue.value,
|
|
() => {
|
|
keyboardIsShow.value = !keyboardIsShow.value
|
|
answerValue.value = ''
|
|
}
|
|
)
|
|
}else{
|
|
keyboardIsShow.value = !keyboardIsShow.value
|
|
}
|
|
}else{
|
|
keyboardIsShow.value = !keyboardIsShow.value
|
|
}
|
|
}else{
|
|
uni.showToast({
|
|
title: '暂无问题需要回答!',
|
|
icon: "none",
|
|
duration:1000
|
|
})
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.talk-btns{
|
|
height: 150rpx;
|
|
background-color: #fff;
|
|
padding: 25rpx 30rpx 0;
|
|
position: relative;
|
|
.touch-btn{
|
|
height: 92rpx;
|
|
box-shadow: 0 8rpx 20rpx 0 rgba(0,0,0,.06);
|
|
text-align: center;
|
|
line-height: 92rpx;
|
|
color: #000;
|
|
font-weight: 400;
|
|
position: relative;
|
|
.input-box{
|
|
width: 100%;
|
|
height: 92rpx;
|
|
line-height: 92rpx;
|
|
text-align: left;
|
|
box-sizing: border-box;
|
|
padding:0 40rpx 0 40rpx;
|
|
}
|
|
}
|
|
.talk-btn-box{
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 120rpx;
|
|
right: 0;
|
|
top:0;
|
|
.talk-btn-icon{
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
position: absolute;
|
|
right: 60rpx;
|
|
top: 50%;
|
|
margin-top: -20rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.overlay-box{
|
|
display: flex;
|
|
flex-direction: column-reverse;
|
|
// justify-content: flex-end;
|
|
width: 100%;
|
|
height: 100%;
|
|
.circle-bg-box{
|
|
height: 234rpx;
|
|
background: url(@/static/images/course/talking.png) no-repeat;
|
|
background-size: cover;
|
|
position: relative;
|
|
.icon-box{
|
|
position: absolute;
|
|
left:50%;
|
|
top: 50%;
|
|
margin-left: -17rpx;
|
|
margin-top: -24rpx;
|
|
width: 34rpx;
|
|
height: 48rpx;
|
|
image{
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
}
|
|
.tips-box{
|
|
height: 40rpx;
|
|
line-height: 40rpx;
|
|
color:#c0c0c0;
|
|
text-align: center;
|
|
margin: 12rpx 0 24rpx;
|
|
}
|
|
.btns-box{
|
|
height: 270rpx;
|
|
// padding: 0 120rpx;
|
|
width: 154rpx;
|
|
margin: 0 auto 100rpx;
|
|
overflow: hidden;
|
|
font-size: 28rpx;
|
|
text-align: center;
|
|
line-height: 116rpx;
|
|
.close-btn{
|
|
float: left;
|
|
height: 100%;
|
|
width: 154rpx;
|
|
overflow: hidden;
|
|
color:#999;
|
|
&.is-out{
|
|
color:#fff;
|
|
}
|
|
}
|
|
.translate-btn{
|
|
float: right;
|
|
height: 100%;
|
|
width: 154rpx;
|
|
overflow: hidden;
|
|
color:#999;
|
|
}
|
|
.btn-box{
|
|
width: 100%;
|
|
height: 154rpx;
|
|
}
|
|
}
|
|
.talk-dialog{
|
|
width: 506rpx;
|
|
height: 234rpx;
|
|
margin: 0 auto;
|
|
background-color: #06f;
|
|
}
|
|
.white-bg-box{
|
|
height: 365rpx;
|
|
background-color: #fff;
|
|
overflow: hidden;
|
|
border-radius: 32rpx 32rpx 0 0;
|
|
padding: 58rpx 36rpx;
|
|
.box-title{
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
line-height: 46rpx;
|
|
margin-bottom: 46rpx;
|
|
}
|
|
.box-btns{
|
|
height: 46rpx;
|
|
line-height: 46rpx;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: #999;
|
|
.is-active{
|
|
color: #06f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
</style>
|