uniapp实时通信(语音/图片/文字)代码复制即用

效果图

整体思路 

通过列表中的type属性判断当前消息的类型 

status 属性决定是哪一方发送的消息

每发送一条或接收到一条消息对列表进行push操作

图片我这里是写死宽度,让高度自适应,这样可以不裁剪图片

图片目前写的是单图片预览,想写多图片可以获取整个聊天列表,拿到type为image的列表将数据将图片路径push进去,就可以实现多图预览了

语音播放方面

使用 uni.createInnerAudioContext() 切换到后台不会继续播放

PS:

关于发送消息这一块,发消息走的后台接口,这样你才能不只是1对1

这里的 sendmessage 方法就是展示用的 如果消息走接口的情况下完全可以删掉,根据后台返回的字段改成自己所需就好了

使用uni-popup uni-icons

也可以自己搞个弹窗,吧icons换成image

代码

<template>
	<view class="chat">
		<view class="bottom">
			<image src="/static/image/chat/recorder.png" @click="onRecorderchange" v-if="!isRecorder"></image>
			<image src="/static/image/chat/input.png" @click="onRecorderchange" v-else></image>
			<view class="onRecorder" v-if="isRecorder" @touchstart="onStart" @touchend="onEnd"
				:id="inrecord ? 'inRecorder' : ''">
				按住说话
			</view>
			<input type="text" placeholder="输入你想说的话" v-model="chat" confirm-type="send" @confirm="onSend" v-else>
			<image src="/static/image/chat/more.png" @click="showPopup"></image>
		</view>
		<view class="card">

		</view>
		<view class="content">
			<scroll-view scroll-y="true" :style="{height : scrollViewHeight + 'px'}" :scroll-into-view="poaMessgae">
				<view class="block" v-for="(item,index) in list" :key="index">
					<view class="time" v-if="index == 0 || item.time - list[index-1].time >= 300000">
						<text>{{formatDate(item.time)}}</text>
					</view>
					<view class="list" :id="item.status == 'l' ? 'l' : 'r'">
						<image :src="item.avatar" class="avatar"></image>
						<text v-if="item.type == 'text'">{{item.message}}</text>
						<image :src="item.message" v-if="item.type == 'image'" @click="onPreview(item.message)"
							mode="widthFix"></image>
						<view class="record" v-if="item.type == 'record'" @click="onPlay(item.src,index)">
							<text>{{item.message}}</text> " <image src="/static/image/chat/record.png"></image>
						</view>
					</view>
				</view>

				<view id="poaMessgae"></view> <!-- 仅用于定位到消息最后一条 -->
			</scroll-view>
		</view>

		<!-- 底部弹出层 -->
		<uni-popup ref="popup" background-color="#fff">
			<view class="image">
				<view class="list" @click="chooseImage">
					<uni-icons type="image-filled" size="26" color="#8183F2"></uni-icons>
					<text>图片</text>
				</view>
			</view>
		</uni-popup>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				bgAudioManager: '', // 全局音频播放
				websocket: true,
				record: '', // 全局唯一录音管理区
				inrecord: false, // 是否处于录音状态
				chat: '', // 用户在聊天框中输入的内容
				scrollViewHeight: '',
				isRecorder: false, // 是否处于语音状态
				// 聊天列表数据
				// 头像其实可以写俩,你对面的,你自己的,我这里从简了 两端一样客服
				list: [{
						message: "这是一条虚拟消息",
						status: 'l',
						type: 'text',
						avatar: '/static/avatar.png',
						time: 1683791638972
					},
					{
						message: "哈哈",
						status: 'l',
						type: 'text',
						avatar: '/static/avatar.png',
						time: 1683793245623
					},
					{
						message: "https://ntg.a40.com.cn/uploads/20230505/69d7f7cefd4f36c2137d8d309d2b0c73.jpg",
						status: 'l',
						type: 'image',
						avatar: '/static/avatar.png',
						time: 1683793315399
					},
					{
						message: "这是一条虚拟消息",
						status: 'r',
						type: 'text',
						avatar: '/static/avatar.png',
						time: 1683793764803
					},
					{
						message: "10",
						status: 'l',
						type: 'record',
						avatar: '/static/avatar.png',
						scr: '音频链接',
						time: 1683795488873
					},
				],

				poaMessgae: 'poaMessgae',

			}
		},
		methods: {
			formatDate(value) {
				if (typeof(value) == 'undefined') {
					return ''
				} else {
					let date = new Date(value)
					let now = new Date()
					let y = date.getFullYear()
					let MM = date.getMonth() + 1
					MM = MM < 10 ? ('0' + MM) : MM
					let d = date.getDate()
					d = d < 10 ? ('0' + d) : d
					let h = date.getHours()
					h = h < 10 ? ('0' + h) : h
					let m = date.getMinutes()
					m = m < 10 ? ('0' + m) : m
					let s = date.getSeconds()
					s = s < 10 ? ('0' + s) : s
					if (now.getDate()-d==1 && now - date < 172800000) {
						return '昨天' + h + ':' + m
					} else if (now - date < 86400000) {
						return h + ':' + m
					} else if (now - date >= 86400000 && now - date < 31536000000) {
						return MM + '-' + d + ' ' + h + ':' + m
					} else if (now - date >= 31536000000) {
						return y + '-' + MM + '-' + d + ' ' + h + ':' + m
					}
				}
			},
			// input 输入框 回车发送消息事件
			onSend() {
				// 发送消息需要将 对象转换成字符串格式 接收消息在转换成对象

				let mess = {
					message: this.chat,
					status: 'r',
					type: 'text',
					avatar: '/static/avatar.png',
					time: new Date() - 0
				}

				this.sendmessage(JSON.stringify(mess))
				this.chat = ''
			},

			// 定位到消息最后一行
			poalast() {
				let that = this
				this.$nextTick(() => {
					this.poaMessgae = ''
					setTimeout(() => {
						this.poaMessgae = 'poaMessgae'
					}, 50)
				})
			},

			// 获取指定元素高度 为了设置scroll-view的高度 否则 srollviewinto 会失效
			getHeight(classNa) {
				setTimeout(() => {
					const query = uni.createSelectorQuery().in(this);
					query.select(classNa).boundingClientRect(data => {
						// this.$emit('heightChange',data.height);
						this.scrollViewHeight = data.height

					}).exec();
					query.select('.bottom').boundingClientRect(res => {
						// this.$emit('heightChange',data.height);
						this.scrollViewHeight = this.scrollViewHeight - res.height
					}).exec();
				}, 10);
			},

			// 切换语音 or 键盘
			onRecorderchange() {
				if (this.isRecorder) {
					this.isRecorder = false
				} else {
					uni.authorize({
						scope: 'scope.record',
						success: res => {
							this.isRecorder = true
						}
					})
				}
			},

			// 长按开始录入语音
			onStart() {
				this.inrecord = true
				this.record.start()
			},
			onEnd() {
				console.log('结束录音');
				this.inrecord = false
				this.record.stop()
				// 监听录音暂停的回调参数
				this.record.onStop(res => {
					console.log(res, "录音回调地址");
					if (res.duration < 1000) {
						return this.global.toast('请说话久一点')
					}
					// 将录音上传至服务器 拿到在线地址 连同地址一并发给客服 客服通过type来判断消息类型 实现播放
					/*
						后台上传步骤 后台莫得接口 省略先
					*/
					let mess = {
						message: Math.round(res.duration / 1000), // 时长
						status: 'r',
						// type 区分消息类型
						type: 'record',
						src: res.tempFilePath,
						avatar: '/static/avatar.png',
						time: new Date() - 0
					}
					// 写在上传回调内
					this.sendmessage(JSON.stringify(mess))


				})
				this.record.onError(err => {
					this.global.toast('获取录音失败')
				})
			},
			onPlay(e, index) {
				// 我这里没有写暂停播放 需要的可以 设置变量 根据变量状态判断 pause 或 play
				this.bgAudioManager = uni.createInnerAudioContext()
				this.bgAudioManager.src = e
				this.bgAudioManager.play()
				this.bgAudioManager.onPlay(() => {
					this.list[index].message--
				})
				this.bgAudioManager.onEnded(() => {
					this.list[index].message = this.bgAudioManager.duration
					this.bgAudioManager.offPlay()
					this.bgAudioManager.offEnded()
					this.bgAudioManager = null
				})

			},

			// 预览图片
			onPreview(e) {
				let urls = []
				urls.push(e)
				uni.previewImage({
					urls
				})
			},
			showPopup() {
				this.$refs.popup.open('bottom')
			},

			chooseImage() {
				let that = this
				uni.chooseImage({
					count: 9,
					sizeType: 'compressed',
					success: file => {
						file.tempFilePaths.forEach(item => {
							that.imgToBase64(item, base => {
								that.$post('api/index/upfileurl', {
									image: base
								}).then(res => {
									if (res.data.code == 1) {
										let mess = {
											message: that.$IMG + res.data.data
												.fileurl,
											status: 'r',
											type: 'image',
											avatar: '/static/avatar.png',
											time: new Date() - 0
										}
										that.sendmessage(JSON.stringify(mess))
									}
									this.$refs.popup.close()
								})
							})
						})
					}
				})
			},

			initWebSocket() {
				const url = 'wss:****'; // webSocket 域名
				this.websock = uni.connectSocket({
					url: url,
					success() {
						console.log('打开 websocket');
					}
				});
				this.websock.onOpen(this.onopen)
				this.websock.onMessage(this.onmessage)
				this.websock.onClose(this.close)
				this.websock.onError(this.onerror)
			},
			onopen() { // 连接建立之后执行send方法发送数据,连接成功
				const data = {
					token: this.userInfo.access_token
				};
				if (data.token) {
					const result = JSON.stringify(data);
					this.websock.send({
						data: result
					})
				} else {
					this.websock.close()
				}
			},
			onmessage(e) { // 数据接收
				e.status = 'l'
				let message = JSON.parse(e);
				this.list.push(message)
			},
			// 发送消息
			sendmessage(e) {
				this.list.push(JSON.parse(e))
				this.poalast() // 定位消息最后一行
			},
			close(e) {
				if (websocket) { // 非手动关闭自动从连
					this.reconnect()
				}
			},
			onerror() {
				if (websocket) { // 非手动关闭自动从连
					console.log('断开了重连');
					this.reconnect()
				}
			},
			reconnect() {
				this.initWebSocket();
			},

		},

		onLoad(option) {
			this.getHeight('.chat')
			// 全局唯一录音管理器
			this.record = uni.getRecorderManager()
		}

	}
</script>

<style scoped lang="less">
	.chat {
		padding: 30rpx;
		height: 100vh;
		box-sizing: border-box;
	}

	.image {
		background: #f6f6f6;
		border-radius: 30rpx 30rpx 0 0;
		display: flex;
		align-items: center;
		padding: 40rpx 80rpx;
		height: 180rpx;
		box-sizing: border-box;

		.list {
			display: flex;
			align-items: center;
			flex-direction: column;
			margin-right: 10rpx;

			text {
				font-size: 26rpx;
				color: #333333;
				line-height: 30rpx;
				margin-top: 10rpx;
			}
		}
	}

	.bottom {
		position: fixed;
		bottom: 0;
		left: 0;
		width: 750rpx;
		height: 150rpx;
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 0 31rpx 40rpx;
		box-sizing: border-box;
		background: #FFF;
		z-index: 99;

		image {
			width: 36rpx;
			height: 36rpx;
		}

		input {
			width: 581rpx;
			height: 69rpx;
			background: #F7F7F9;
			border-radius: 40rpx 40rpx 40rpx 40rpx;
			padding: 0pt 30rpx;
			font-size: 28rpx;
			box-sizing: border-box;
		}

		.onRecorder {
			width: 581rpx;
			height: 69rpx;
			line-height: 69rpx;
			text-align: center;
			background: #F7F7F9;
			border-radius: 40rpx 40rpx 40rpx 40rpx;
			padding: 0pt 30rpx;
			font-size: 28rpx;
			box-sizing: border-box;
		}

		#inRecorder {
			background: #cdcdcd;
		}
	}

	.content {
		padding-bottom: 150rpx;

		.time {
			display: flex;
			align-items: center;
			flex-direction: column;
			margin-bottom: 20rpx;

			text {
				background: rgba(0, 0, 0, 0.6);
				color: #f6f6f6;
				padding: 6rpx 10rpx;
				border-radius: 10rpx;
				font-size: 20rpx;
			}
		}

		.list {
			display: flex;
			align-items: flex-start;
			margin-bottom: 39rpx;

			.avatar {
				border-radius: 50%;
				width: 70rpx;
				height: 70rpx;
			}

			image {
				width: 120rpx;
				// height: 120rpx;
			}

			text {
				font-size: 22rpx;
				font-family: PingFang SC-Regular, PingFang SC;
				font-weight: 400;
				max-width: 520rpx;
			}

			.record {
				display: flex;
				align-items: center;

				image {
					width: 40rpx;
					height: 40rpx;
				}
			}
		}

		#l {
			flex-direction: row;

			text {
				min-height: 84rpx;
				background: #F4F8FB;
				border-radius: 0rpx 20rpx 20rpx 20rpx;
				padding: 0 20rpx;
				line-height: 84rpx;
				color: #333;
			}

			.avatar {
				margin-right: 18rpx;
			}

			.record {
				min-height: 84rpx;
				background: #F4F8FB;
				border-radius: 0rpx 20rpx 20rpx 20rpx;
				padding: 0 20rpx;
				line-height: 84rpx;
				color: #333;
			}
		}

		#r {
			flex-direction: row-reverse;

			text {
				min-height: 84rpx;
				background: #6B72F6;
				border-radius: 20rpx 0rpx 20rpx 20rpx;
				padding: 0 20rpx;
				line-height: 84rpx;
				color: #FFF;
			}

			.avatar {
				margin-left: 18rpx;
			}

			.record {
				min-height: 84rpx;
				background: #6B72F6;
				border-radius: 20rpx 0rpx 20rpx 20rpx;
				padding: 0 20rpx;
				line-height: 84rpx;
				color: #FFF;
			}
		}
	}
</style>

  • 12
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
uniapp中上传图片/视频并添加水印,可以按照以下步骤: 1. 在前端页面中,添加一个文件上传控件,例如: ```html <template> <div> <input type="file" ref="fileInput" @change="uploadFile"> </div> </template> ``` 2. 在前端编写上传文件的逻辑,可以使用uniapp提供的上传组件,例如: ```js methods: { // 上传文件 uploadFile() { const file = this.$refs.fileInput.files[0] uni.uploadFile({ url: '/upload', filePath: file.tempFilePath, name: 'file', success: (res) => { console.log('上传成功', res) }, fail: (err) => { console.log('上传失败', err) } }) } } ``` 3. 在后端编写一个接口,用于接收上传的文件并添加水印。可以使用Java、PHP等语言实现添加水印的逻辑,下面以Java为例。在接口中,可以使用以下代码实现添加水印: ```java // 获取上传的文件 Part filePart = request.getPart("file"); String fileName = filePart.getSubmittedFileName(); InputStream fileContent = filePart.getInputStream(); // 添加水印 Image image = ImageIO.read(fileContent); Graphics2D g = image.createGraphics(); g.drawImage(image, 0, 0, null); g.setFont(new Font("Arial", Font.BOLD, 30)); g.setColor(Color.RED); g.drawString("Watermark", 10, 30); g.dispose(); // 保存文件到服务器 File file = new File("/path/to/save/" + fileName); ImageIO.write(image, "jpg", file); ``` 4. 在前端接收后端返回的结果,例如: ```js success: (res) => { console.log('上传成功', res) // 处理上传成功的逻辑 }, fail: (err) => { console.log('上传失败', err) // 处理上传失败的逻辑 } ``` 以上是uniapp中上传图片/视频并添加水印的基本步骤,需要注意的是,添加水印时需要使用服务器端的代码实现,客户端无法实现。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值