导语:在一些社交软件中,经常可以看到各种聊天室的界面,接下来就总结一下聊天室的原理个实现方法,最后做一个简易的聊天室,包括登录/登出、加入/离开房间、发送接收聊天消息等功能。
pages/index
文件夹下面新建一个名叫chat
的组件;此聊天室前端方面使用了 uniapp 提供的几个 API 实现包括:
uni.connectSocket
:连接到 websocket 服务器;SocketTask.onOpen
:监听服务端连接打开;SocketTask.onClose
:监听服务端连接关闭;SocketTask.onError
:监听服务端连接错误;SocketTask.onMessage
:监听服务端的消息;SocketTask.send
:向服务端发送消息;SocketTask.close
:关闭服务端连接;此聊天室服务端使用 npm 库ws
搭建,另外头像上传部分使用原生node
实现,待会儿会详细介绍实现方法。
准备工作和原理分析完成后,接下来写一个简单的页面,下面主要是展示主要的内容。
包括输入用户名和上传头像的页面和功能。
<view class="chat-login" v-if="!wsInfo.isLogin">
<view class="chat-login-item">
<input
v-model="userInfo.name"
class="chat-login-ipt"
type="text"
:maxlength="10"
placeholder="请输入用户名" />
view>
<view class="chat-login-item">
<button class="chat-login-ipt" @click="uploadAvatar">上传头像button>
view>
<view class="chat-login-item">
<button class="chat-login-btn" type="primary" @click="wsLogin">用户登录button>
view>
view>
包括选择房间的退出登录的页面和功能。
<view class="chat-login" v-if="wsInfo.isLogin && !wsInfo.isJoin">
<view class="chat-login-item">
<picker mode="selector" :range="roomInfo.list" :value="roomInfo.id" @change="changeRoom">
请选择房间号:{
{roomInfo.name}}
picker>
view>
<view class="chat-login-item">
<button class="chat-login-btn" type="primary" @click="joinRoom">进入房间button>
view>
<view class="chat-login-item">
<button type="warn" @click="wsLogout">退出登录button>
view>
view>
包括展示房间号,退出房间,在线用户列表,聊天消息区域以及输入聊天内容和发送消息的页面和功能。
<view class="chat-room" v-if="wsInfo.isLogin && wsInfo.isJoin">
<view class="chat-room-set">
<text class="chat-room-name">房间{
{ roomInfo.id }}text>
<button class="chat-room-logout" size="mini" type="warn" @click="leaveRoom">退出房间button>
view>
<view class="chat-room-users">
在线人数({
{userInfo.users.length}}人):{
{ userInfo.usersText }}view
>
<scroll-view
:scroll-y="true"
:scroll-top="roomInfo.scrollTop"
@scroll="handlerScroll"
class="chat-room-area">
<view
:class="{'chat-room-area-item': true, 'active': item.name == userInfo.name}"
v-for="(item, index) in msg.list"
:key="`msg${index+1}`">
<view class="chat-room-user">
<text
v-if="roomInfo.mode == 'name'"
:class="{'chat-room-username': true, 'active': item.name == userInfo.name}"
>{
{ item.name }}text
>
<text v-if="roomInfo.mode == 'name'" class="chat-room-time"> ({
{item.createTime}}): text>
view>
<image
v-if="roomInfo.mode == 'avatar'"
class="chat-room-avatar"
:src="item.name == userInfo.name ? userInfo.avatar : item.avatar">image>
<view class="chat-room-content"> {
{item.content}} view>
view>
<view id="chat-room-area-pos">view>
scroll-view>
<view class="chat-room-bot">
<input
class="chat-room-bot-ipt"
type="text"
placeholder="请输入内容"
:maxlength="100"
v-model="msg.current" />
<button class="chat-room-bot-btn" size="mini" type="primary" @click="sendMsg">发送button>
view>
view>
.chat-login {
.chat-login-item {
margin-bottom: 20rpx;
height: 80rpx;
.chat-login-ipt,
uni-picker {
box-sizing: border-box;
padding: 10rpx 30rpx;
height: 100%;
font-size: 26rpx;
border: 1rpx solid $e;
border-radius: 10rpx;
color: var(--UI-BG-4);
}
uni-picker {
display: flex;
align-items: center;
}
.chat-login-btn {
background: $mainColor;
}
}
}
.chat-room {
display: flex;
flex-direction: column;
height: 100%;
.chat-room-set {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
font-size: 31rpx;
font-weight: bold;
.chat-room-name {
padding: 10rpx;
}
.chat-room-logout {
margin: 0;
}
}
.chat-room-users {
margin: 20rpx 0;
padding: 20rpx 0;
font-size: 28rpx;
line-height: 1.5;
border-bottom: 2rpx solid $e;
word-break: break-all;
}
.chat-room-area {
box-sizing: border-box;
padding: 20rpx;
height: calc(100% - 280rpx);
background: $f8;
.chat-room-area-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
margin-bottom: 20rpx;
padding-bottom: 20rpx;
.chat-room-user {
.chat-room-username {
color: $iptBorColor;
font-size: 31rpx;
line-height: 2;
&.active {
color: $mainColor;
}
}
}
.chat-room-avatar {
width: 68rpx;
height: 68rpx;
background: $white;
border-radius: 10rpx;
}
.chat-room-time {
font-size: 26rpx;
color: $iptBorColor;
}
.chat-room-content {
box-sizing: border-box;
margin-left: 12rpx;
padding: 15rpx;
max-width: calc(100% - 80rpx);
text-align: left;
font-size: 24rpx;
color: $white;
border-radius: 10rpx;
word-break: break-all;
background: $mainColor;
}
&.active {
flex-direction: row-reverse;
.chat-room-content {
margin-left: 0;
margin-right: 12rpx;
text-align: right;
color: $uni-text-color;
background: $white;
}
}
}
}
.chat-room-bot {
position: fixed;
bottom: 0;
left: 0;
display: flex;
justify-content: space-between;
align-items: center;
box-sizing: border-box;
padding: 0 30rpx;
width: 100%;
height: 100rpx;
background: $white;
border-top: 3rpx solid $f8;
.chat-room-bot-ipt {
flex: 1;
box-sizing: border-box;
padding: 0 30rpx;
height: 70rpx;
font-size: 24rpx;
border: 2rpx solid $iptBorColor;
border-radius: 10rpx;
}
.chat-room-bot-btn {
margin-left: 50rpx;
width: 150rpx;
height: 70rpx;
line-height: 70rpx;
font-size: 26rpx;
color: $white;
background: $mainColor;
}
}
}
// ws
let wsInfo = reactive({
ws: null, // ws对象
alive: false, // 是否连接
isLogin: false, // 是否登录
isJoin: false, // 是否加入
lock: false, // 锁住重连
reconnectTimer: null, // 重连计时
reconnectTime: 5000, // 重连计时间隔
clientTimer: null, // 客户端计时
clientTime: 10000, // 客户端计时间隔
serverTimer: null, // 服务端计时
serverTime: 30000, // 服务端计时间隔
});
// 用户信息
const userInfo = reactive({
id: "1", // 用户id
name: "", // 用户名称
avatar: "", // 用户头像
users: [], // 用户在线列表
usersText: "", // 用户在线列表文本
});
// 房间信息
const roomInfo = reactive({
id: 1, // 房间id
name: "房间1", // 房间名称
list: ["房间1", "房间2", "房间3"], // 房间列表
mode: "avatar", // 模式:avatar头像
scrollTop: 0, // 滚动到顶部距离
scrollHei: 0, // 滚动高度
goBottomTimer: null, // 到底部计时
goBottomTime: 500, // 到顶部计时间隔
});
// 聊天信息
const msg = reactive({
current: "", // 输入框内容
list: [], // 聊天消息列表
});
基本工作准备完毕后,接下来就开始实现功能。
进入页面后,首先连接上 websocket 服务端。
// 连接ws
function connectWs() {
wsInfo.ws = null;
wsInfo.ws = uni.connectSocket({
url: proxy.$apis.urls.wsUrl,
success() {
wsInfo.alive = true;
console.log("ws连接成功!");
},
fail()