uniapp+websocket聊天功能实现(超详细!!附代码,可直接复用)

最近项目上用到了聊天的功能,下面来分享一下关于websocket,键盘弹出等问题,避免别的朋友踩坑。

先给大家看看效果图 

 接着进入正文了!!!!!

一、需要注意的几个点

1.scroll-view的高度

先看看整体的页面布局

 system.windowHeight : 页面总高度

totalHeight:顶部导航栏高度

sendHeight:底部输入框高度 (设置样式的时候自己设置的)

keyboardHeight:键盘高度(键盘没有弹出的时候为0)

	            uni.getSystemInfo({
					success: res => {
						this.system = res
					}
				})

				// #ifdef MP-WEIXIN
				//获取胶囊信息
				this.menu = uni.getMenuButtonBoundingClientRect()

				//计算组件高度
				this.statusBarHeight = this.system.statusBarHeight //状态栏高度
				this.navigatorHeight = (this.menu.top - this.system.statusBarHeight) * 2 + this.menu.height //导航栏高度
				this.totalHeight = this.statusBarHeight + this.navigatorHeight //总高度
				// 保存到全局
				this.$store.state.totalHeight = this.totalHeight
				this.$store.state.system = this.system
				// #endif

				// #ifdef APP
				this.statusBarHeight = this.system.statusBarHeight //状态栏高度
				this.navigatorHeight = this.system.statusBarHeight + 45 //导航栏高度
				this.totalHeight = this.navigatorHeight //总高度
				// 保存到全局
				this.$store.state.totalHeight = this.totalHeight
				this.$store.state.system = this.system

				// #endif

这里需要注意的是scroll-view没有高度就和view差不多,scroll-into-view属性就不生效。

2.键盘弹起时,使页面向上移动,顶部导航栏不动

使用textarea原生的键盘弹起时,页面向上移是这样的

 解决后的图片:

 是不是看着顺眼多了

首先的话需要在页面加载的时候监听一下键盘的高度

		listenerKeyboardHeight() {
			this.listener = (res) => {
				console.log("键盘高度", res.height)
				this.keyboardHeight = res.height
				this.$nextTick(() => {
					this.scrollToBottom()
				})
			}
			uni.onKeyboardHeightChange(this.listener)
		}

当键盘弹出的时候,将scroll-view的高度减去键盘高度,底部发送框向上移动‘键盘高度’的px

底部的发送框一般都是position: fixed;来定位的,将bottom属性变一下就行了

		<view class="information-box lky-flex-items-center" :style="{bottom: bottom+'px'}">
			<view style="margin-right: 20rpx; width: 100%">
				<u--textarea :confirmType="null" v-model="value" placeholder="请输入" height="40rpx" :showConfirmBar="false"
					:placeholderStyle="placeholderStyle" @focus="textfocus" :adjustPosition="false" />
			</view>
			<image src="@/static/images/chat/icon_attachment.svg" class="icon" v-if="isUploadFile" @click="openFile" />
			<image src="@/static/images/chat/icon_send.svg" class="icon" @click="send" />
		</view>

3.定位到底部

先再来看看我们的页面布局

最后一条是用于定位到底部的

<!-- 用于定位到底部 -->
<view id="last-msg-item" style="height: 1px;"></view>

方法:

		// 定位到底部
		scrollToBottom() {
			this.$nextTick(() => {
				this.scrollIntoView = "last-msg-item"
                // 清空,为下一次定位做准备
				this.$nextTick(() => {
					this.scrollIntoView = ""
				})
			})
		},

二、websocket代码

这是封装的一个mixin类

import {
	socket
} from "@/common/mixin/socket.js"

//引入方法
export default {
    mixins: [socket],
}

socket.js

export const socket = {
	data() {
		return {
			// socket是否开启
			socketOpen: false,
			// 定时器
			timer: null,

			// 链接
			surl: `websocket链接`,

			// 底部id用于定位到底部
			scrollIntoView: "",

			// 键盘高度
			keyboardHeight: 0,

			// 监听键盘高度的方法
			listener: null
		}
	},

	onLoad(option) {
		// 开启键盘高度监听
		this.listenerKeyboardHeight()

		// socket初始化
		this.init()

		// 定时器,定时判断socket有没有掉线
		this.timer = setInterval(() => {
			this.isSocketConnct()
		}, 2000)

	},
	beforeDestroy() {
		// 关闭定时器
		clearInterval(this.timer)

		// 关闭键盘高度监听
		uni.offKeyboardHeightChange(this.listener)

		// 关闭Socket
		this.closeSocket()
	},
	methods: {
		// 发送消息
		sendSocketMessage(msg) {
			console.log("发送消息", msg);
			let that = this
			if (this.socketOpen) {
				uni.sendSocketMessage({
					data: msg,
					success: (res) => {
						setTimeout(() => {
                            // json转对象
							let param = JSON.parse(msg)
							that.sendMessageHandle(param)
						}, 300)
					},
					fail(err) {
						// 发送失败处理
					}
				});
			} else {
				// Socket没有开启,重新连接并重新发送消息
				this.init()
				setTimeout(() => {
					this.sendSocketMessage(msg)
				},300)
			}
		},

		// 判断是否连接
		isSocketConnct() {
			if (!this.socketOpen) {
				console.log("WebSocket 再次连接!");
				this.init()
			}
		},

		// 初始化
		init() {
			this.connect()
			this.openSocket()
			this.onclose()
			this.onSocketMessage()
		},

		// 建立连接
		connect() {
			console.log(this.surl);
			uni.connectSocket({
				url: this.surl,
				method: 'GET'
			});
		},

		// 监听关闭
		onclose() {
			let that = this
			uni.onSocketClose((res) => {
				that.socketOpen = false
				console.log('WebSocket 已关闭!');
			});
		},

		// 关闭
		closeSocket() {
			uni.closeSocket();
		},

		// 打开Soceket
		openSocket() {
			let that = this
			uni.onSocketOpen((res) => {
				that.socketOpen = true
				console.log('WebSocket连接已打开!');
			});
		},

		// 接收事件
		onSocketMessage() {
			let that = this
			uni.onSocketMessage((res) => {
				let obj = JSON.parse(res.data)
				console.log("接收事件", obj);
				this.onMessageHandle(obj)
			});
		},

		// 接收到事件后处理的方法(可自己重写)
		onMessageHandle(obj) {
			// 根据自己业务逻辑重写
		},

		// 发送消息后处理的方法(可自己重写)
		sendMessageHandle(msg) {
			// 根据自己业务逻辑重写
		},


		// 定位到底部
		scrollToBottom() {
			this.$nextTick(() => {
				this.scrollIntoView = "last-msg-item"
				this.$nextTick(() => {
					this.scrollIntoView = ""
				})
			})
		},

        // 开启键盘高度的监听
		listenerKeyboardHeight() {
			this.listener = (res) => {
				console.log("键盘高度", res.height)
				this.keyboardHeight = res.height
				this.$nextTick(() => {
					this.scrollToBottom()
				})
			}
			uni.onKeyboardHeightChange(this.listener)
		}
	}

}

直接引入就可以使用啦 , 接下来说说几个可能用到的方法,在自己的页面直接用就可以啦

	// 发送消息方法
	send(value) {
        // 自定义消息体
		let param = {
			"type": 1,
			"content": value,
	   	}

        // 对象转json
		let m = JSON.stringify(param)
		this.sendSocketMessage(m)
	},
			// 接收到事件后处理的方法(可自己重写)
			onMessageHandle(obj) {   
                // obj 接收到的事件对象
			},

			// 发送消息后处理的方法(可自己重写)
			sendMessageHandle(msg) {
				// msg 发送的消息对象
			}

三、页面布局代码

<template>
	<view>
        <!-- 自定义顶部导航栏 用原生的话下面scroll-view应该可以不用减去导航栏的高度的-->
		<u-navbar :placeholder='true' :autoBack='true' leftIconSize='40rpx'>
			<view class="navbar-title" slot="center">{{navbarTitle}}</view>
		</u-navbar>

		<scroll-view 
            scroll-y="true" 
            class="content-box" id="chat"
			:style="{'height':system.windowHeight - totalHeight - sendHeight - keyboardHeight+'px' }"
			:scroll-into-view='scrollIntoView'>
			<lky-gap size='40' />
			<view id="msglistview"></view>
			<view id="last-msg-item" style="height: 1px;"></view>
		</scroll-view>

		<lky-send 
            @send="send" 
            :is-upload-file="false" 
            @imageSend="imageSend" 
            @fileSend="fileSend"
			:bottom-copy='keyboardHeight'></lky-send>
	</view>
</template>

<script>

	import {
		mapState
	} from 'vuex'

	import {
		socket
	} from "@/common/mixin/socket.js"


	import LkySend from "../component/lky-p-send/lky-p-send.vue"

	export default {
		components: {
		},
		mixins: [socket],
		data() {
			return {
				src: '',
                
                // 聊天记录列表
				messageList: [],

                // socket链接,如果在socket.js中改了的话就不用写这个了
				surl: ``,

			};
		},
        
        //  我的高度存在vuex的
		computed: mapState([
			"totalHeight", "system", "sendHeight"
		]),

		onLoad(option) {
			// 获取聊天记录
			this.messageList = uni.getStorageSync(`messageList_${this.$store.state.userInfo.id}`) || []
			console.log("缓存中的聊天记录", this.messageList);
			this.scrollToBottom()
		},

		methods: {

			send(value) {
				let param = {
					"type": 1,
					"content": value,
				}
				let m = JSON.stringify(param)
				this.sendSocketMessage(m)
			},


            // 预览图片
			imageClick(url) {
				uni.previewImage({
					urls: [
						url
					]
				})
			},

            // 发送图片
			imageSend(url) {
				let param = {
					"type": 3,
					"text": "",
					"url": url,
					"userId": this.$store.state.userInfo.id
				}
				let m = JSON.stringify(param)
				this.sendSocketMessage(m)
			},

            // 发送文件
			fileSend(url, name) {
				let param = {
					"type": 4,
					"text": name,
					"url": url,
					"userId": this.$store.state.userInfo.id
				}
				let m = JSON.stringify(param)
				this.sendSocketMessage(m)
			},
            
            // 打开文件
			openFile(item) {
				uni.downloadFile({
					url: item.url,
					success: (res) => {
						var filePath = res.tempFilePath;
						uni.openDocument({
							filePath: filePath,
							showMenu: true,
							success: function(res) {}
						});
					}
				});
			},

			// 接收到事件后处理的方法(可自己重写)
			onMessageHandle(obj) {
                //根据业务逻辑重写
                // obj接收到的事件

			},

			// 发送消息后处理的方法(可自己重写)
			sendMessageHandle(msg) {
				 //根据业务逻辑重写
                // msg 发送的消息
			},
		}
	}
</script>

<style lang="scss">
	body {
		box-sizing: border-box;
		padding: 0 ;
		background-color: $lky-hui-bg ;
	}
</style>

<style lang="scss" scoped>

	.content-box {
		padding: 0 28rpx;
		box-sizing: border-box;
		height: auto;
		z-index: -1;
		overflow: hidden;
	}
</style>

到这里一个聊天的功能就结束了 ,这应该已经很详细了,其余的自己根据自己的业务逻辑和ui进行调整就行了!


最后有什么不足的地方也请各位指正优化一下

结束!

  • 16
    点赞
  • 82
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值