merchapp/java-mall-app-shop-admin/pages/IM/voicePopup.vue
2025-05-22 16:51:36 +08:00

381 lines
8.6 KiB
Vue

<template>
<view class="voice-mask" v-show="showVoiceMask">
<!--语音条 -->
<view
class="voice-bar voice-del"
:class="{ voiceDel: needCancel }"
:style="{ width: getVoiceBarWidth }"
>
<image
src="../../static/images/chat/yinping.png"
class="voice-volume"
></image>
<view class="trangle-bottom" :class="{ trangleDel: needCancel }"></view>
</view>
<view class="voice-send">
<!-- 取消和转文字图标 -->
<view class="voice-middle-wrapper">
<!-- 取消 -->
<view class="voice-left-wrapper">
<view class="cancel-del" :class="{ delTip: needCancel }">
松开 取消
</view>
<view
class="voice-middle-inner close"
:class="{ bigger: needCancel }"
>
<u-icon name="close" color="#fff" size="24"></u-icon>
</view>
</view>
<!-- 转文字 -->
<view class="send-tip" :class="{ sendTipNone: needCancel }">
松开 发送
</view>
</view>
<!-- 底部语音按钮 -->
<view
ref="maskBottom"
class="mask-bottom"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
<image src="../../static/images/chat/voice.png"></image>
</view>
</view>
</view>
</template>
<script>
const recorderManager = uni.getRecorderManager();
export default {
props: {
showVoiceMask: {
type: Boolean,
default: false,
},
voicePopupTouches: {
type: Object,
default: {},
},
},
data() {
return {
voicelength: 0,
needCancel: false,
startX: 0,
startY: 0,
timer: 0,
maskBottomRect: null,
isRecording: false,
isOutside: false,
tempFilePath: null,
startTime: 0,
isOutside: false,
};
},
computed: {
// 计算语音条宽度
getVoiceBarWidth() {
return 230 + this.voicelength * 4 + "rpx";
},
},
watch: {
showVoiceMask: {
handler: function (val, oldVal) {
if (val) {
console.log("showVoicePopupTouches", this.voicePopupTouches);
console.log(this.$refs["maskBottom"].$el.dispatchEvent);
const mockEvent = new TouchEvent("touchstart", {
touches: [{ pageX: 100, pageY: 100 }], // 模拟触摸坐标
});
this.$refs["maskBottom"].$el.dispatchEvent(mockEvent);
this.handleTouchStart(this.voicePopupTouches);
}
},
},
},
created() {
// 初始化录音管理器
recorderManager.onStart(() => {
console.log("录音开始");
this.isRecording = true;
this.startTime = Date.now();
});
recorderManager.onPause(() => {
console.log("录音暂停");
this.isRecording = false;
});
recorderManager.onResume(() => {
console.log("录音继续");
this.isRecording = true;
});
},
methods: {
async getMaskBottomRect() {
return new Promise((resolve) => {
const query = uni.createSelectorQuery().in(this);
query
.select(".mask-bottom")
.boundingClientRect((data) => {
this.maskBottomRect = data;
console.log("元素位置信息:", data); // 调试用
resolve(data);
})
.exec();
});
},
async handleTouchStart(e) {
console.log("handleTouchStart", e);
if (!this.maskBottomRect) {
await this.getMaskBottomRect();
}
recorderManager.start();
this.length = 1;
this.startX = e.touches[0].pageX;
this.startY = e.touches[0].pageY;
this.timer = setInterval(() => {
this.length += 1;
if (this.length >= 60) {
clearInterval(this.timer);
this.handleTouchEnd();
}
}, 1000);
this.isRecording = true;
this.isOutside = false;
},
handleTouchMove(e) {
// 检查是否在mask-bottom区域外
if (!this.maskBottomRect) return;
const touchX = e.touches[0].pageX;
const touchY = e.touches[0].pageY;
// 检查是否在mask-bottom区域外
// 更精确的边界检查
const SAFE_MARGIN = 5; // 像素单位
const isNowOutside =
touchX < this.maskBottomRect.left - SAFE_MARGIN ||
touchX >
this.maskBottomRect.left + this.maskBottomRect.width + SAFE_MARGIN ||
touchY < this.maskBottomRect.top - SAFE_MARGIN ||
touchY >
this.maskBottomRect.top + this.maskBottomRect.height + SAFE_MARGIN;
// 调试输出
console.log(
`触摸点: (${touchX}, ${touchY})`,
`元素区域: [${this.maskBottomRect.left}, ${this.maskBottomRect.top}, ${this.maskBottomRect.width}, ${this.maskBottomRect.height}]`,
`是否在外: ${isNowOutside}`
);
// 如果状态发生变化(进入或离开区域)
if (isNowOutside !== this.isOutside) {
this.isOutside = isNowOutside;
if (this.isOutside) {
// 离开区域,暂停录制
recorderManager.pause();
console.log("已离开区域,暂停录制");
} else {
// 回到区域,继续录制
recorderManager.resume();
console.log("已回到区域,继续录制");
}
}
// 原有的取消逻辑
if (this.startX - touchX > 14 && this.startY - touchY > 50) {
this.needCancel = true;
} else {
this.needCancel = false;
}
},
handleTouchEnd(e) {
clearInterval(this.timer);
recorderManager.stop();
recorderManager.onStop((res) => {
const message = {
voice: res.tempFilePath,
length: this.length,
};
if (!this.needCancel) {
this.inputSubmit(message, 2);
} else {
this.inputSubmit();
}
this.needCancel = false;
});
},
inputSubmit(msg, types) {
if (msg && types) {
this.$emit("voiceEnd", msg, types);
} else {
this.$emit("voiceEnd");
}
},
},
};
</script>
<style lang="scss">
.voice-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.8);
z-index: 9999; /* 确保足够高 */
display: flex;
justify-content: center;
align-items: center;
}
.voice-bar {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -30%);
height: 150rpx;
background-color: #51ff50;
border-radius: 26rpx;
margin-bottom: 220rpx;
z-index: 9999;
}
.voiceDel {
left: 80rpx;
top: 50%;
width: 170rpx !important;
transform: translateX(0%);
transform: translateY(-30%);
background-color: red;
}
.voice-volume {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 160rpx;
height: 36rpx;
}
.volumeDel {
width: 80rpx;
}
.trangle-bottom {
position: absolute;
bottom: -38rpx;
left: 50%;
transform: translateX(-50%);
border-width: 20rpx;
border-style: solid;
border-color: #51ff50 transparent transparent transparent;
z-index: 9999;
}
.trangleDel {
border-color: red transparent transparent transparent;
}
.voice-send {
position: relative;
bottom: 0;
width: 100%;
height: 100%;
z-index: 9999;
}
.voice-middle-wrapper {
width: 100%;
display: flex;
position: absolute;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 40rpx;
bottom: 15%;
}
.voice-left-wrapper {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
}
.cancel-del {
display: none;
}
.delTip {
display: block;
color: #bfbfbf;
margin: 0 22rpx 18rpx 0;
}
.voice-middle-inner {
display: flex;
justify-content: center;
align-items: center;
background-color: #595959;
width: 140rpx;
height: 140rpx;
border-radius: 50%;
}
.close {
transform: rotate(350deg);
margin-left: 80rpx;
}
.bigger {
width: 170rpx;
height: 170rpx;
background: #ececec;
}
.to-text {
transform: rotate(10deg);
margin-right: 80rpx;
}
.close-icon {
width: 80rpx;
height: 80rpx;
color: #fff;
}
.wen {
font-size: 40rpx;
color: #bfbfbf;
}
.send-tip {
position: absolute;
left: 50%;
bottom: 0rpx;
transform: translate(-50%, 36%);
color: #bfbfbf;
}
.sendTipNone {
display: none;
}
.mask-bottom {
position: absolute;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 180rpx;
border-top: #bababb 8rpx solid;
border-radius: 50% 50% 0 0;
background-image: linear-gradient(#949794, #e1e3e1);
z-index: 9999;
}
.mask-bottom image {
position: absolute;
width: 60rpx;
height: 60rpx;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
</style>