Files
tra-app/src/pages/course/components/touchBtn.vue
T
2025-07-21 18:02:59 +08:00

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>