|
@@ -1,10 +1,23 @@
|
|
|
<script setup>
|
|
|
-import { unref } from 'vue';
|
|
|
+import { ref, unref, onMounted } from 'vue';
|
|
|
|
|
|
const modelInpValue = defineModel();
|
|
|
const emit = defineEmits(['on-submit']);
|
|
|
const modelLoading = defineModel('loading');
|
|
|
|
|
|
+const isRecording = ref(false);
|
|
|
+const recordingTip = ref('松开 发送');
|
|
|
+const showVolume = ref(false);
|
|
|
+const volumeLevel = ref(50); // 模拟音量级别
|
|
|
+let recorderManager = null;
|
|
|
+const audioPath = ref(null);
|
|
|
+
|
|
|
+
|
|
|
+const innerAudioContext = wx.createInnerAudioContext();
|
|
|
+
|
|
|
+// 类型 input|voice
|
|
|
+const inpType = ref('input');
|
|
|
+
|
|
|
// 提交问题
|
|
|
const onSubmit = () => {
|
|
|
const val = unref(modelInpValue);
|
|
@@ -25,27 +38,152 @@ const onSubmit = () => {
|
|
|
|
|
|
emit('on-submit', { showVal: val, question: val });
|
|
|
};
|
|
|
+
|
|
|
+const onChangeInpType = () => {
|
|
|
+ inpType.value = inpType.value === 'input' ? 'voice' : 'input';
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const playRecording = () => {
|
|
|
+
|
|
|
+ if (innerAudioContext) {
|
|
|
+ innerAudioContext.src = audioPath.value;
|
|
|
+ innerAudioContext.play();
|
|
|
+
|
|
|
+ innerAudioContext.onPlay(() => {
|
|
|
+ console.log('开始播放');
|
|
|
+ });
|
|
|
+
|
|
|
+ innerAudioContext.onError((err) => {
|
|
|
+ console.error('播放错误:', err);
|
|
|
+ uni.showToast({ title: '播放失败', icon: 'none' });
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 语音播放相关
|
|
|
+const startRecording = e => {
|
|
|
+ isRecording.value = true;
|
|
|
+ showVolume.value = true;
|
|
|
+
|
|
|
+ recorderManager.start({
|
|
|
+ format: 'mp3',
|
|
|
+ duration: 60000, // 最长1分钟
|
|
|
+ sampleRate: 44100,
|
|
|
+ numberOfChannels: 1,
|
|
|
+ encodeBitRate: 192000,
|
|
|
+ frameSize: 50, // 帧大小,影响onFrameRecorded回调频率
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function setupRecorderEvents() {
|
|
|
+ if (!recorderManager) return;
|
|
|
+
|
|
|
+ recorderManager.onStart(() => {
|
|
|
+ console.log('录音开始');
|
|
|
+ });
|
|
|
+
|
|
|
+ recorderManager.onPause(() => {
|
|
|
+ console.log('录音暂停');
|
|
|
+ });
|
|
|
+
|
|
|
+ recorderManager.onStop((res) => {
|
|
|
+ // audioPath.value = 'https://web-ext-storage.dcloud.net.cn/uni-app/ForElise.mp3';
|
|
|
+
|
|
|
+ audioPath.value = res.tempFilePath;
|
|
|
+
|
|
|
+ // setTimeout(() => {
|
|
|
+ // innerAudioContext.src = 'https://dlink.host/musics/aHR0cHM6Ly9vbmVkcnYtbXkuc2hhcmVwb2ludC5jb20vOnU6L2cvcGVyc29uYWwvc3Rvcl9vbmVkcnZfb25taWNyb3NvZnRfY29tL0VjYzBzQUxiWFk5TWdHQl9GUVNkV2pJQm5wRmM0MktDZWpURnhhMjhELUdXeVE.mp3';
|
|
|
+ // innerAudioContext.play();
|
|
|
+ // }, 1000)
|
|
|
+
|
|
|
+ console.log('录音停止', res);
|
|
|
+ // 这里可以处理录音结果,如上传或播放
|
|
|
+ });
|
|
|
+ recorderManager.onFrameRecorded((res) => {
|
|
|
+ // 可以在这里获取实时音量等信息
|
|
|
+ const volume = res.volumn; // 注意微信可能是volumn而不是volume
|
|
|
+ if (volume !== undefined) {
|
|
|
+ volumeLevel.value = Math.min(100, volume * 200); // 调整音量显示比例
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+function stopRecording() {
|
|
|
+ if (!isRecording.value) return;
|
|
|
+
|
|
|
+ isRecording.value = false;
|
|
|
+ showVolume.value = false;
|
|
|
+
|
|
|
+ recorderManager.stop();
|
|
|
+
|
|
|
+ // 停止音量检测
|
|
|
+ // stopVolumeDetection();
|
|
|
+}
|
|
|
+
|
|
|
+// 初始化录音管理器
|
|
|
+onMounted(() => {
|
|
|
+ recorderManager = wx.getRecorderManager();
|
|
|
+
|
|
|
+ setupRecorderEvents();
|
|
|
+});
|
|
|
</script>
|
|
|
|
|
|
<template>
|
|
|
+ <view class="voice-wrapper" v-show="isRecording">
|
|
|
+ <view class="voice-inner">
|
|
|
+ <view class="voice-card">
|
|
|
+ <view class="voice-tip">
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="vocie-btn_wrapper"
|
|
|
+ @touchstart="startRecording"
|
|
|
+ @touchend="stopRecording"
|
|
|
+ @touchcancel="stopRecording"
|
|
|
+ >
|
|
|
+ <view class="tips">{{ recordingTip }}</view>
|
|
|
+ <view class="btn">
|
|
|
+ <uni-icons type="sound" size="20" color="#777"></uni-icons>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<view class="chat-inp-container">
|
|
|
+ <!-- <view @click="playRecording">按钮</view> -->
|
|
|
<view class="chat-inp-inner">
|
|
|
- <view class="voice-btn">
|
|
|
+ <view class="voice-btn" @click="onChangeInpType">
|
|
|
<TheSvgIcon class="icon" src="icon-voice" size="42"></TheSvgIcon>
|
|
|
</view>
|
|
|
<view class="inp-inner">
|
|
|
- <textarea v-model.trim="modelInpValue" auto-height :maxlength="2000" class="chat-inp" placeholder="输入您的问题或需求"
|
|
|
- placeholder-style="color:#9A9A9A">
|
|
|
+ <textarea
|
|
|
+ v-model.trim="modelInpValue"
|
|
|
+ :show-confirm-bar="false"
|
|
|
+ :cursor-spacing="30"
|
|
|
+ auto-height
|
|
|
+ :maxlength="2000"
|
|
|
+ class="chat-inp"
|
|
|
+ placeholder="输入您的问题或需求"
|
|
|
+ placeholder-style="color:#9A9A9A"
|
|
|
+ v-show="inpType == 'input'"
|
|
|
+ >
|
|
|
</textarea>
|
|
|
+
|
|
|
+ <view
|
|
|
+ class="chat-voice"
|
|
|
+ v-show="inpType == 'voice'"
|
|
|
+ @touchstart="startRecording"
|
|
|
+ @touchend="stopRecording"
|
|
|
+ @touchcancel="stopRecording"
|
|
|
+ >
|
|
|
+ <text>按住 说话</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
<view class="send-btn" @click="onSubmit">
|
|
|
<TheSvgIcon class="icon" src="icon-send-plane" size="30" v-show="!modelLoading"></TheSvgIcon>
|
|
|
<view class="la-ball-running-dots la-sm" v-show="modelLoading">
|
|
|
- <view class="item"></view>
|
|
|
- <view class="item"></view>
|
|
|
- <view class="item"></view>
|
|
|
- <view class="item"></view>
|
|
|
- <view class="item"></view>
|
|
|
+ <view class="item" v-for="item in 5" :key="item"></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
@@ -59,8 +197,8 @@ const onSubmit = () => {
|
|
|
|
|
|
.chat-inp-inner {
|
|
|
display: flex;
|
|
|
- padding: 26rpx 28rpx;
|
|
|
align-items: flex-end;
|
|
|
+ padding: 26rpx 28rpx;
|
|
|
border-radius: 32rpx;
|
|
|
background: #FFF;
|
|
|
box-shadow: 0px 8rpx 16rpx 2rpx rgba(172, 200, 224, 0.20);
|
|
@@ -68,26 +206,32 @@ const onSubmit = () => {
|
|
|
.voice-btn {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
- justify-content: center;
|
|
|
+ justify-content: flex-end;
|
|
|
height: 56rpx;
|
|
|
}
|
|
|
|
|
|
.inp-inner {
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
- margin-bottom: 8rpx;
|
|
|
+ min-height: 56rpx;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ // padding-bottom: 10rpx;
|
|
|
|
|
|
.chat-inp {
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
max-height: 200rpx;
|
|
|
- width: 100%;
|
|
|
- height: 32rpx;
|
|
|
+ height: 100%;
|
|
|
padding: 0 16rpx;
|
|
|
font-size: 28rpx;
|
|
|
color: #333;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
+
|
|
|
+ .chat-voice {
|
|
|
+ width: 100%;
|
|
|
+ font-size: 28rpx;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
.send-btn {
|
|
@@ -106,299 +250,65 @@ const onSubmit = () => {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-.la-ball-running-dots,
|
|
|
-.la-ball-running-dots>.item {
|
|
|
- position: relative;
|
|
|
- -webkit-box-sizing: border-box;
|
|
|
- -moz-box-sizing: border-box;
|
|
|
- box-sizing: border-box;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots {
|
|
|
- // display: block;
|
|
|
- font-size: 0;
|
|
|
- color: #fff;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-dark {
|
|
|
- color: #333;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item {
|
|
|
- display: inline-block;
|
|
|
- float: none;
|
|
|
- background-color: currentColor;
|
|
|
- border: 0 solid currentColor;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots {
|
|
|
- width: 10px;
|
|
|
- height: 10px;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item {
|
|
|
+.voice-wrapper {
|
|
|
position: absolute;
|
|
|
- width: 20rpx;
|
|
|
- height: 20rpx;
|
|
|
- margin-left: -25px;
|
|
|
- border-radius: 100%;
|
|
|
- -webkit-animation: ball-running-dots-animate 2s linear infinite;
|
|
|
- -moz-animation: ball-running-dots-animate 2s linear infinite;
|
|
|
- -o-animation: ball-running-dots-animate 2s linear infinite;
|
|
|
- animation: ball-running-dots-animate 2s linear infinite;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(1) {
|
|
|
- -webkit-animation-delay: 0s;
|
|
|
- -moz-animation-delay: 0s;
|
|
|
- -o-animation-delay: 0s;
|
|
|
- animation-delay: 0s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(2) {
|
|
|
- -webkit-animation-delay: -.4s;
|
|
|
- -moz-animation-delay: -.4s;
|
|
|
- -o-animation-delay: -.4s;
|
|
|
- animation-delay: -.4s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(3) {
|
|
|
- -webkit-animation-delay: -.8s;
|
|
|
- -moz-animation-delay: -.8s;
|
|
|
- -o-animation-delay: -.8s;
|
|
|
- animation-delay: -.8s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(4) {
|
|
|
- -webkit-animation-delay: -1.2s;
|
|
|
- -moz-animation-delay: -1.2s;
|
|
|
- -o-animation-delay: -1.2s;
|
|
|
- animation-delay: -1.2s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(5) {
|
|
|
- -webkit-animation-delay: -1.6s;
|
|
|
- -moz-animation-delay: -1.6s;
|
|
|
- -o-animation-delay: -1.6s;
|
|
|
- animation-delay: -1.6s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(6) {
|
|
|
- -webkit-animation-delay: -2s;
|
|
|
- -moz-animation-delay: -2s;
|
|
|
- -o-animation-delay: -2s;
|
|
|
- animation-delay: -2s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(7) {
|
|
|
- -webkit-animation-delay: -2.4s;
|
|
|
- -moz-animation-delay: -2.4s;
|
|
|
- -o-animation-delay: -2.4s;
|
|
|
- animation-delay: -2.4s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(8) {
|
|
|
- -webkit-animation-delay: -2.8s;
|
|
|
- -moz-animation-delay: -2.8s;
|
|
|
- -o-animation-delay: -2.8s;
|
|
|
- animation-delay: -2.8s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(9) {
|
|
|
- -webkit-animation-delay: -3.2s;
|
|
|
- -moz-animation-delay: -3.2s;
|
|
|
- -o-animation-delay: -3.2s;
|
|
|
- animation-delay: -3.2s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots>.item:nth-child(10) {
|
|
|
- -webkit-animation-delay: -3.6s;
|
|
|
- -moz-animation-delay: -3.6s;
|
|
|
- -o-animation-delay: -3.6s;
|
|
|
- animation-delay: -3.6s;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-sm {
|
|
|
- width: 8rpx;
|
|
|
- height: 8rpx;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-sm>.item {
|
|
|
- width: 8rpx;
|
|
|
- height: 8rpx;
|
|
|
- margin-left: -12px;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-2x {
|
|
|
- width: 20px;
|
|
|
- height: 20px;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-2x>.item {
|
|
|
- width: 20px;
|
|
|
- height: 20px;
|
|
|
- margin-left: -50px;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-3x {
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
-}
|
|
|
-
|
|
|
-.la-ball-running-dots.la-3x>.item {
|
|
|
- width: 30px;
|
|
|
- height: 30px;
|
|
|
- margin-left: -75px;
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
- * Animation
|
|
|
- */
|
|
|
-@-webkit-keyframes ball-running-dots-animate {
|
|
|
-
|
|
|
- 0%,
|
|
|
- 100% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -webkit-transform: translateY(0) translateX(500%);
|
|
|
- transform: translateY(0) translateX(500%);
|
|
|
- }
|
|
|
-
|
|
|
- 80% {
|
|
|
- -webkit-transform: translateY(0) translateX(0);
|
|
|
- transform: translateY(0) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 85% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -webkit-transform: translateY(-125%) translateX(0);
|
|
|
- transform: translateY(-125%) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 90% {
|
|
|
- width: 200%;
|
|
|
- height: 75%;
|
|
|
- }
|
|
|
-
|
|
|
- 95% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -webkit-transform: translateY(-100%) translateX(500%);
|
|
|
- transform: translateY(-100%) translateX(500%);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@-moz-keyframes ball-running-dots-animate {
|
|
|
-
|
|
|
- 0%,
|
|
|
- 100% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -moz-transform: translateY(0) translateX(500%);
|
|
|
- transform: translateY(0) translateX(500%);
|
|
|
- }
|
|
|
-
|
|
|
- 80% {
|
|
|
- -moz-transform: translateY(0) translateX(0);
|
|
|
- transform: translateY(0) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 85% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -moz-transform: translateY(-125%) translateX(0);
|
|
|
- transform: translateY(-125%) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 90% {
|
|
|
- width: 200%;
|
|
|
- height: 75%;
|
|
|
- }
|
|
|
-
|
|
|
- 95% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -moz-transform: translateY(-100%) translateX(500%);
|
|
|
- transform: translateY(-100%) translateX(500%);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@-o-keyframes ball-running-dots-animate {
|
|
|
-
|
|
|
- 0%,
|
|
|
- 100% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -o-transform: translateY(0) translateX(500%);
|
|
|
- transform: translateY(0) translateX(500%);
|
|
|
- }
|
|
|
-
|
|
|
- 80% {
|
|
|
- -o-transform: translateY(0) translateX(0);
|
|
|
- transform: translateY(0) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 85% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -o-transform: translateY(-125%) translateX(0);
|
|
|
- transform: translateY(-125%) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 90% {
|
|
|
- width: 200%;
|
|
|
- height: 75%;
|
|
|
- }
|
|
|
-
|
|
|
- 95% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -o-transform: translateY(-100%) translateX(500%);
|
|
|
- transform: translateY(-100%) translateX(500%);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-@keyframes ball-running-dots-animate {
|
|
|
-
|
|
|
- 0%,
|
|
|
- 100% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -webkit-transform: translateY(0) translateX(500%);
|
|
|
- -moz-transform: translateY(0) translateX(500%);
|
|
|
- -o-transform: translateY(0) translateX(500%);
|
|
|
- transform: translateY(0) translateX(500%);
|
|
|
- }
|
|
|
-
|
|
|
- 80% {
|
|
|
- -webkit-transform: translateY(0) translateX(0);
|
|
|
- -moz-transform: translateY(0) translateX(0);
|
|
|
- -o-transform: translateY(0) translateX(0);
|
|
|
- transform: translateY(0) translateX(0);
|
|
|
- }
|
|
|
-
|
|
|
- 85% {
|
|
|
- width: 100%;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ width: 100vh;
|
|
|
+ height: 100vh;
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
+ z-index: 10;
|
|
|
+
|
|
|
+ .voice-inner {
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-flow: column;
|
|
|
height: 100%;
|
|
|
- -webkit-transform: translateY(-125%) translateX(0);
|
|
|
- -moz-transform: translateY(-125%) translateX(0);
|
|
|
- -o-transform: translateY(-125%) translateX(0);
|
|
|
- transform: translateY(-125%) translateX(0);
|
|
|
- }
|
|
|
|
|
|
- 90% {
|
|
|
- width: 200%;
|
|
|
- height: 75%;
|
|
|
- }
|
|
|
-
|
|
|
- 95% {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- -webkit-transform: translateY(-100%) translateX(500%);
|
|
|
- -moz-transform: translateY(-100%) translateX(500%);
|
|
|
- -o-transform: translateY(-100%) translateX(500%);
|
|
|
- transform: translateY(-100%) translateX(500%);
|
|
|
+ .voice-card {
|
|
|
+ flex:1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100vw;
|
|
|
+ .voice-tip {
|
|
|
+ width: 100px;
|
|
|
+ height: 30px;
|
|
|
+ background: red;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .vocie-btn_wrapper {
|
|
|
+ position: relative;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-flow: column;
|
|
|
+ flex-shrink: 0;
|
|
|
+ height: 200rpx;
|
|
|
+ width: 100vw;
|
|
|
+ overflow: hidden;
|
|
|
+ .tips {
|
|
|
+ padding-bottom: 10rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 24rpx;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+ .btn {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ width: 100vw;
|
|
|
+ height: 120rpx;
|
|
|
+ border-top-left-radius: 50%;
|
|
|
+ border-top-right-radius: 50%;
|
|
|
+ border-top: 1px solid #fff;
|
|
|
+ background: #b5b5b5;
|
|
|
+ padding-bottom: env(safe-area-inset-bottom);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
</style>
|