Node.js(Express4.x)搭建聊天室3——完善網(wǎng)頁

0.目標與前置條件

這一節(jié)琅绅,我將完全實現(xiàn)聊天室的全部頁面顯示和響應(yīng)奏赘。

Socket.io聊天室Demo

這一節(jié)內(nèi)容是在上兩節(jié)完成的情況下進行的,請先參照第一節(jié)完成基本框架的搭建:
Node.js(Express4.x)搭建聊天室1——基本框架

并參照第二節(jié)添加幾個監(jiān)聽:
Node.js(Express4.x)搭建聊天室2——消息發(fā)送與監(jiān)聽


我把搭建聊天室的步驟分成了幾個部分蛮浑,請按順序閱讀:

獲取代碼
Node.js(Express4.x)搭建聊天室1——基本框架
Node.js(Express4.x)搭建聊天室2——消息發(fā)送與監(jiān)聽
Node.js(Express4.x)搭建聊天室3——完善網(wǎng)頁


1.服務(wù)端

1.1 chatroom.js

在之前的幾節(jié)中迎献,我們已經(jīng)搭建了chatroom的簡易版,但如果進入的用戶昵稱重復(fù)了值朋,我們也不能作出判斷和處理叹侄;此外,當(dāng)用戶修改昵稱時昨登,也有可能出現(xiàn)用戶昵稱重復(fù)的情況趾代。(在上一節(jié),我把它定義為了一個對象)

所以丰辣,在這一節(jié)撒强,我增加了一個數(shù)組來存儲用戶昵稱。

var userlist = new Array();

在用戶加入聊天時笙什,將昵稱存入該數(shù)組飘哨,如果用戶的昵稱已存在,則在此昵稱后增加一個隨機數(shù)來保證昵稱不同得湘。

  /* *************** 用戶emit消息"join"時杖玲,響應(yīng) *************** */
  socket.on('join', function (username) {
    if (addedUser) return;
          
    // 用戶信息存儲在socket會話中:在此之前,要檢查是否重復(fù)
    for(var i=0; i<userlist.length; i++) {
        if(userlist[i] == username) {
            username = username+Math.ceil(Math.random()*10000);
            break;
        }
    }
    
    ...
        
    userlist.push(username)  // 將昵稱加入數(shù)組
        
    ...

  });

在用戶修改昵稱時淘正,在上一節(jié)是直接將socket.name替換為新的昵稱的摆马。而現(xiàn)在,首先檢查數(shù)組中是否存在這個昵稱鸿吆,如果沒有囤采,則替換,否則提示用戶修改失敗惩淳。

/* *************** 更改昵稱 *************** */
  socket.on('change_name', function (newname) {
    if (addedUser) {
        var oldname = socket.username;
        
        // ************************** 這里開始本節(jié)更新 ************************** 
        for(var i=0; i<userlist.length; i++) {
            if(userlist[i] == newname) {
                // 通知該用戶修改成功
                socket.emit('name_changed_msg', {
                    res: "failed",
                    error: "已有此用戶:"+newname,
                    oldname: oldname,
                    newname: newname,
                    type: "RETURN"
                });
                return -1;
            }
        }
        // 通知該用戶修改成功
        socket.emit('name_changed_msg', {
            res: "success",
            error: null,
            oldname: oldname,
            newname: newname,
            msg: "["+oldname+"] 改名為 ["+socket.username + "]",
            type: "RETURN",
            numUsers: guest_num
        });
            
        for(var i=0; i<userlist.length; i++) {
            if(userlist[i] == oldname) {
                userlist[i] = newname;
                socket.username = newname;
            }
        }
        // ************************** 這里結(jié)束本節(jié)更新 ************************** 

        // 告知所有用戶
        socket.broadcast.emit('name_changed', {
            username: newname,
            msg: "["+oldname+"] 改名為 ["+socket.username + "]",
            type: "BROADCAST",
            numUsers: guest_num
        });
    }
  });

此外蕉毯,為了維護昵稱數(shù)組乓搬,還需要在用戶離開時,將離開的用戶剔除出昵稱數(shù)組代虾。為了達到這個目的进肯,我增加了一個函數(shù)來實現(xiàn):

// 移除數(shù)組元素
var removeArr = function(arr, ele) {
    var new_arr = new Array();
    for(var i=0; i<arr.length; i++) {
        if(ele != arr[i]) {
            new_arr.push(arr[i])
        }
    }
    return new_arr;
}

用戶離開聊天室:

/* *************** 用戶離開 *************** */
  socket.on('disconnect', function () {

      ...

      // 將離開的用戶昵稱移出數(shù)組  
      userlist = removeArr(userlist, socket.username)

      // 告知所有用戶
      ...

    }
  });

1.2 路由

在上一節(jié),我們直接就在index頁面進行操作了棉磨。這一節(jié)江掩,我把index界面改為了一個輸入用戶昵稱的界面,然后跳轉(zhuǎn)到一個新界面other乘瓤。要在routes/index.js中增加一個路由:

router.get('/other', function(req, res, next) {
  res.render('other', { title: 'Express' });
});

2. 客戶端

2.1 更改index.jade頁面

doctype html
html
    head
        title= title
        link(rel='stylesheet', href='/stylesheets/style.css')
    body
        h1 歡迎使用socket.io聊天室
        form(method='get' action='/other')
            input(id='name' name='name' placeholder='輸入您的名字')
            input(type='submit' value='進入聊天室')

2.2 新增other.jade頁面

然后在views/index文件夾下創(chuàng)建一個other.jade文件:

doctype html
html
    head
        title= title
        link(rel='stylesheet', href='/stylesheets/style.css')
        link(rel='stylesheet', href='/stylesheets/bootstrap.css')
    body
        h1 socket.io聊天室
        p
            span#status
            span 环形,
            span#roomstatus
        p#notice.notice
        
        a(href='/')
            [退出] 聊天室
        
        hr
        
        div
            h3 聊天記錄
        
        div.scrollbar#msg.msgbox
        
        hr
        div
            textarea(id='msgsend' name='msgsend' placeholder='輸入消息' rows='4').form-control
        br
        div
            a.btn.btn-primary(onclick="OL_SendMsg()") 發(fā)送
        hr
        form.form-inline
            div.form-group
                input.form-control(id='newnickname' placeholder='新昵稱')
                a.btn.btn-danger(onclick="OL_ModifyNickName()") 修改昵稱
        
        hr
        h3 系統(tǒng)消息
        div#history
        
    script(src='/javascripts/jquery.min.js')
    script(src='https://cdn.socket.io/socket.io-1.4.5.js')

這里我們引用了一個Bootstrap的css文件,請自行下載衙傀,并放入public/stylesheets文件夾中抬吟。

另外,我們還需要對css文件進行一下替換:

body {
  padding: 50px;
  font: 14px "Microsoft Yahei", Helvetica, Arial, sans-serif;
}

a {
  color: #00B7FF;
}

.msgbox {
  height:300px; 
  overflow-x:auto; 
  overflow-y:auto; 
  border:1px #ccc solid; 
  border-radius:5px; 
  background:#fff; 
  padding:14px 20px;
}
.notice {
  color:#EF0000; 
  font-weight:bold;
}
.time {
  float:right; 
  color:#999;
}
.mymsg {
  color:#2289DB;
  font-weight:bold;
}

/* 滾動條 */
.scrollbar::-webkit-scrollbar-track
{
  background-color: #e1e1e1;
}
.scrollbar::-webkit-scrollbar
{
  width: 10px;
  background-color: #e1e1e1;
}
.scrollbar.shortscroll::-webkit-scrollbar
{
  width: 8px;
  background-color: #e1e1e1;
}
.scrollbar::-webkit-scrollbar-thumb
{
  background-color: #888;   
}

2.3 other.jade頁面的js代碼

在other.jade頁面中统抬,加入一些js代碼火本。

首先,加入基本功能函數(shù)蓄喇,用于此頁面的一些基礎(chǔ)功能

script.
        // 基本功能函數(shù)
        function ol_pad(num, n)
        { 
            num = ""+num
            var temp = num;
            
            for(var i=0;i<(n-num.length);i++)
            {
                temp = "0"+temp
            }   
            return temp
        }
        function GetRequest() { 
            var url = location.search; //獲取url中"?"符后的字串 
            var theRequest = new Object(); 
            if (url.indexOf("?") != -1) { 
                var str = url.substr(1); 
                strs = str.split("&"); 
                for(var i = 0; i < strs.length; i ++) { 
                    theRequest[strs[i].split("=")[0]]=unescape(strs[i].split("=")[1]); 
                } 
            } 
            return theRequest; 
        } 
        function GetDateTime() {
            var obj = new Date();
            return (obj.getFullYear()+"/"+ol_pad(obj.getMonth()+1, 2)+"/"+ol_pad(obj.getDate(), 2)+" "+ol_pad(obj.getHours(),2)+":"+ol_pad(obj.getMinutes(),2)+":"+ol_pad(obj.getSeconds(),2));
        }
        

發(fā)送聊天信息后发侵,觸發(fā)的一些響應(yīng),包括發(fā)送消息妆偏、在聊天框中顯示刃鳄、清空輸入框等。

script.
        // 發(fā)送聊天信息
        function OL_CleanInput() {
            var obj = document.getElementById('msgsend');
            obj.value = "";
        }
        function OL_ScrollChatWin() {
            var obj = document.getElementById('msg');
            obj.scrollTop = obj.scrollHeight;
        }
        function OL_SentAction() {
            OL_ScrollChatWin();
            OL_CleanInput();
        }
        function OL_CleanNotice() {
            document.getElementById("notice").innerHTML = "";
        }
        function OL_SendMsg() {
            var msg = document.getElementById("msgsend").value;
            if(""==msg) {
                alert("消息不能為空钱骂!")
                return -1;
            }
            
            send_msg(msg);
            
            document.getElementById("msg").innerHTML += "<p class='mymsg'>"+G_Name+": "+msg+"<span class='time'>"+GetDateTime()+"</span></p>";
            
            OL_SentAction();
        }
        

修改昵稱后的響應(yīng)

script.
        // 修改昵稱
        function OL_ModifyNickName() {
            var newnickname = document.getElementById("newnickname").value;
            if(""==newnickname) {
                alert("新昵稱不能為空叔锐!")
                return -1;
            }
            
            change_name(newnickname);
            
            document.getElementById("newnickname").value = "";
        }

顯示系統(tǒng)公告

script.
        // 通知
        var NoticeTimer = null;
        function OL_ShowNotice(msg, second) {
            NoticeTimer = null;
            document.getElementById("notice").innerHTML = "[消息] "+msg;
            NoticeTimer = setTimeout("OL_CleanNotice()", second*1000)
            
            var history = document.getElementById("history");
            history.innerHTML = "<p>[消息] "+msg+"<span class='time'>"+GetDateTime()+"</span></p>" + history.innerHTML
        }

這部分是根據(jù)上一節(jié)index.jade的socket.io客戶端代碼進行修改后的內(nèi)容:

script.
        ////////////////////////////////////////////////////////////////////
        //啟動
        var socket = io.connect('http://127.0.0.1:3000');
        
        //發(fā)送消息
        var Request = new Object(); 
        Request = GetRequest();     
        var G_Name = Request["name"];
        if(null==G_Name) {
            G_Name = "訪客"+Math.ceil(Math.random()*10000);
        }
        socket.emit('join', G_Name, function (data) {
            console.log(data);
        });
        
        //監(jiān)聽
        socket.on('login', function (data) {
            console.log(data);
            // 如果有重名的,要更改一個隨機名稱
            G_Name = data.username;
            document.getElementById("status").innerHTML = "歡迎您见秽!"+G_Name;
            document.getElementById("roomstatus").innerHTML = "當(dāng)前聊天有"+data.numUsers+"人";
        });
        
        socket.on('user_joined', function (data) {
            console.log(data);
            OL_ShowNotice(data.msg, 3);
            document.getElementById("roomstatus").innerHTML = "當(dāng)前聊天有"+data.numUsers+"人";
        });
        
        socket.on('user_left', function (data) {
            console.log(data);
            OL_ShowNotice(data.msg, 3);
            document.getElementById("roomstatus").innerHTML = "當(dāng)前聊天有"+data.numUsers+"人";
        });
        
        //修改昵稱
        function change_name(name){
            socket.emit('change_name', name, function (data) {
                console.log(data);
            });
        }
        // 監(jiān)聽修改昵稱后返回的消息
        socket.on('name_changed', function (data) {
            console.log(data);
            document.getElementById("status").innerHTML = "歡迎您愉烙!"+G_Name;
            OL_ShowNotice(data.msg, 3);
        });
        // 監(jiān)聽修改昵稱后返回給修改者的消息
        socket.on('name_changed_msg', function (data) {
            console.log(data);
            if("success"==data.res) {
                document.getElementById("status").innerHTML = "歡迎您!"+data.newname;
                OL_ShowNotice(data.msg, 3);
            }
            else {
                OL_ShowNotice("修改昵稱失斀馊 步责!"+data.error, 3);
            }
        });
        
        //發(fā)送消息
        function send_msg(msg){
            socket.emit('send_msg', msg, function (data) {
                console.log(data);
            });
        }
        // 監(jiān)聽消息
        socket.on('msg_sent', function (data) {
            console.log(data);
            
            document.getElementById("msg").innerHTML += "<p>"+data.username+": "+data.msg+"<span class='time'>"+GetDateTime()+"</span></p>";
            OL_ScrollChatWin();
        });

3.演示

運行應(yīng)用(supervisor bin/www 或 node bin/www)

打開兩個瀏覽器,進入127.0.0.1:3000


輸入不同的用戶昵稱后禀苦,進入聊天室:

輸入不同昵稱

先進入的用戶在其他用于進入時蔓肯,會收到系統(tǒng)公告:

新用戶加入的公告

如果用戶昵稱與之前的重名,將會:

昵稱重名的情況

用戶可以更改昵稱振乏,如果成功蔗包,會收到提示;其他用戶也會通過公告的形式收到提醒慧邮。

更改昵稱成功

如果失敗调限,用戶會收到提示

更改昵稱失敗

用戶聊天時舟陆,在輸入框中輸入消息,點擊發(fā)送后耻矮,在聊天記錄面板中會有對應(yīng)的顯示秦躯。

Socket.io聊天室Demo

當(dāng)一個用戶離開聊天室了,其他用戶會收到消息:

用戶離開聊天室

所有的系統(tǒng)公告會保留在底部:

系統(tǒng)公告

結(jié)語

至此淘钟,一個相對飽滿一些的聊天室就搭建好了宦赠。當(dāng)然,即使“相對飽滿”颗圣,依然是很簡陋的聊天室芬迄。接下來如果要豐滿這個聊天室、乃至集成到我們的其他應(yīng)用中,還是有很多工作可以做的萎坷,比如:

  • 支持房間管理。用戶可以創(chuàng)建房間僧著,可以選擇進入某一個房間
  • 用戶管理既绩。用戶可以注冊帳戶、登錄帳戶丐谋,這個涉及到數(shù)據(jù)庫
  • 聊天記錄芍碧。保存聊天記錄
  • 圖片、文件發(fā)送号俐。允許用戶發(fā)送圖片或其他文件
  • ...

要做好一個聊天室并不容易泌豆,但如果我們把它分解成一個個獨立的分支,再逐一實現(xiàn)它吏饿,就不會那么茫然和不知所措了踪危。

最后,歡迎fork或star我的項目:

https://github.com/KKDestiny/chatroom.git


原創(chuàng)文章猪落,未經(jīng)許可贞远,請勿轉(zhuǎn)載
作者:Mike的讀書季
日期:2016.09.29

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市笨忌,隨后出現(xiàn)的幾起案子蓝仲,更是在濱河造成了極大的恐慌,老刑警劉巖官疲,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袱结,死亡現(xiàn)場離奇詭異,居然都是意外死亡袁余,警方通過查閱死者的電腦和手機擎勘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颖榜,“玉大人棚饵,你說我怎么就攤上這事煤裙。” “怎么了噪漾?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵硼砰,是天一觀的道長。 經(jīng)常有香客問我欣硼,道長题翰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任诈胜,我火速辦了婚禮豹障,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焦匈。我一直安慰自己血公,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布缓熟。 她就那樣靜靜地躺著累魔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪够滑。 梳的紋絲不亂的頭發(fā)上垦写,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機與錄音彰触,去河邊找鬼梯投。 笑死,一個胖子當(dāng)著我的面吹牛渴析,可吹牛的內(nèi)容都是我干的晚伙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼俭茧,長吁一口氣:“原來是場噩夢啊……” “哼咆疗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起母债,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤午磁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后毡们,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體迅皇,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年衙熔,在試婚紗的時候發(fā)現(xiàn)自己被綠了登颓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡红氯,死狀恐怖框咙,靈堂內(nèi)的尸體忽然破棺而出咕痛,到底是詐尸還是另有隱情,我是刑警寧澤喇嘱,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布茉贡,位于F島的核電站,受9級特大地震影響者铜,放射性物質(zhì)發(fā)生泄漏腔丧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一作烟、第九天 我趴在偏房一處隱蔽的房頂上張望愉粤。 院中可真熱鬧,春花似錦俗壹、人聲如沸科汗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怖亭,卻和暖如春涎显,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背兴猩。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工期吓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倾芝。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓讨勤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親晨另。 傳聞我的和親對象是個殘疾皇子潭千,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容