聊天室入门实战(node,sockte.io实现)--第三章(在线成员列表及头像显示,单聊)

来源:互联网 发布:java system.gc 编辑:程序博客网 时间:2024/06/15 07:29

项目已经部署,请访问: "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) {            socket.emit('login',{username: _username});   //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了        }    };    let beginChat = function (data) {        /**         * 1.隐藏登录框,取消它绑定的事件         * 2.显示聊天界面         */        $("#loginbox").hide('slow');        _$inputname.off('keyup');        _$loginButton.off('click');        /**         * 显示聊天界面,并显示一行文字,表示是谁的聊天界面         * 一个2s的弹框,显示欢迎字样         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染         */        $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));        $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));        $("#myalert1").hide();        $("#myalert2").hide();        $('#myalert').alert();        setTimeout(function () {            $('#myalert').alert('close');        },2000);        $("#chatbox").show('slow');        /**         * 用户列表渲染         * 先添加自己,在从data中找到别人添加进去         */        _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg>  ${_username}</a>`);        //添加别人        for(let _user of data.userGroup) {            if (_user.username !== _username) {                _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item">${_user.username}</a>`);            }        }    };    let sendMessage = function () {        /**         * 得到输入框的聊天信息,如果不为空,就触发sendMessage         * 将信息和用户名发送过去         */        let _message = _$chatinput.val();        if(_message) {            socket.emit('sendMessage',{username: _username, message: _message});        }    };    let setInputPosition = function () {        let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;        _$inputGroup.css({'top': height});    };    let showMessage = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username){            $("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`);        }else {            $("#content").append(`<p style='background: lightpink'><span>${data.username} : </span> ${data.message}</p>`);        }        setInputPosition();    };    let sendImg = function (event) {        /**         * 先判断浏览器是否支持FileReader         */        if (typeof FileReader === 'undefined') {            alert('您的浏览器不支持,该更新了');            //使用bootstrap的样式禁用Button            _$imgButton.attr('disabled', 'disabled');        } else {            let file = event.target.files[0];  //先得到选中的文件            //判断文件是否是图片            if(!/image\/\w+/.test(file.type)){   //如果不是图片                alert ("请选择图片");                return false;            }            /**             * 然后使用FileReader读取文件             */            let reader = new FileReader();            reader.readAsDataURL(file);            /**             * 读取完自动触发onload函数,我们触发sendImg事件给服务器             */            reader.onload = function (e) {                socket.emit('sendImg',{username: _username, dataUrl: this.result});            }        }    };    let showImg = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username) {            $('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true"  style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>:  <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);        } else {            $('#content').append(`<p class="bg-info" style=''><strong style="font-size: 1.5em;">${_username} </strong>:  <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);        }        setInputPosition();    };    /**     *     * @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"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-yonghu" style="font-size: 2em"></use></svg>${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();        }    };    /*       前端事件         */    /*登录事件*/    _$loginButton.on('click',function (event) {    //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数        setUsername();    });    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数            setUsername();        }    });    /*聊天事件*/    _$chatinput.on('keyup',function (event) {        if(event.keyCode === 13) {            sendMessage();            _$chatinput.val('');        }    });    //点击图片按钮触发input    _$imgButton.on('click',function (event) {        _$imgInput.click();        return false;    });    _$imgInput.change(function (event) {        sendImg(event);        //重置一下form元素,否则如果发同一张图片不会触发change事件        $("#resetform")[0].reset();    });    /*        socket.io部分逻辑        */    socket.on('loginSuccess',(data)=>{        /**         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录         * 否则说明有地方出问题了,拒绝登录         */        if(data.username === _username) {            beginChat(data);        }else {            comAndLeave(1,data);        }    });    socket.on('receiveMessage',(data)=>{        /**         * 监听到事件发生,就显示信息         */        showMessage(data);    });    socket.on('usernameErr',(data)=>{        /**         * 我们给外部div添加 .has-error         * 拷贝label插入         * 控制显示的时间为1.5s         */        $(".login .form-inline .form-group").addClass("has-error");        $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name'));        setTimeout(function() {            $('.login .form-inline .form-group').removeClass('has-error');            $("#name + label").remove();        }, 1500)    });    socket.on('receiveImg',(data)=>{        /**         * 监听到receiveImg发生,就显示图片         */        showImg(data);    });    socket.on('oneLeave',(data)=>{        comAndLeave(-1,data);    });});

然后我们给所有人设置一个随机头像

    //随机设置头像的地址    let touXiang = function (url) {        let _url = url || (Math.random()*8 | 0);        switch (_url) {            case 0 :                return "icon-river__easyiconnet1";            case 1 :                return "icon-river__easyiconnet";            case 2 :                return "icon-photo_camera__easyiconnet";            case 3 :                return "icon-planet_earth__easyiconnet";            case 4 :                return "icon-palace__easyiconnet";            case 5 :                return "icon-mountain__easyiconnet";            case 6 :                return "icon-parachute__easyiconnet";            case 7 :                return "icon-map__easyiconnet";            case 8 :                return "icon-mountains__easyiconnet";            case -1 :                return "icon-yonghu"        }    };
这个就是一个随机生成头像url的函数,没什么难度。

    //设置用户名,当用户登录的时候触发    let setUsername = function () {        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名        //判断用户名是否存在        if(_username) {            socket.emit('login',{username: _username, touXiangUrl: touXiang()});   //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了        }    };

然后我们将头像URL也传给服务器

        //如果用户名存在。将该用户的信息存进数组中        if(socket.username){            users.push({                username: data.username,                message: [],                dataUrl:[],                touXiangUrl:data.touXiangUrl            });

我们现在只需要在每一个传username的地方再传一个头像地址就行,然后,在showMwssage,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 _touXiangUrl = null;    let socket = io.connect(url);    //随机设置头像的地址    let touXiang = function (url) {        let _url = url || (Math.random()*8 | 0);        switch (_url) {            case 0 :                return "icon-river__easyiconnet1";            case 1 :                return "icon-river__easyiconnet";            case 2 :                return "icon-photo_camera__easyiconnet";            case 3 :                return "icon-planet_earth__easyiconnet";            case 4 :                return "icon-palace__easyiconnet";            case 5 :                return "icon-mountain__easyiconnet";            case 6 :                return "icon-parachute__easyiconnet";            case 7 :                return "icon-map__easyiconnet";            case 8 :                return "icon-mountains__easyiconnet";            case -1 :                return "icon-yonghu"        }    };    //设置用户名,当用户登录的时候触发    let setUsername = function () {        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名        _touXiangUrl = touXiang();        //判断用户名是否存在        if(_username) {            socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl});   //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了        }    };    let beginChat = function (data) {        /**         * 1.隐藏登录框,取消它绑定的事件         * 2.显示聊天界面         */        $("#loginbox").hide('slow');        _$inputname.off('keyup');        _$loginButton.off('click');        /**         * 显示聊天界面,并显示一行文字,表示是谁的聊天界面         * 一个2s的弹框,显示欢迎字样         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染         */        $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));        $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));        $("#myalert1").hide();        $("#myalert2").hide();        $('#myalert').alert();        setTimeout(function () {            $('#myalert').alert('close');        },2000);        $("#chatbox").show('slow');        /**         * 用户列表渲染         * 先添加自己,在从data中找到别人添加进去         */        _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg>  ${_username}</a>`);        //添加别人        for(let _user of data.userGroup) {            if (_user.username !== _username) {                _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg>  ${_user.username}</a>`);            }        }    };    let sendMessage = function () {        /**         * 得到输入框的聊天信息,如果不为空,就触发sendMessage         * 将信息和用户名发送过去         */        let _message = _$chatinput.val();        if(_message) {            socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl});        }    };    let setInputPosition = function () {        let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;        _$inputGroup.css({'top': height});    };    let showMessage = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username){            $("#content").append(`<p style='background: lightskyblue'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <span>${data.username} : </span> ${data.message}</p>`);        }else {            $("#content").append(`<p style='background: lightpink'><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>  <span>${data.username} : </span> ${data.message}</p>`);        }        setInputPosition();    };    let sendImg = function (event) {        /**         * 先判断浏览器是否支持FileReader         */        if (typeof FileReader === 'undefined') {            alert('您的浏览器不支持,该更新了');            //使用bootstrap的样式禁用Button            _$imgButton.attr('disabled', 'disabled');        } else {            let file = event.target.files[0];  //先得到选中的文件            //判断文件是否是图片            if(!/image\/\w+/.test(file.type)){   //如果不是图片                alert ("请选择图片");                return false;            }            /**             * 然后使用FileReader读取文件             */            let reader = new FileReader();            reader.readAsDataURL(file);            /**             * 读取完自动触发onload函数,我们触发sendImg事件给服务器             */            reader.onload = function (e) {                socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl});            }        }    };    let showImg = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username) {            $('#content').append(`<p class="bg-primary" style=' text-align:center;'><svg class="icon" aria-hidden="true"  style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg> <strong style="font-size: 1.5em;">${_username} </strong>:  <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);        } else {            $('#content').append(`<p class="bg-info" style=''><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>  <strong style="font-size: 1.5em;">${data.username} </strong>:  <img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></p>`);        }        setInputPosition();    };    /**     *     * @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"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${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();        }    };    /*       前端事件         */    /*登录事件*/    _$loginButton.on('click',function (event) {    //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数        setUsername();    });    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数            setUsername();        }    });    /*聊天事件*/    _$chatinput.on('keyup',function (event) {        if(event.keyCode === 13) {            sendMessage();            _$chatinput.val('');        }    });    //点击图片按钮触发input    _$imgButton.on('click',function (event) {        _$imgInput.click();        return false;    });    _$imgInput.change(function (event) {        sendImg(event);        //重置一下form元素,否则如果发同一张图片不会触发change事件        $("#resetform")[0].reset();    });    /*        socket.io部分逻辑        */    socket.on('loginSuccess',(data)=>{        /**         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录         * 否则说明有地方出问题了,拒绝登录         */        if(data.username === _username) {            beginChat(data);        }else {            comAndLeave(1,data);        }    });    socket.on('receiveMessage',(data)=>{        /**         * 监听到事件发生,就显示信息         */        showMessage(data);    });    socket.on('usernameErr',(data)=>{        /**         * 我们给外部div添加 .has-error         * 拷贝label插入         * 控制显示的时间为1.5s         */        $(".login .form-inline .form-group").addClass("has-error");        $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name'));        setTimeout(function() {            $('.login .form-inline .form-group').removeClass('has-error');            $("#name + label").remove();        }, 1500)    });    socket.on('receiveImg',(data)=>{        /**         * 监听到receiveImg发生,就显示图片         */        showImg(data);    });    socket.on('oneLeave',(data)=>{        comAndLeave(-1,data);    });});

实现单聊

首先,我们给本人的a标签设置不可点击属性:在class中写disable

_$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg>  ${_username}</a>`);

然后我们找两个模态框:

一个用于发送单聊消息,一个用于显示。修改一下


<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>聊天室</title>    <script src="JavaScripts/jquery-3.2.1.js"></script>    <link rel="stylesheet" href="stylesheets/bootstrap.css">    <script src="JavaScripts/bootstrap.js"></script>    <script src="JavaScripts/main.js"></script>    <script src="/socket.io/socket.io.js"></script>    <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>    <style>        html,body {            background: #ccc;            padding: 20px 0 20px 0;            height: 100%;        }        h1 {            text-align: center;        }        .login {            text-align: center;        }        #chatbox {            height: 100%;        }        #inputgrop {            display: block;            width: 100%;            position: absolute;            min-height: 40px;            bottom: 0;        }        #content {            height: auto;        }        .alert {            z-index: 100;            position: absolute;            top: 0;            width: 100%;        }        .chatwindow {            height: 100%;        }    </style></head><body><div id="loginbox">    <h1>登录</h1>    <hr>    <div class="login">        <div class="form-inline">            <div class="form-group">                <label>用户名</label>                <input type="text" class="form-control" id="name" placeholder="请输入用户名">            </div>            <br><br>            <button type="button" class="btn btn-info btn-lg" id="loginbutton">登录</button>        </div>    </div></div><div style="display: none;" id="chatbox">    <div id="usergroup" class="col-md-2">        <span  class="label label-primary" style="font-size: 2em;">在线成员</span>        <div class="list-group"></div>    </div>    <div class="col-md-9 chatwindow 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 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>        <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>    <!-- 发送模态框(Modal) -->    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">        <div class="modal-dialog">            <div class="modal-content">                <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">                        ×                    </button>                    <h4 class="modal-title" id="myModalLabel">                        模态框(Modal)标题                    </h4>                </div>                <div class="modal-body">                    <input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone">                </div>                <div class="modal-footer">                    <button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">关闭                    </button>                    <button type="button" class="btn btn-primary" id="sendtoo">                        发送                    </button>                </div>            </div><!-- /.modal-content -->        </div><!-- /.modal -->    </div>    <!-- 收到模态框(Modal) -->    <div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">        <div class="modal-dialog">            <div class="modal-content">                <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">                        ×                    </button>                    <h4 class="modal-title" id="myModalLabel1">                        模态框(Modal)标题                    </h4>                </div>                <div class="modal-body shoudao">                </div>                <div class="modal-footer">                    <button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo">                        知道了                    </button>                </div>            </div><!-- /.modal-content -->        </div><!-- /.modal -->    </div>    <button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button></div></body></html>
我们的button是用来触发模态框的,收到消息后需要触发显示


我们在列表的好友的a标签中添加:

 data-toggle="modal" data-target="#myModal"
这两个属性即可点击触发modal

然后监听点击事件,并换文字:

    //监听成员点击事件    _$listGroup.on('click',function (event) {        initModal(event);    });
    initModal = function (event) {        let _name = $(event.target).attr('name');        $("#myModalLabel").text(`发给${_name}`);    };

然后就点击发送了,我们发送给服务器,

这里我们设了一个全局变量 _to 里面是收件人的用户名

    //监听私聊的按钮,触发私聊事件    $("#sendtoo").on('click',function (event) {        /**         * 得到用户输入的消息,如果部位空,就发送,清空内容关闭模态框         */        let _text = $("#inputtoone").val();        if (typeof _text !== 'undefined') {            socket.emit('sendToOne', {to: _to, text: _text, username: _username});            $("#inputtoone").val('');            $("#closesendtoo").click();        }    });


打开app.js,写监听:

我们是通过不同的socket来区别不同客户端的,每一个连接的socket都是不同的,所以,我们需要将socket和用户名匹配,我们使用数组:

        //如果用户名存在。将该用户的信息存进数组中        if(socket.username){            users.push({                username: data.username,                message: [],                dataUrl: [],                touXiangUrl: data.touXiangUrl            });            //保存socket            _sockets[socket.username] = socket;            //然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发            data.userGroup = users;         //将所有用户数组传过去            io.emit('loginSuccess',data);   //将data原封不动的再发给该浏览器        }

    socket.on('sendToOne',(data)=>{        //判断该用户是否存在,如果存在就触发receiveToOne事件        for (let _user of users) {            if (_user.username === data.to) {                _sockets[data.to].emit('receiveToOne',data);            }        }    });

下面我们在客户端得到该事件的data

    socket.on('receiveToOne',(data)=>{        $("#myModalLabel1").text(`来自${data.username}`);        $(".shoudao").text(`${data.text}`);        $("#showmodal").click();    });
三行代码就搞定了。

下面试一下吧,能过成功私聊了。


最后,我们修改一下聊天消息的样式:

在html中添加:

    <style>        .sender {            clear: both        }        .sender div:nth-of-type(1) {            float: left        }        .sender div:nth-of-type(2) {            background-color: #7fffd4;            float: left;            margin: 0 20px 10px 15px;            padding: 10px 10px 10px 0;            border-radius: 7px        }        .receiver div:first-child img,        .sender div:first-child img {            width: 50px;            height: 50px        }        .receiver {            clear: both        }        .receiver div:nth-child(1) {            float: right        }        .receiver div:nth-of-type(2) {            float: right;            background-color: gold;            margin: 0 10px 10px 20px;            padding: 10px 0 10px 10px;            border-radius: 7px        }        .left_triangle {            height: 0;            width: 0;            border-width: 8px;            border-style: solid;            border-color: transparent #7fffd4 transparent transparent;            position: relative;            left: -16px;            top: 3px        }        .right_triangle {            height: 0;            width: 0;            border-width: 8px;            border-style: solid;            border-color: transparent transparent transparent gold;            position: relative;            right: -16px;            top: 3px        }    </style>
然后修改显示消息和图片的代码:

html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>聊天室</title>    <script src="JavaScripts/jquery-3.2.1.js"></script>    <link rel="stylesheet" href="stylesheets/bootstrap.css">    <script src="JavaScripts/bootstrap.js"></script>    <script src="JavaScripts/main.js"></script>    <script src="/socket.io/socket.io.js"></script>    <script src="http://at.alicdn.com/t/font_o86wdrgtu766r.js"></script>    <style>        .sender {            clear: both        }        .sender div:nth-of-type(1) {            float: left        }        .sender div:nth-of-type(2) {            background-color: #7fffd4;            float: left;            margin: 0 20px 10px 15px;            padding: 10px 10px 10px 0;            border-radius: 7px        }        .receiver div:first-child img,        .sender div:first-child img {            width: 50px;            height: 50px        }        .receiver {            clear: both        }        .receiver div:nth-child(1) {            float: right        }        .receiver div:nth-of-type(2) {            float: right;            background-color: gold;            margin: 0 10px 10px 20px;            padding: 10px 0 10px 10px;            border-radius: 7px        }        .left_triangle {            height: 0;            width: 0;            border-width: 8px;            border-style: solid;            border-color: transparent #7fffd4 transparent transparent;            position: relative;            left: -16px;            top: 3px        }        .right_triangle {            height: 0;            width: 0;            border-width: 8px;            border-style: solid;            border-color: transparent transparent transparent gold;            position: relative;            right: -16px;            top: 3px        }    </style>    <style>        .icon {            width: 1em; height: 1em;            vertical-align: -0.15em;            fill: currentColor;            overflow: hidden;        }    </style>    <style>        html,body {            background: #ccc;            padding: 20px 0 20px 0;            height: 100%;        }        h1 {            text-align: center;        }        .login {            text-align: center;        }        #chatbox {            height: 100%;        }        #inputgrop {            display: block;            width: 100%;            position: absolute;            min-height: 40px;            bottom: 0;        }        #content {            height: auto;        }        .alert {            z-index: 100;            position: absolute;            top: 0;            width: 100%;        }        .chatwindow {            height: 100%;        }    </style></head><body><div id="loginbox">    <h1>登录</h1>    <hr>    <div class="login">        <div class="form-inline">            <div class="form-group">                <label>用户名</label>                <input type="text" class="form-control" id="name" placeholder="请输入用户名">            </div>            <br><br>            <button type="button" class="btn btn-info btn-lg" id="loginbutton">登录</button>        </div>    </div></div><div style="display: none;" id="chatbox">    <div id="usergroup" class="col-md-2">        <span  class="label label-primary" style="font-size: 2em;">在线成员</span>        <div class="list-group"></div>    </div>    <div class="col-md-9 chatwindow 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 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>        <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>    <!-- 发送模态框(Modal) -->    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">        <div class="modal-dialog">            <div class="modal-content">                <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">                        ×                    </button>                    <h4 class="modal-title" id="myModalLabel">                        模态框(Modal)标题                    </h4>                </div>                <div class="modal-body">                    <input type="text" placeholder="say something" class="form-control chatinput" id="inputtoone">                </div>                <div class="modal-footer">                    <button type="button" class="btn btn-default" data-dismiss="modal" id="closesendtoo">关闭                    </button>                    <button type="button" class="btn btn-primary" id="sendtoo">                        发送                    </button>                </div>            </div><!-- /.modal-content -->        </div><!-- /.modal -->    </div>    <!-- 收到模态框(Modal) -->    <div class="modal fade" id="myModal1" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">        <div class="modal-dialog">            <div class="modal-content">                <div class="modal-header">                    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">                        ×                    </button>                    <h4 class="modal-title" id="myModalLabel1">                        模态框(Modal)标题                    </h4>                </div>                <div class="modal-body shoudao">                </div>                <div class="modal-footer">                    <button type="button" class="btn btn-primary" data-dismiss="modal" id="closesendtoo">                        知道了                    </button>                </div>            </div><!-- /.modal-content -->        </div><!-- /.modal -->    </div>    <button id="showmodal" data-toggle="modal" data-target="#myModal1" style="display: none;"></button></div></body></html>

main.js:

/** * 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 _touXiangUrl = null;    let _to = null;    let socket = io.connect(url);    //随机设置头像的地址    let touXiang = function (url) {        let _url = url || (Math.random()*8 | 0);        switch (_url) {            case 0 :                return "icon-river__easyiconnet1";            case 1 :                return "icon-river__easyiconnet";            case 2 :                return "icon-photo_camera__easyiconnet";            case 3 :                return "icon-planet_earth__easyiconnet";            case 4 :                return "icon-palace__easyiconnet";            case 5 :                return "icon-mountain__easyiconnet";            case 6 :                return "icon-parachute__easyiconnet";            case 7 :                return "icon-map__easyiconnet";            case 8 :                return "icon-mountains__easyiconnet";            case -1 :                return "icon-yonghu"        }    };    initModal = function (event) {        _to = $(event.target).attr('name');        $("#myModalLabel").text(`发给${_to}`);    };    //设置用户名,当用户登录的时候触发    let setUsername = function () {        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名        _touXiangUrl = touXiang();        //判断用户名是否存在        if(_username) {            socket.emit('login',{username: _username, touXiangUrl: _touXiangUrl});   //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了        }    };    let beginChat = function (data) {        /**         * 1.隐藏登录框,取消它绑定的事件         * 2.显示聊天界面         */        $("#loginbox").hide('slow');        _$inputname.off('keyup');        _$loginButton.off('click');        /**         * 显示聊天界面,并显示一行文字,表示是谁的聊天界面         * 一个2s的弹框,显示欢迎字样         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染         */        $(`<h2 style="text-align: center">${_username}的聊天室</h2>`).insertBefore($("#content"));        $(`<strong>欢迎你</strong><span>${_username}!</span>`).insertAfter($('#myalert button'));        $("#myalert1").hide();        $("#myalert2").hide();        $('#myalert').alert();        setTimeout(function () {            $('#myalert').alert('close');        },2000);        $("#chatbox").show('slow');        /**         * 用户列表渲染         * 先添加自己,在从data中找到别人添加进去         */        _$listGroup.append(`<a href="#" name="${_username}" class="list-group-item disabled"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#icon-yonghu"></use></svg>  ${_username}</a>`);        //添加别人        for(let _user of data.userGroup) {            if (_user.username !== _username) {                _$listGroup.append(`<a href="#" name="${_user.username}" class="list-group-item"  data-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${_user.touXiangUrl}"></use></svg>  ${_user.username}</a>`);            }        }    };    let sendMessage = function () {        /**         * 得到输入框的聊天信息,如果不为空,就触发sendMessage         * 将信息和用户名发送过去         */        let _message = _$chatinput.val();        if(_message) {            socket.emit('sendMessage',{username: _username, message: _message, touXiangUrl: _touXiangUrl});        }    };    let setInputPosition = function () {        let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;        _$inputGroup.css({'top': height});    };    let showMessage = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username) {        $('#content').append(`<div class="receiver">                                    <div>                                        <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">                                            <use xlink:href="#icon-yonghu"></use>                                        </svg>                                        <strong style="font-size: 1.5em;">                                            ${data.username}                                         </strong>                                    </div>                                    <div>                                        <div class="right_triangle"></div>                                        <span>  ${data.message}</span>                                    </div>                                </div>`);    } else {        $('#content').append(`<div class="sender">                                    <div>                                        <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">                                            <use xlink:href="#${data.touXiangUrl}"></use>                                        </svg>                                        <strong style="font-size: 1.5em;">${data.username} </strong>                                    </div>                                    <div>                                        <div class="left_triangle"></div>                                        <span>  ${data.message}</span>                                    </div>                                                                    </div>`);    }        setInputPosition();    };    let sendImg = function (event) {        /**         * 先判断浏览器是否支持FileReader         */        if (typeof FileReader === 'undefined') {            alert('您的浏览器不支持,该更新了');            //使用bootstrap的样式禁用Button            _$imgButton.attr('disabled', 'disabled');        } else {            let file = event.target.files[0];  //先得到选中的文件            //判断文件是否是图片            if(!/image\/\w+/.test(file.type)){   //如果不是图片                alert ("请选择图片");                return false;            }            /**             * 然后使用FileReader读取文件             */            let reader = new FileReader();            reader.readAsDataURL(file);            /**             * 读取完自动触发onload函数,我们触发sendImg事件给服务器             */            reader.onload = function (e) {                socket.emit('sendImg',{username: _username, dataUrl: this.result, touXiangUrl: _touXiangUrl});            }        }    };    let showImg = function (data) {        //先判断这个消息是不是自己发出的,然后再以不同的样式显示        if(data.username === _username) {            $('#content').append(`<div class="receiver">                                    <div>                                        <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">                                            <use xlink:href="#icon-yonghu"></use>                                        </svg>                                        <strong style="font-size: 1.5em;">                                            ${data.username}                                         </strong>                                    </div>                                    <div>                                        <div class="right_triangle"></div>                                        <span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span>                                    </div>                                </div>`);        } else {            $('#content').append(`<div class="sender">                                    <div>                                        <svg class="icon img-circle" aria-hidden="true" style="font-size: 2em;">                                            <use xlink:href="#${data.touXiangUrl}"></use>                                        </svg>                                        <strong style="font-size: 1.5em;">${data.username} </strong>                                    </div>                                    <div>                                        <div class="left_triangle"></div>                                        <span><img class="img-thumbnail" src="${data.dataUrl}" style="max-height: 100px"/></span>                                    </div>                                                                    </div>`);        }        setInputPosition();    };    /**     *     * @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-toggle="modal" data-target="#myModal"><svg class="icon" aria-hidden="true" style="font-size: 2em"><use xlink:href="#${data.touXiangUrl}"></use></svg>${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();        }    };    /*       前端事件         */    /*登录事件*/    _$loginButton.on('click',function (event) {    //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数        setUsername();    });    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数            setUsername();        }    });    /*聊天事件*/    _$chatinput.on('keyup',function (event) {        if(event.keyCode === 13) {            sendMessage();            _$chatinput.val('');        }    });    //点击图片按钮触发input    _$imgButton.on('click',function (event) {        _$imgInput.click();        return false;    });    _$imgInput.change(function (event) {        sendImg(event);        //重置一下form元素,否则如果发同一张图片不会触发change事件        $("#resetform")[0].reset();    });    //监听成员点击事件    _$listGroup.on('click',function (event) {        initModal(event);    });    //监听私聊的按钮,触发私聊事件    $("#sendtoo").on('click',function (event) {        /**         * 得到用户输入的消息,如果部位空,就发送,清空内容关闭模态框         */        let _text = $("#inputtoone").val();        if (typeof _text !== 'undefined') {            socket.emit('sendToOne', {to: _to, text: _text, username: _username});            $("#inputtoone").val('');            $("#closesendtoo").click();        }    });    /*        socket.io部分逻辑        */    socket.on('loginSuccess',(data)=>{        /**         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录         * 否则说明有地方出问题了,拒绝登录         */        if(data.username === _username) {            beginChat(data);        }else {            comAndLeave(1,data);        }    });    socket.on('receiveMessage',(data)=>{        /**         * 监听到事件发生,就显示信息         */        showMessage(data);    });    socket.on('usernameErr',(data)=>{        /**         * 我们给外部div添加 .has-error         * 拷贝label插入         * 控制显示的时间为1.5s         */        $(".login .form-inline .form-group").addClass("has-error");        $('<label class="control-label" for="inputError1">用户名重复</label>').insertAfter($('#name'));        setTimeout(function() {            $('.login .form-inline .form-group').removeClass('has-error');            $("#name + label").remove();        }, 1500)    });    socket.on('receiveImg',(data)=>{        /**         * 监听到receiveImg发生,就显示图片         */        showImg(data);    });    socket.on('oneLeave',(data)=>{        comAndLeave(-1,data);    });    socket.on('receiveToOne',(data)=>{        $("#myModalLabel1").text(`来自${data.username}`);        $(".shoudao").text(`${data.text}`);        $("#showmodal").click();    });});

app.js

/** * Created by zhouxinyu on 2017/8/6. */const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住const server = require('http').Server(app);const path = require('path');        // 这是node的路径处理模块,可以格式化路径const io = require('socket.io')(server);     //将socket的监听加到app设置的模块里。const users = [];                    //用来保存所有的用户信息let usersNum = 0;const _sockets = [];                 //将socket和用户名匹配server.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。});/** * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。 * req带表浏览器的请求对象,res代表服务器的返回对象 */app.get('/',(req,res)=>{    res.redirect('/chat.html');       // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中});/** * __dirname表示当前文件所在的绝对路径,所以我们使用path.join将app.js的绝对路径和public加起来就得到了public的绝对路径。 * 用path.join是为了避免出现 ././public 这种奇怪的路径 * express.static就帮我们托管了public文件夹中的静态资源。 * 只要有 127.0.0.1:3000/XXX 的路径都会去public文件夹下找XXX文件然后发送给浏览器。 */app.use('/',express.static(path.join(__dirname,'./public')));        //一句话就搞定。/*socket*/io.on('connection',(socket)=>{              //监听客户端的连接事件    /**     * 所有有关socket事件的逻辑都在这里写     */    usersNum ++;    console.log(`当前有${usersNum}个用户连接上服务器了`);    socket.on('login',(data)=>{        /**         * 先保存在socket中         * 循环数组判断用户名是否重复,如果重复,则触发usernameErr事件         * 将用户名删除,之后的事件要判断用户名是否存在         */        socket.username = data.username;        for (let user of users) {            if(user.username === data.username){                socket.emit('usernameErr',{err: '用户名重复'});                socket.username = null;                break;            }        }        //如果用户名存在。将该用户的信息存进数组中        if(socket.username){            users.push({                username: data.username,                message: [],                dataUrl: [],                touXiangUrl: data.touXiangUrl            });            //保存socket            _sockets[socket.username] = socket;            //然后触发loginSuccess事件告诉浏览器登陆成功了,广播形式触发            data.userGroup = users;         //将所有用户数组传过去            io.emit('loginSuccess',data);   //将data原封不动的再发给该浏览器        }    });    /**     * 监听sendMessage,我们得到客户端传过来的data里的message,并存起来。     * 我使用了ES6的for-of循环,和ES5 的for-in类似。     * for-in是得到每一个key,for-of 是得到每一个value     */    socket.on('sendMessage',(data)=>{        for(let _user of users) {            if(_user.username === data.username) {                _user.message.push(data.message);                //信息存储之后触发receiveMessage将信息发给所有浏览器                io.emit('receiveMessage',data);                break;            }        }    });    /**     * 仿照sendMessage监听sendImg事件     */    socket.on("sendImg",(data)=>{        for(let _user of users) {            if(_user.username === data.username) {                _user.dataUrl.push(data.dataUrl);                //存储后将图片广播给所有浏览器                io.emit("receiveImg",data);                break;            }        }    });    socket.on('sendToOne',(data)=>{        //判断该用户是否存在,如果存在就触发receiveToOne事件        for (let _user of users) {            if (_user.username === data.to) {                _sockets[data.to].emit('receiveToOne',data);            }        }    });    //断开连接后做的事情    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);       //找到该用户,删除            }        })    })});

到这里,第三章的内容就结束了,该系列教程的项目源码已经上传至github  https://github.com/neuqzxy/chat   欢迎浏览拷贝,如果觉得不错,给个星星吧吐舌头


阅读全文
0 1
原创粉丝点击