随着移动互联网的发展,即时通讯应用变得越来越普遍。本文将介绍如何使用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)
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.

聊天界面组件

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

聊天界面代码
<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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.

最终效果

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

注意查看控制台

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

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

uniapp使用websock实现实时聊天功能_uni-app_03

总结

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