项目已经部署,请访问: "chat.mycollagelife.com"
这是整个实战系列的第三章,这三章关联性较大,建议先阅读第一 , 第二两章,本章实现了在线成员的动态显示,和头像的匹配,单聊的设置,以及消息的美化,该系列教程非常详细,建议耐心读完。
实现的结果如图:
登录:
群聊,用户列表
单聊:
该项目已经上传至github https://github.com/neuqzxy/chat ,觉的可以的话,给个星星吧。
技术要求:
本章没有使用到什么其他的技术,如果你符合第二章的要求,就可以完成本章内容
实现在线用户列表
准备工作
1. 先拷贝一份第二章的源码到chat++文件夹中。
2. 逻辑分析:
在线用户列表是通过服务器端的全部成员数组来实现的,
首先,我们在登录之后要初始化用户列表,这需要服务器端返回一个数组,最少包括用户名和头像信息。
之后,处于登录状态,每次新增成员只需要服务器端广播该成员用户名和头像即可。
3. 界面调整:
我们要将界面分为两部分左部为用户列表,右部为聊天界面,并且聊天区域要设置最大的高度,超出显示滚动条。
创建静态文件
首先,用bootstrap的样式,将页面分成两部分,现在的所有内容为一部分,用户列表为另一部分,设置用户列表id为usergroup
<div style="display: none;" id="chatbox">
<div id="usergroup" class="col-md-2">
</div>
<div class="col-md-9 col-md-offset-1">
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
</div>
<div class="alert alert-info alert-dismissible fade in" role="alert" id="myalert1">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
<div id="content"></div>
<div id="inputgrop">
<div class="col-md-10">
<input type="text" placeholder="saying somgthing" class="form-control chatinput" id="chatinput">
</div>
<form action="" style="display: none;" id="resetform"><input type="file" style="display: none" id="imginput"></form>
<div class="col-md-2">
<button type="button" class="btn btn-primary" id="imgbutton">发送图片</button>
</div>
</div>
</div>
</div>
接下来我们使用bootstrap中的标签,显示在线成员字样,然后下面就是成员列表了:
<span class="label label-primary" style="font-size: 2em;">在线成员</span>
成员列表我们使用bootstrap中的列表组来实现
<div id="usergroup" class="col-md-2">
<span class="label label-primary" style="font-size: 2em;">在线成员</span>
<div class="list-group"></div>
</div>
现在看一下,已经成型了,但是聊天输入框的定位失效了,接下来我们打开开发者工具,发现html,chatbox 和里面两个div的高度全变成失效了,我们设置以下属性:
html,body {
background: #ccc;
padding: 20px 0 20px 0;
height: 100%;
}
#chatbox {
height: 100%;
}
然后再聊天区域的div里设置一个class,设置高度100%。
<div class="col-md-9 chatwindow col-md-offset-1">
.chatwindow {
height: 100%;
}
现在就可以啦。
初始化页面时显示用户列表
当我们登录进去的时候要渲染出现在在线的所有用户,并且让自己排在第一位。
我们打开app.js在loginSuccess中添加一行,将所有用户数组传过去
//然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发
data.userGroup = users; //将所有用户数组传过去
io.emit('loginSuccess',data); //将data原封不动的再发给该浏览器
在main.js中,参照bootstrap的链接样式:
<div class="list-group">
<a href="#" class="list-group-item active">
Cras justo odio
</a>
<a href="#" class="list-group-item">Dapibus ac facilisis in</a>
<a href="#" class="list-group-item">Morbi leo risus</a>
<a href="#" class="list-group-item">Porta ac consectetur ac</a>
<a href="#" class="list-group-item">Vestibulum at eros</a>
</div>
我们这么写:
/**
* 用户列表渲染
* 先添加自己,在从data中找到别人添加进去
*/
_$listGroup.append(`<a href="#" class="list-group-item">${_username}</a>`);
//添加别人
for(let _user of data.userGroup) {
if (_user.username !== _username) {
_$listGroup.append(`<a href="#" class="list-group-item">${_user.username}</a>`);
}
}
注意传入data。 这样就渲染出来了。
用户上下线时列表更新
这部分逻辑要重新写监听事件:
首先我们在loginSuccess中写上线时列表更新,加一行就行:
socket.on('loginSuccess',(data)=>{
/**
* 如果服务器返回的用户名和刚刚发送的相同的话,就登录
* 否则说明有地方出问题了,拒绝登录
*/
if(data.username === _username) {
beginChat(data);
}else {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用户列表添加该用户
_$listGroup.append(`<a href="#" class="list-group-item">${data.username}</a>`);
}
});
然后我们写用户离开的监听。
//断开连接后做的事情
socket.on('disconnect',()=>{ //注意,该事件不需要自定义触发器,系统会自动调用
usersNum --;
console.log(`当前有${usersNum}个用户连接上服务器了`);
//触发用户离开的监听
socket.broadcast.emit("oneLeave",{username: socket.username});
//删除用户
users.forEach(function (user,index) {
if(user.username === socket.username) {
users.splice(index,1); //找到该用户,删除
}
})
})
之所以用socket.broadcast.io是因为我们不需要离开的用户知道了。
在main.js中监听,这里我们有两种方式,第一种:用数组记录下用户的列表,删除该用户,使用数组重新渲染。第二种:提前在DOM中标记,找到所在元素,删除。
这里显然第二种方式更加适合。
我们找到刚才添加的那些用户的<a>标签,给他们添加name属性,值为用户名。
然后写:
socket.on('oneLeave',(data)=>{
//找到该用户并删除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
});
用户下线提示,及功能封装
还记得我们的好友上线提醒吗?我们现在写下线提醒:
我们复制一个红色警告框,
<div class="alert alert-danger alert-dismissible fade in" role="alert" id="myalert2">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
<span></span>
</div>
然后让他隐藏,在beginChat里写:
$("#myalert2").hide();
到了这里我们直接写一个函数,将好友上下线的功能写进去:
/**
*
* @param flag 为1代表好友上线,-1代表好友下线
* @param data 存储用户信息
*/
let comAndLeave = function (flag,data) {
//上线显示警告框,用户列表添加一个
if(flag === 1) {
$('#myalert1 span').html(`<span>您的好友<strong>${data.username}</strong>上线了!</span>`);
setTimeout(function() {
$("#myalert1").hide();
}, 1000);
$("#myalert1").show();
//用户列表添加该用户
_$listGroup.append(`<a href="#" name="${data.username}" class="list-group-item">${data.username}</a>`);
} else {
//下线显示警告框,用户列表删除一个
$('#myalert2 span').html(`<span>您的好友<strong>${data.username}</strong>下线了!</span>`);
setTimeout(function() {
$("#myalert2").hide();
}, 1000);
$("#myalert2").show();
//找到该用户并删除
_$listGroup.find($(`a[name='${data.username}']`)).remove();
}
};
函数内容很简单,全都是复制之前的代码,现在loginSuccess和onLeave都只需要调用一下该函数就行了
socket.on('oneLeave',(data)=>{
comAndLeave(-1,data);
});
socket.on('loginSuccess',(data)=>{
/**
* 如果服务器返回的用户名和刚刚发送的相同的话,就登录
* 否则说明有地方出问题了,拒绝登录
*/
if(data.username === _username) {
beginChat(data);
}else {
comAndLeave(1,data);
}
});
设置用户头像
到了这一步还不够,我们想有一个头像,这个头像是随机分配,但是在所有用户界面看都是相同的,所以我们需要随机选一个图片并广播给所有人。
我们进入阿里的图标库,选你喜欢的图片吧,可以添加至项目中以外链的形式加入到源码中。我已经选好了:
最后一个是自己,每个人随机头像自己是看不到的,在自己的界面只能看到最后的那个人头。
具体用法官网很清楚,我使用的是Symbol,因为他支持彩色。
我们打开使用帮助:
设置URL,css:
<script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script>
<style>
.icon {
width: 1em; height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
注意,每个人的URL不同。
在showMessage,showImg,beginChat等地方设置本人头像:
/**
* Created by zhouxinyu on 2017/8/6.
*/
$(function(){
const url = 'http://127.0.0.1:3000';
let _username = null;
let _$inputname = $("#name");
let _$loginButton = $("#loginbutton");
let _$chatinput = $("#chatinput");
let _$inputGroup = $("#inputgrop");
let _$imgButton = $("#imgbutton");
let _$imgInput = $("#imginput");
let _$listGroup = $(".list-group");
let socket = io.connect(url);
//设置用户名,当用户登录的时候触发
let setUsername = function () {
_username = _$inputname.val().trim(); //得到输入框中用户输入的用户名
//判断用户名是否存在
if(_username) {
soc