美文网首页
自制聊天系统

自制聊天系统

作者: 我是上帝可爱多 | 来源:发表于2017-08-04 17:59 被阅读53次

    今天是星期五,周末又到了,又是一个人在家路代码的黄金时期,这次给大家分享一个用js做的在线聊天界面。

    1 参数设置

    var jid = "";   // 当前登录的JID  
    
    var current_target_name = '';   // 当前聊天对象
    var last_target_name = '';   // 上次聊天对象
    
    var max_msg_count = 50;   // 最大储存信息数量
    var isFirst = true;   // 是否首次登入
    
    //通讯录
    var friends_name_arr = [];//name1,name2...  这个数组是装好友名字的  ['张三',‘李四’]
    //记录好友上线状态
    var online_friends_arr = [];//{name1,status1},{name2,status2} ....  对象包括名字和状态(在线,离线)
    
    // 在线好友和离线好友的数组
    var online_friends_names = [];
    var offline_friends_names = [];
    
    //登陆用户与密码
    var username_str='';   // 这是啥 后面会解释
    var username = '';
    var password = '';
    
    //消息盒子 用于保存离线消息(不在线时,受到的好友信息会放到这里)
    var message_box = [];
    

    相信通过上面的参数大家已经看出来了这是哪一处戏,无非就是一个即使聊天的工具。

    2 Html页面

     <!-- 好友列表区域 -->
            <div class="user-list-canvas">
                <!-- 搜索区域 -->
                <div class="search-canvas">
                    <div class="search-img"></div>
                    <input class="search-tool" type="text" placeholder="搜索">
                </div>
                <!-- 列表区域 -->
                <ul class="list-canvas">
                    
                </ul>
                <script type="text/template" id="friend-row">
                    <li class="friend-canvas {name}">  // 在每个li上标记该好友的名字
                        <div class="friend-logo">
                            <span class="friend-logo-lab">{logo}</span>
                            <span class="noread-num num-{noReadNum}">{noReadNum}</span>  //未读消息数量
                        </div>
                        <div class="friend-info">
                            <span class="username" style="display: none">{name}</span>
                            <span class="nickName">{nickName}</span>  //昵称
                            <span class="last-message">{lastMessage}</span>  //受到的最后一条消息
                        </div>
                    </li>
                </script>
            </div>
    
    1. .search-tool是一个搜索框,可以进行选择。
    2. .list-canvas 是好友列表
      可能有人看不懂这个script里面写的是啥,其实就是一个简单的模板用于渲染数据,后面会说明。
    <!-- 聊天区域 -->
            <div class="chat-content-canvas">
                <div class="chat-content-title">
                    <!-- 记录当前聊天对象 -->
                    <div class="current-name"></div>
                    <div class="status-logo" style="display: none;"></div>
                    <div class="current-status" style="display: none;"></div>
                    <div class="clear-content"></div>
                </div>
                 <!-- 记录当前聊天内容 -->
                <div class="chat-content-panel">
                </div>
    
                <!-- 发出信息模板 -->
                <script type="text/template" id="send-message-model">
                    <div class="message-row">
                        <div class="send-message-style">{txt}</div>
                        <div class="message-info send-message-info">
                            <span>{date}</span>
                            <span></span>
                        <div>
                    </div>
                </script>
                <!-- 接收信息模板 -->
                <script type="text/template" id="receive-message-model">
                    <div class="message-row">
                        <div class="receive-message-style">{txt}</div>
                        <div class="message-info receive-message-info">
                            <span>{date}</span>
                            <span></span>
                        <div>
                    </div>
                </script>
    
            </div>
            <!-- 书写区域 -->
            <div class="send-content-canvas">
                <textarea class="message-canvas" placeholder="请输入要发送的信息"></textarea>
                <div class="send-button">发送</div>
            </div>
        </div>
    

    .send-content-canvas是书写区域,点击发送会把消息发出去,渲染到发送消息的模板。
    好了,页面就是这样的,已经很简洁了,下面来看功能实现的代码。

    3 Js代码

    // 连接状态改变的事件  
    function onConnect(status) {  
        if (status == Strophe.Status.CONNFAIL) {  
            alert("连接失败!");  
        } else if (status == Strophe.Status.AUTHFAIL) {  
            alert("登录失败!"); 
        } else if (status == Strophe.Status.DISCONNECTED) {  
            loginOutFunc();
        } else if (status == Strophe.Status.CONNECTED) {  
            loginInFunc();
        }  
    }
    

    上面展示了几种状态的函数执行,我们来看看登录后如何操作。

    //用户登陆
    function loginInFunc(){
        connected = true; 
        connection.send($pres().tree());
    
        $('#btn-login').html('断开');   //这是一个切换按钮,用于登录和登出。
        $("#chat-panel").show();     // 聊天面板展开
        $("#loading-background").hide();   //loading效果消失
    
        // 当接收到<message>节,调用onMessage回调函数  
        connection.addHandler(onMessage, null, 'message', null, null, null);
        // 当接收到<presence>节,调用onPresenceOn回调函数  
        connection.addHandler(onPresenceOn, null, 'presence', null, null, null);   **这2步先不管**
    
        //上线状态
        changeOnlineStatus('online');
        //获取好友列表
        get_roster();
    }
    

    登录后会拉取好友信息渲染,简单说渲染之后就会直到那些好友在线,那些不在线。

    // 好友列表
    var friend_list_data = [];   里面存放的是person
    var friend_arr = [];
    function onRoster(iq) {
        $(".list-canvas").html('');
        friends_name_arr = [];
        friend_list_data = [];
    
        $(iq).find('item').each(function () {
            var jid = $(this).attr('jid'),
                // name = $(this).attr('name')!=undefined ? $(this).attr('name') : jid.substring(0,jid.indexOf('@')),
                name = jid.substring(0,jid.indexOf('@')),
                nickName = $(this).attr('name')?$(this).attr('name'):name;
    
                //构建了一个person对象
                var person = new Person(name.charAt(0).toUpperCase() , name , nickName , 'offline');
    
                $.each(message_box, function(index, val) {
                    if(val.from == name){
                        person.addNoReadNum();
                    }
                });
    
                friend_list_data.push(person);
    
                //记录好友name数组
                friends_name_arr.push(name);
        });
        //数据都是通过iq传递过来的
    
        //初始化,获取未上线的好友名称数组
        $.each(friends_name_arr, function(index, val) {
             if(online_friends_names.indexOf(val) == -1){
                offline_friends_names.push(val);
             }
        });
        offline_friends_names.sort();//排序
    
        //填充好友
        fillFriendTemplate();
        update_friend_status();
        return true;
    }
    

    $.each(message_box, function(index, val) {
    if(val.from == name){
    person.addNoReadNum();
    }
    }); 遍历离线消息,如果该名字相等,这个人的未读信息加1.

    $.each(friends_name_arr, function(index, val) {
    if(online_friends_names.indexOf(val) == -1){
    offline_friends_names.push(val);
    }
    }); 将所有好友名单,分成online和offline的。

    问题来了:现在我们已经获得在线的friend_list_data和friend_name_arr,那么如何将它渲染到页面上呢,还有好友的的状态会改变的,在线的会下线,离线的会上线,如何实现实时监测。

    先来看模板渲染把

    //填充模板 填充好友模板
    //friend_list_data ==> Person {logo: "A", name: "alice", status: "offline", lastMessage: "", message_arr: Array[0]}
    function fillFriendTemplate(){
        // 获取好友模板html
        var html = $("#friend-row").html();
        friend_arr = [];
    
        //对数组进行排序  按名字进行排序  by是一个排序函数  这里不贴出来了
        friend_list_data.sort(by("name"));
    
        $.each(friend_list_data, function(index, val) {
            friend_arr.push(formatTemplate(val , html));
        });
    
        // 这一步把.list-canvas的好友列表渲染出来了
        $('.list-canvas').append(friend_arr.join(''));
    
        //渲染最后一条信息
        $.each(friends_name_arr, function(index, val) {
            var key = val<username_str ? (val+"_"+username_str) : (username_str+"_"+val);
            var messages = getLocalMessages(key);
            if(messages){
                $("."+val).find('.last-message').html(messages[0].txt);
            }
        });
    }
    

    我们来看看formatTemplate干了啥

    function formatTemplate(dta, tmpl) {  
        var format = {  
            name: function(x) {  
                return x  
            }  
        };  
        return tmpl.replace(/{(\w+)}/g, function(m1, m2) {  
            if (!m2)  
                return "";  
            return (format && format[m2]) ? format[m2](dta[m2]) : dta[m2];  
        });  
    }
    

    这是一个很好的模板替换函数,还记得之前的script里面写html吗,就是通过这种方式替换的。

    $.each(friends_name_arr, function(index, val) {
    var key = val<username_str ? (val+""+username_str) : (username_str+""+val);
    var messages = getLocalMessages(key);
    if(messages){
    $("."+val).find('.last-message').html(messages[0].txt);
    }
    }); 这一步是把每个好友和自己最后一次聊天的内容放到.last-message里面

    这里我们用到了localstorage缓存,看看是如何写的把

    //获取本地消息
    function getLocalMessages(name){
        return JSON.parse(localStorage.getItem(name));
    }
    

    非常简单的一段代码

    接下来我们看看怎么实现上下线的监听,有点像qq呀

    //监听presence
    function onPresenceOn(pres){
        var from_jid = $(pres).attr('from');
        var name = from_jid.substring(0,from_jid.indexOf('@'));
        var status = $(pres).find('status').text();
    
        //好友下线
        if($(pres).attr('type') == "unavailable"){
            ********
        }else{
            ********
        }
    
        return true;
    }
    

    还记得这段代码么

    // 当接收到<presence>节,调用onPresenceOn回调函数  
        connection.addHandler(onPresenceOn, null, 'presence', null, null, null);  
    

    就是在这个代码里面我们能听到qq里面的敲门声。其实就是监听好友上下线的。。

    //好友下线
        if($(pres).attr('type') == "unavailable"){
            var dom_str = '.'+name;
            //修改好友状态-下线
            update_status_action(name , 'offline');  
            //更新上下线数组
            if(offline_friends_names.indexOf(name) == -1){
                online_friends_names.splice(online_friends_names.indexOf(name) , 1);
                offline_friends_names.push(name);
                offline_friends_names.sort();
            }
            if($(dom_str).hasClass('selected-friend')){
                $(dom_str).removeClass('selected-friend');
        }
    

    上面是这种情况,当前选中的好友的如果下线,会把select-friend类去掉。

    /好友上线
           
            online_friends_arr.push({'name':name , 'status':status});
            //更新上下线数组
            if(online_friends_names.indexOf(name) == -1 && (name!=username_str)){
                offline_friends_names.splice(offline_friends_names.indexOf(name) , 1);
                //记录上线好友名称
                online_friends_names.push(name);
                online_friends_names.sort();//排序
           }
          update_status_action(name,status);
    

    还记得我们之前提到的messagebox把,它记录了所有的离线消息,那么设想这样一个情景:
    好友A不在线,我给他发了一条信息,那么这条信息将会被存入到messagebox,这就是所谓的离线缓存。继续看
    message_box = [{from:from_name , to:to_name , txt:txt_msg , date:date}] 谁发的,发给谁,信息内容,时间

    function onMessage(msg) {
        // 解析出<message>的from、type属性,以及body子元素  
        var from = msg.getAttribute('from');
        var from_name = from.substring(0,from.indexOf('@'));
    
        var to = msg.getAttribute('to');
        var to_name = to.substring(0,to.indexOf('@'));
    
        var type = msg.getAttribute('type');
        var elems = msg.getElementsByTagName('body');
    
        var body = elems[0];
        var txt_msg = Strophe.getText(body);
    
        var date = new Date().format("yyyy-MM-dd hh:mm:ss");
    
        //离线 保存至信息箱
        if(type == "chat"){
            if(friend_list_data.length == 0){
                message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
            }else{
                if(elems.length > 0){
                    var person = findPerson(from_name);
                    
                    person.message_arr.push({from:from_name , to:to_name , txt:txt_msg , date:date});
    
                    //消息已收到
                    connection.send($msg({to: from}).c('received', {xmlns: 'urn:xmpp:receipts'}));
                }
                //判断是否是当前聊天窗口对象发送过来的信息
                update_message_action({from:from_name , to:to_name , txt:txt_msg , date:date} , (current_target_name===from_name?true:false));
            }
    
            localSaveMessage({from:from_name , to:to_name , txt:txt_msg , date:date});
        }else{
            // 信息反馈
        }
        return true;
    }
    

    onMessage这个东西很眼熟把,就像socket接收到message一样。

    var from = msg.getAttribute('from');
    var from_name = from.substring(0,from.indexOf('@')); 发送消息人的name
    var to = msg.getAttribute('to');
    var to_name = to.substring(0,to.indexOf('@')); 发给谁
    var type = msg.getAttribute('type'); // 消息发送类型
    var elems = msg.getElementsByTagName('body');
    var body = elems[0];
    var txt_msg = Strophe.getText(body); // 消息内容
    var date = new Date().format("yyyy-MM-dd hh:mm:ss");

    上面代码有一处位置是错的

    if(friend_list_data.length == 0){
                message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
            }
    

    好友列表为0的时候,往离线盒子推送,显然是错的。这里应该判断to_name的状态是否离线,离线就发送到离线邮箱。

    // 查找好友(非搜索)
    function findPerson(name){
        var idx = -1;
        $.each(friend_list_data, function(index, val) {
            if(val.name == name){
                idx = index;
            }
        });
        return friend_list_data[idx];
    }
    if(findPerson(to_name).getStatus() == 'offline'){
           message_box.push({from:from_name , to:to_name , txt:txt_msg , date:date});
    }
    
    

    如果是在线好友,就不用发送到离线信箱,进行下面的操作即可。

    //消息渲染
    function update_message_action(msg , flag){
        var html = '';
        
        // flag判断消息是否来自当前聊天对象
        if(flag){
            // 发出信息   send-message-model 
            if(msg.from == username_str){
                html = $('#send-message-model').html();
                var msg_context = formatTemplate(msg , html);
                $('.chat-content-panel').append(msg_context);
                $("."+msg.to).find('.last-message').html(msg.txt);   // 发给谁,谁的最后一条消息就是这个
            }else{
            // 收到信息  receive-message-model
                html = $('#receive-message-model').html();
                var msg_context = formatTemplate(msg , html);
                $('.chat-content-panel').append(msg_context);
                $("."+msg.from).find('.last-message').html(msg.txt);
            }
            //滚动条置底
            $('.chat-content-panel').get(0).scrollTop = $('.chat-content-panel').get(0).scrollHeight;
        }else{
            var person = findPerson(msg.from);
            person.addNoReadNum();
            $("."+msg.from).find('.noread-num').html(person.getNoReadNum());
            $('.'+person.name).find('.noread-num').removeClass('num-0');
            $("."+msg.from).find('.last-message').html(msg.txt);
        }
    }
    

    无论发送还是接收,$('.chat-content-panel')都会追加内容。由于写作匆忙,很多位置还未更正,下回我会同步到github上,供大家下载。

    相关文章

      网友评论

          本文标题:自制聊天系统

          本文链接:https://www.haomeiwen.com/subject/juivlxtx.html