聊天室入門實戰(node,sockte.io實現)--第三章(在線成員列表及頭像顯示,單聊)

項目已經部署,請訪問: "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   歡迎瀏覽拷貝,如果覺得不錯,給個星星吧吐舌頭


發佈了59 篇原創文章 · 獲贊 49 · 訪問量 15萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章