随着移动互联网的发展,即时通讯应用变得越来越普遍。本文将介绍如何使用uni-app框架结合WebSocket实现一个简单的实时聊天功能。

准备工作

  • 确保已经安装了uni-app开发环境。
  • 了解基本的Vue.js知识。
  • WebSocket服务器已经搭建好并运行正常。

创建项目

  1. 使用HBuilderX创建一个新的uni-app项目。
  2. 在项目中添加必要的组件和样式。

WebSocket 混入模块

首先,我们需要创建一个混入模块来管理WebSocket连接的状态。这个模块将被引入到聊天界面中。

创建 WebSocket 混入模块

在项目的mixins目录下创建一个名为socket.js的文件,内容如下:

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

            // 链接
            surl: `ws://120.55.76.143:48080/infra/ws?token=66971089117e4372847ff4f31bc538df`, // 请替换为实际的WebSocket服务器URL

            // 底部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 data = {
                content: {
                    "fromUserId": uni.getStorageSync("userId"),
                    "text": msg,
                    "single": false,
                },
                type: "member-message"
            }
            data = JSON.stringify(data)
            let that = this
            if (this.socketOpen) {
                uni.sendSocketMessage({
                    data,
                    success: (res) => {
                        setTimeout(() => {
                            // json转对象
                            let param = JSON.parse(data)
                            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'
            });
        },
        // 打开Soceket
        openSocket() {
            let that = this
            uni.onSocketOpen((res) => {
                that.socketOpen = true
                console.log('WebSocket连接已打开!');
            });
        },
        // 监听关闭
        onclose() {
            let that = this
            uni.onSocketClose((res) => {
                that.socketOpen = false
                console.log('WebSocket 已关闭!');
            });
        },

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


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

        // 接收---到事件后处理的方法
        onMessageHandle(obj) {
            var data = JSON.parse(obj.content)
            this.list.push({
                userType: 'friend',
                avatar: this._friendAvatar,
                content: data.text
            })

            // 滚动到底部
            this.scrollToBottom()
        },

        // 发送---消息后处理的方法
        sendMessageHandle(obj) {
            var data = obj.content
            this.list.push({
                userType: 'self',
                avatar: this._selfAvatar,
                content: data.text
            })

            // 滚动到底部
            this.scrollToBottom()
        },


        // 定位到底部
        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)
        }
    }
}

聊天界面组件

接下来,在聊天界面中引入上面创建的混入模块,并实现聊天功能。

聊天界面代码

<template>
	<view class="page">
		<scroll-view class="scroll-view" scroll-y scroll-with-animation :scroll-top="top">
			<view style="padding: 30rpx 30rpx 240rpx;">
				<view class="message" :class="[item.userType]" v-for="(item,index) in list" :key="index">
					<image :src="item.avatar" v-if="item.userType === 'friend'" class="avatar" mode="widthFix"></image>
					<view class="content" v-if="item.messageType === 'image'">
						<image :src="item.content" mode="widthFix"></image>
					</view>
					<view class="content" v-else>
						{{ item.content }}
					</view>
					<image :src="item.avatar" v-if="item.userType === 'self'" class="avatar" mode="widthFix"></image>
				</view>
			</view>
		</scroll-view>
		<view class="tool">
			<input type="text" v-model="content" class="input"  />
			<view  @click="sendSocketMessage(content)">发送</view>
 		</view>
		<view id="last-msg-item" style="height: 1px;"></view>
	</view>
</template>

<script>
	import { socket } from "@/mixins/socket.js"
	export default {
		mixins: [socket],
		data() {
			return {
				content: '',
				list: [],
				top: 0
			};
		},
		onLoad(options) {
			uni.setNavigationBarTitle({
				title: options.name
			})
			this._friendAvatar = 'https://jiejinda.oss-cn-beijing.aliyuncs.com/d66c27ff806a7c51e56bbd2b402bcdc2e2b8244fbca7481014a0cbb474b1d78f.png'
			this._selfAvatar = 'https://jiejinda.oss-cn-beijing.aliyuncs.com/157422b1c0c4e79ffb4e1d84bb20b76a1849a4b93d08637b8d49567aa5968ceb.png'
			this.list = [
				{
					content: '欠我的工资什么时候还',
					userType: 'friend',
					avatar: this._friendAvatar
				},{
					content: '马上还!',
					userType: 'self',
					avatar: this._selfAvatar
				},{
					content: '还完拉黑你',
					userType: 'self',
					avatar: this._selfAvatar
				}
			]
		},
		methods: {
		 
		 
			scrollToBottom() {
				this.top = this.list.length * 1000
			}
		}
	}
</script>

<style lang="scss" scoped>
	.scroll-view {
		/* #ifdef H5 */
		height: calc(100vh - 44px);
		/* #endif */
		/* #ifndef H5 */
		height: 100vh;
		/* #endif */
		background: #eee;
		box-sizing: border-box;
	}
	.message {
		display: flex;
		align-items: flex-start;
		margin-bottom: 30rpx;
		
		.avatar {
			width: 80rpx;
			height: 80rpx;
			border-radius: 10rpx;
			margin-right: 30rpx;
		}
		.content {
			min-height: 80rpx;
			max-width: 60vw;
			box-sizing: border-box;
			font-size: 28rpx;
			line-height: 1.3;
			padding: 20rpx;
			border-radius: 10rpx;
			background: #fff;
			image {
				width: 200rpx;
			}
		}
		&.self {
			justify-content: flex-end;
			.avatar {
				margin: 0 0 0 30rpx;
			}
			.content {
				position: relative;
				&::after {
					position: absolute;
					content: '';
					width: 0;
					height: 0;
					border: 16rpx solid transparent;
					border-left: 16rpx solid #fff;
					right: -28rpx;
					top: 24rpx;
				}
			}
		}
		&.friend {
			.content {
				position: relative;
				&::after {
					position: absolute;
					content: '';
					width: 0;
					height: 0;
					border: 16rpx solid transparent;
					border-right: 16rpx solid #fff;
					left: -28rpx;
					top: 24rpx;
				}
			}
		}
	}

	.tool {
		position: fixed;
		width: 100%;
		min-height: 120rpx;
		left: 0;
		bottom: 0;
		background: #fff;
		display: flex;
		justify-content: space-between;
		align-items: flex-start;
		box-sizing: border-box;
		padding: 20rpx 24rpx 20rpx 40rpx;
		padding-bottom: calc(20rpx + constant(safe-area-inset-bottom)/2) !important;
		padding-bottom: calc(20rpx + env(safe-area-inset-bottom)/2) !important;
		.input {
			background: #eee;
			border-radius: 10rpx;
			height: 70rpx;
			margin-right: 30rpx;
			flex: 1;
			padding: 0 20rpx;
			box-sizing: border-box;
			font-size: 28rpx;
		}
		>view {
			width: 150rpx;
			line-height: 70rpx;
			text-align: center;
			height: 70rpx;
			background-color: #eee;
			border-radius: 10rpx;
		}
	}
</style>

最终效果

uniapp使用websock实现实时聊天功能_键盘高度

注意查看控制台

uniapp使用websock实现实时聊天功能_webscok_02

查看network的messages,往下的箭头就是接受消息,往上的箭头就是发送的消息

uniapp使用websock实现实时聊天功能_聊天_03

总结

通过以上步骤,我们成功地实现了一个基于uni-app的实时聊天功能。在这个过程中,我们学习了如何使用WebSocket进行实时通信,以及如何处理