2025-09-29

聊天图片上传
录音修改
This commit is contained in:
2025-09-29 14:42:41 +08:00
parent 08c54797b6
commit 22c4b5d4d5
5 changed files with 231 additions and 103 deletions

View File

@@ -18,7 +18,9 @@
}, },
/* */ /* */
"modules" : { "modules" : {
"Payment" : {} "Payment" : {},
"Record" : {},
"Camera" : {}
}, },
/* */ /* */
"distribute" : { "distribute" : {

View File

@@ -27,51 +27,59 @@
<image src="/static/icon/Camera.png" mode="" style="width: 100%;height: 100%;"></image> <image src="/static/icon/Camera.png" mode="" style="width: 100%;height: 100%;"></image>
</view> </view>
<!-- 文本发送 --> <!-- 文本发送 -->
<view v-show="text!=null && text!=''" class="icon u-m-r-10 u-m-l-10" @click="handleSend"> <view v-show="text!=null && text!=''" class="icon u-m-r-10 u-m-l-10" @click="handleSendText">
<image src="/static/icon/send.png" mode="" style="width: 100%;height: 100%;"></image> <image src="/static/icon/send.png" mode="" style="width: 100%;height: 100%;"></image>
</view> </view>
</view> </view>
<scroll-view :style="'height:'+scrollHeight+'px'" scroll-y="true" class="scroll-main" :scroll-top="scrollTop"
<scroll-view :style="'height:'+scrollHeight+'px'" scroll-y="true" class="scroll-main"> :scroll-with-animation="true">
<view v-for="(item, index) in dataList"> <view id="scroll-main">
<view v-if="item.createId==userId" class="my-message"> <view v-for="(item, index) in dataList">
<view class="msg-main"> <view v-if="item.createId==userId" class="my-message">
<view class="msg-content" v-if="item.type=='text'"> <view class="msg-main">
<view class="text"> <view class="msg-content" v-if="item.type=='text'">
{{item.content}} <view class="text">
</view> {{item.content}}
</view>
<view class="mp3-content" v-if="item.type=='mp3'">
<view class="mp3">
<view class="mp3-icon">
<image style="width: 100%;height: 100%;" src="/static/icon/listen.png" mode="">
</image>
</view> </view>
点击播放
</view> </view>
</view> <view class="mp3-content" v-if="item.type=='mp3'">
<view class="msg-avatar"> <view class="mp3" @click="handlePlayMp3(item.content)">
<image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)" mode=""> <view class="mp3-icon">
</image> <image style="width: 100%;height: 100%;" src="/static/icon/listen.png" mode="">
</image>
</view>
点击播放
</view>
</view>
<view class="img-content" v-if="item.type=='img'">
<u-image @click="handlePreView(item.content)" height="100%" :src="item.content"
:lazy-load="true" mode="heightFix"></u-image>
</view>
<!-- 头像 -->
<view class="msg-avatar">
<image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)"
mode="">
</image>
</view>
</view> </view>
</view> </view>
</view> <view v-else class="other-message">
<view v-else class="other-message"> <view class="msg-main">
<view class="msg-main"> <view class="msg-avatar">
<view class="msg-avatar"> <image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)"
<image style="width: 100%;height: 100%;" :src="getImageUrl(item.userInfo.avatar)" mode=""> mode="">
</image> </image>
</view> </view>
<view class="msg-content"> <view class="msg-content">
<view class="text"> <view class="text">
{{item.content}} {{item.content}}
</view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
<u-popup v-model="showLongText" mode="bottom" height="80%"> <u-popup v-model="showLongText" mode="bottom" height="80%">
<view class="long-text"> <view class="long-text">
<u-input v-model="text" type="textarea" maxlength="3000" /> <u-input v-model="text" type="textarea" maxlength="3000" />
@@ -82,7 +90,14 @@
<u-mask :show="showMask" @click="handleStopMic" blur="10"> <u-mask :show="showMask" @click="handleStopMic" blur="10">
<view class="mask-warp"> <view class="mask-warp">
<view class="mask-text"> <view class="mask-text">
{{!showMaskBtn?'正在说话...点击结束': '点击播放录音'}} <template v-if="!showMaskBtn">
正在说话...点击结束
</template>
<template v-else>
<u-image :fade="false" width="50" height="50"
:src="isPlayRecord?'/static/icon/record_ing.png':'/static/icon/record_play.png'"
:show-loading="false"></u-image>{{isPlayRecord?'播放中...':'点击播放录音'}}
</template>
</view> </view>
<view v-if="showMaskBtn" class="mask-btn"> <view v-if="showMaskBtn" class="mask-btn">
<u-button class="btn" @click.stop="handleCancel">取消</u-button> <u-button class="btn" @click.stop="handleCancel">取消</u-button>
@@ -91,13 +106,18 @@
</view> </view>
</u-mask> </u-mask>
<u-mask :show="showPreMask" @click="showPreMask=false" blur="10">
<u-image width="100%" class="mask-pre-img" :src="preImg" :lazy-load="true" mode="aspectFit"></u-image>
</u-mask>
<u-toast ref="msgToast" duration="6000" /> <u-toast ref="msgToast" duration="6000" />
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, getCurrentInstance, onBeforeMount } from 'vue'; import { onMounted, ref, getCurrentInstance, onBeforeMount, nextTick } from 'vue';
import { import {
onLoad, onLaunch onLoad, onLaunch
} from "@dcloudio/uni-app"; } from "@dcloudio/uni-app";
@@ -105,16 +125,27 @@
import { getImageUrl } from '../../util/common'; import { getImageUrl } from '../../util/common';
import { permissionUtil } from '@/uni_modules/colorful-uni-perm'; import { permissionUtil } from '@/uni_modules/colorful-uni-perm';
const text = ref('')
const socket = ref()
const navBarRef = ref() const navBarRef = ref()
const instance = getCurrentInstance();
const scrollHeight = ref(0)
const loadHeight = () => {
uni.getSystemInfo({
success(res) {
let screenHeight = res.screenHeight
uni.createSelectorQuery().in(instance.proxy).select("#navBarId").boundingClientRect((data : any) => {
scrollHeight.value = screenHeight - data.height
}).exec()
uni.createSelectorQuery().in(instance.proxy).select("#bottomId").boundingClientRect((data : any) => {
scrollHeight.value = scrollHeight.value - data.height
}).exec()
}
})
}
const dataList = ref([]) const dataList = ref([])
const mockData = () => { const mockData = () => {
for (var i = 0; i < 3; i++) { for (var i = 0; i < 5; i++) {
if (i % 2 == 0) { if (i % 2 == 0) {
dataList.value.push({ dataList.value.push({
"createId": 3641, "createId": 3641,
@@ -165,19 +196,47 @@
"avatar": "/uploads/documents/1753833656669_524106424.jpg" "avatar": "/uploads/documents/1753833656669_524106424.jpg"
} }
}) })
dataList.value.push(
{
"createId": 3641,
"groupId": "5",
"content": "http://114.55.111.44:9000/jurongquan/app_records/1759110192420_Screenshot_20250929_084636_com.bilibili.star.bili.jpg",
"type": "img",
"messageId": 62,
"createTime": "2025-09-28T17:43:13.000Z",
"userInfo": {
"id": 3641,
"username": "15867461617",
"userType": "agent",
"isSystemAccount": 0,
"avatar": "/uploads/documents/1753833656669_524106424.jpg"
}
})
} }
} }
const handleSend = () => { // 发送消息
const text = ref('')
const showLongText = ref(false)
const handleSendText = () => {
uni.hideKeyboard()
socket.value.emit('clientMsg', { socket.value.emit('clientMsg', {
createId: userId.value, createId: userId.value,
groupId: groupId.value, groupId: groupId.value,
content: text.value, content: text.value,
type: "text" type: "text"
}) })
text.value = ''
} }
const showLongText = ref(false) // 文本放大
const handleMagnify = () => {
showLongText.value = true
}
// 录音
const showMic = ref(false) const showMic = ref(false)
const showMask = ref(false) const showMask = ref(false)
const showMaskBtn = ref(false) const showMaskBtn = ref(false)
@@ -186,9 +245,15 @@
const voicePath = ref('') const voicePath = ref('')
const recorderManager = uni.getRecorderManager(); const recorderManager = uni.getRecorderManager(); // 录音管理器
const innerAudioContext = uni.createInnerAudioContext(); const innerAudioContext = uni.createInnerAudioContext(); // 音频管理器
innerAudioContext.autoplay = true; const options = {
duration: 60000, // 录音时长,单位为毫秒
sampleRate: 44100, // 采样率
numberOfChannels: 2, // 通道数
format: 'mp3', // 音频格式
};
const isPlayRecord = ref(false)
// 选择麦克风 // 选择麦克风
const handleChangeMic = () => { const handleChangeMic = () => {
@@ -197,35 +262,43 @@
// 开始录音 // 开始录音
const handleSay = () => { const handleSay = () => {
permissionUtil.requestAndroidPermission('android.permission.READ_MEDIA_AUDIO').then((status) => { permissionUtil.requestAndroidPermission('android.permission.RECORD_AUDIO').then((status) => {
// status 为 1 表示用户已授权0 表示用户已拒绝, -1 表示用户永久拒绝 // status 为 1 表示用户已授权0 表示用户已拒绝, -1 表示用户永久拒绝
console.log('权限申请结果:', status); console.log('权限申请结果:', status);
if (status == -1) { if (status == -1) {
// 弹窗提示开启权限 msgErrorRcord()
msgToast.value.show({
title: '您已关闭录音权限,请在设置开启应用录音权限',
type: 'warning'
})
} else if (status == 1) { } else if (status == 1) {
showMask.value = true // start方法无法被及时调用的处理方法
recorderManager.start(); while (!showMask.value) {
recorderManager.onStart(() => { recorderManager.start(options);
console.log('录音开始'); recorderManager.onStart(() => {
}); console.log('录音开始');
recorderManager.onError((err) => { showMask.value = true
console.error('录音错误', err); });
}); }
} else if (status == 0) {
msgErrorRcord()
} }
}); });
} }
const msgErrorRcord = () => {
msgToast.value.show({
title: '请确认您的麦克风权限是否开启',
type: 'error'
})
}
// 停止录音 // 停止录音
const handleStopMic = () => { const handleStopMic = () => {
showMaskBtn.value = true showMaskBtn.value = true
if (voicePath.value) { if (voicePath.value) {
console.log("播放录音"); isPlayRecord.value = true
innerAudioContext.src = voicePath.value innerAudioContext.src = voicePath.value
innerAudioContext.play(); innerAudioContext.play();
innerAudioContext.onEnded(() => {
isPlayRecord.value = false
})
} else { } else {
recorderManager.stop() recorderManager.stop()
// showMask.value = false // showMask.value = false
@@ -241,10 +314,12 @@
showMask.value = false showMask.value = false
showMaskBtn.value = false showMaskBtn.value = false
voicePath.value = '' voicePath.value = ''
innerAudioContext.stop()
} }
// 发送录音文件 // 发送录音文件
const handleSendMp3 = () => { const handleSendMp3 = () => {
innerAudioContext.stop()
const token = uni.getStorageSync("token") const token = uni.getStorageSync("token")
uni.uploadFile({ uni.uploadFile({
// url: 'http://192.168.0.4:3005/upload', // url: 'http://192.168.0.4:3005/upload',
@@ -275,11 +350,7 @@
}) })
} }
// 文本放大 // 打开相册
const handleMagnify = () => {
showLongText.value = true
}
const handleCamera = () => { const handleCamera = () => {
permissionUtil.requestAndroidPermission('android.permission.READ_EXTERNAL_STORAGE').then((status) => { permissionUtil.requestAndroidPermission('android.permission.READ_EXTERNAL_STORAGE').then((status) => {
console.log('相册权限申请结果:', status); console.log('相册权限申请结果:', status);
@@ -302,27 +373,66 @@
}) })
} }
const uploadImages = (value) => { // 上传图片
console.log(value); const uploadImages = (imgList : any) => {
const token = uni.getStorageSync("token") const token = uni.getStorageSync("token")
uni.uploadFile({ imgList.forEach((item : string) => {
// url: 'http://192.168.0.4:3005/upload/image', uni.uploadFile({
url: 'http://192.168.0.15:3007/upload/files', // url: 'http://192.168.0.4:3005/upload',
filePath: value, url: 'http://192.168.0.15:3007/upload/file',
header: { filePath: item,
"Authorization": "Bearer " + token header: {
}, "Authorization": "Bearer " + token
name: 'file', },
success: (uploadFileRes) => { name: "file",
console.log(uploadFileRes); success: (uploadFileRes) => {
// console.log(JSON.stringify(uploadFileRes.data)); let result = JSON.parse(uploadFileRes.data)
}, if (result.data.fileUrl) {
fail: (res) => { // 上传文件成功,添加消息记录
console.log(JSON.stringify(res)); socket.value.emit('clientMsg', {
} createId: userId.value,
groupId: groupId.value,
content: result.data.fileUrl,
type: "img"
})
}
},
fail: (res) => {
console.log(res);
}
})
}) })
} }
// 图片预览
const showPreMask = ref(false)
const preImg = ref('')
const handlePreView = (value) => {
preImg.value = value
showPreMask.value = true
}
const playError = () => {
msgToast.value.show({
title: '音频错误,无法播放',
type: 'error'
})
}
// 播放录音
const handlePlayMp3 = (value) => {
innerAudioContext.src = value
innerAudioContext.play()
innerAudioContext.onPlay(() => {
// 正在播放录音
});
innerAudioContext.onError((res) => {
playError()
});
}
const groupId = ref() const groupId = ref()
const userId = ref() const userId = ref()
@@ -330,22 +440,9 @@
groupId.value = val.groupId groupId.value = val.groupId
}); });
const instance = getCurrentInstance(); const socket = ref()
const scrollHeight = ref(0)
const loadHeight = () => {
uni.getSystemInfo({
success(res) {
let screenHeight = res.screenHeight
uni.createSelectorQuery().in(instance.proxy).select("#navBarId").boundingClientRect((data : any) => {
scrollHeight.value = screenHeight - data.height
}).exec()
uni.createSelectorQuery().in(instance.proxy).select("#bottomId").boundingClientRect((data : any) => {
scrollHeight.value = scrollHeight.value - data.height
}).exec()
}
})
}
// 通信连接
const connection = () => { const connection = () => {
socket.value = io('http://192.168.0.15:3007', { socket.value = io('http://192.168.0.15:3007', {
query: {}, query: {},
@@ -358,18 +455,32 @@
socket.value.emit('addGroup', { socket.value.emit('addGroup', {
groupId: groupId.value, groupId: groupId.value,
}) })
socket.value.on('serverMsg', async (message) => { socket.value.on('serverMsg', async (message:any) => {
dataList.value.push(message) dataList.value.push(message)
console.log(dataList.value); // console.log(dataList.value);
setTimeout(()=>{
scrollToBottom()
}, 500)
}) })
} }
const scrollTop = ref(0)
const scrollToBottom = () => {
uni.createSelectorQuery().in(this).select("#scroll-main").boundingClientRect((res : any) => {
let top = res.height - scrollHeight.value;
if (top > 0) {
scrollTop.value = top;
}
}).exec()
}
const loadData = () => { const loadData = () => {
userId.value = uni.getStorageSync("user").id userId.value = uni.getStorageSync("user").id
} }
onMounted(() => { onMounted(() => {
mockData() // mockData()
loadData() loadData()
loadHeight() loadHeight()
@@ -415,6 +526,7 @@
} }
.btn-mic { .btn-mic {
flex: 1;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@@ -460,13 +572,18 @@
align-items: center; align-items: center;
.mp3-icon { .mp3-icon {
margin-left: 10rpx; margin: 0 10rpx;
width: 25rpx; width: 25rpx;
height: 25rpx; height: 25rpx;
} }
} }
} }
.img-content {
margin-right: 10rpx;
height: 200rpx;
}
.msg-avatar { .msg-avatar {
width: 80rpx; width: 80rpx;
height: 80rpx; height: 80rpx;
@@ -517,6 +634,8 @@
.mask-text { .mask-text {
font-size: 36rpx; font-size: 36rpx;
color: #fff; color: #fff;
display: flex;
align-items: center;
} }
.mask-btn { .mask-btn {
@@ -531,5 +650,12 @@
} }
} }
} }
// 图片预览
.mask-pre-img {
position: absolute;
top: 0;
bottom: 0;
}
} }
</style> </style>

View File

@@ -208,7 +208,7 @@
background-color: transparent; background-color: transparent;
.search { .search {
padding: 60rpx 42rpx 0; padding: 80rpx 42rpx 0;
} }
} }

BIN
static/icon/record_ing.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
static/icon/record_play.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB