123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364 |
- <script setup>
- // const plugin = requirePlugin("WechatSI")
- import { ref, unref, onMounted } from 'vue';
- import { baseURL } from '@/utils/https';
- 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 manager = plugin.getRecordRecognitionManager();
- console.log( "manager", manager );
- const innerAudioContext = wx.createInnerAudioContext();
- // 类型 input|voice
- const inpType = ref('input');
- // 提交问题
- const onSubmit = () => {
- const val = unref(modelInpValue);
- if (modelLoading.value) {
- return uni.showToast({ title: '当前对话进行中', duration: 3000, icon: 'none' });
- }
- if (!val) {
- return uni.showToast({ title: '请输入您的问题或需求', duration: 3000, icon: 'none' });
- }
- if (val.length > 2000) {
- return uni.showToast({ title: '问题限制2000个字以内', duration: 3000, icon: 'none' });
- }
- modelInpValue.value = '';
- emit('on-submit', { showVal: val, question: val });
- };
- const onChangeInpType = () => {
- inpType.value = inpType.value === 'input' ? 'voice' : 'input';
- }
- const uploadAudio = (filePath) => {
- uni.showLoading({
- title: '上传中...'
- });
- uni.uploadFile({
- url: 'https://your-server.com/upload',
- filePath: filePath,
- name: 'audio', // 后端接收文件的字段名
- formData: {
- // 可以附加其他表单数据
- 'userId': '123',
- 'timestamp': new Date().getTime()
- },
- success: (uploadRes) => {
- uni.hideLoading();
- console.log('上传成功', uploadRes);
- // 处理服务器返回的数据
- const data = JSON.parse(uploadRes.data);
- uni.showToast({
- title: '上传成功',
- icon: 'success'
- });
- },
- fail: (error) => {
- uni.hideLoading();
- console.log('上传失败', error);
- uni.showToast({
- title: '上传失败',
- icon: 'none'
- });
- }
- });
- }
- 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;
- uni.authorize({
- scope: 'scope.record',
- success() {
- recorderManager.onStart(() => {
- console.log('录音开始');
- });
- recorderManager.onPause(() => {
- console.log('录音暂停');
- });
- recorderManager.onStop((res) => {
- 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); // 调整音量显示比例
- }
- });
- },
- fail() {
- uni.showToast({ title: '用户拒绝授权录音权限', icon: 'none' });
- },
- });
- }
- 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 class="la-line-scale-pulse-out">
- <view></view>
- <view></view>
- <view></view>
- <view></view>
- <view></view>
- </view>
- </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" @click="onChangeInpType">
- <TheSvgIcon class="icon" src="icon-voice" size="42"></TheSvgIcon>
- </view>
- <view class="inp-inner">
- <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" v-for="item in 5" :key="item"></view>
- </view>
- </view>
- </view>
- </view>
- </template>
- <style lang="scss" scoped>
- .chat-inp-container {
- padding: 0 24rpx;
- box-sizing: content-box;
- .chat-inp-inner {
- display: flex;
- align-items: flex-end;
- padding: 26rpx 28rpx;
- border-radius: 32rpx;
- background: #FFF;
- box-shadow: 0px 8rpx 16rpx 2rpx rgba(172, 200, 224, 0.20);
- .voice-btn {
- display: flex;
- align-items: center;
- justify-content: flex-end;
- height: 56rpx;
- }
- .inp-inner {
- width: 100%;
- min-height: 56rpx;
- display: flex;
- align-items: center;
- // padding-bottom: 10rpx;
- .chat-inp {
- width: 100%;
- max-height: 200rpx;
- 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 {
- flex-shrink: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 56rpx;
- height: 56rpx;
- border-radius: 100%;
- background: #212121;
- .icon {
- transition: all 0.3s ease-in-out;
- }
- }
- }
- }
- .voice-wrapper {
- position: absolute;
- 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%;
- .voice-card {
- flex: 1;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100vw;
- .voice-tip {
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 10rpx 20rpx;
- width: 100px;
- border-radius: 10rpx;
- background: #00aa5b;
- }
- }
- .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>
|