前言
上篇我们讲了如何使用vue + websocket + nodejs搭建一个聊天室,
下面我们在该聊天室基础上增加了一对一单聊功能。支持一对一单聊,一对多群聊。

先看效果:

在这里插入图片描述
在这里插入图片描述
大概思路:
主要通过参数brige区分是群聊还是单聊:
brige为空是群聊;
brige包含了当前登录人的uid还有聊天对应人的uid,是单聊。

核心代码:

// 单聊
if (obj.brige && obj.brige.length) {
        obj.brige.forEach(item => {
            conns[item].sendText(JSON.stringify(obj))
        })
        return;
    }
// 群聊(目前是默认写死的一个群)
server.connections.forEach(function (conn) { // 注意:这里是server,不是ws
        conn.sendText(JSON.stringify(obj)) // 注意:这里得转成字符串发送过去,不然会报错。
    })   

流程图如下:

在这里插入图片描述
上代码:

服务端:

const ws = require('nodejs-websocket')
const moment = require('moment')
let users = []
let conns = {}

function broadcast(obj) {
    // 单聊
    if (obj.brige && obj.brige.length) {
        obj.brige.forEach(item => {
            conns[item].sendText(JSON.stringify(obj))
        })
        return;
    }
    server.connections.forEach(function (conn) {
        conn.sendText(JSON.stringify(obj))
    })
}
const server = ws.createServer(function (conn) {
    conn.on('text', function (data) {
        const obj = JSON.parse(data)
        switch (obj.type) {
            case 1:
                {
                    // 将所有uid对应的连接都保存到一个对象里
                    conns[obj.uid] = conn;
                    
                    // 不存在uid对应的用户(不是本人),才会添加,避免重复
                    const isSelf = users.some(m => m.uid === obj.uid)
                    console.log(isSelf, data.uid, users, '所有用户')
                    if (!isSelf) {
                        users.push({
                            nickname: obj.nickname,
                            uid: obj.uid
                        })
                    }
                    broadcast({
                        type: 1,
                        nickname: obj.nickname,
                        uid: obj.uid,
                        msg: `${obj.nickname}进入了聊天室`,
                        date: moment().format('YYYY-MM-DD HH:mm:ss'),
                        users,
                        brige: obj.brige
                    })
                }
                break;
            case 2:
                // 聊天时候不需要users,type为1已经处理了
                broadcast({
                    type: 2,
                    nickname: obj.nickname,
                    uid: obj.uid,
                    msg: obj.msg,
                    users,
                    date: moment().format('YYYY-MM-DD HH:mm:ss'),
                    brige: obj.brige
                })
                break;
        }
    })

    conn.on('close', function (e) {
        console.log(e, '服务端连接关闭')
    })

    conn.on('error', function (e) {
        console.log(e, '服务端异常')
    })

}).listen(8888)
console.log('服务端已开启')

客户端:

视图层:(由之前的class为right改为现在web-im。多加了左侧菜单栏,其他地方跟上篇一致)

<div class="web-im">
      <div class="left">
        <div class="user" @click="triggerGroup()">
          群一
        </div>
        <div class="user" v-for="(itm, idex) in users" :key="idex" v-show="itm.uid !== uid" @click="triggerUser(itm)">
          <span>{{itm.nickname}}</span>
          </div>
      </div>
      <div class="right">
        <div class="body im-record" id="im-record">
          <p>{{title}}</p>
          <div class="ul">
            <!-- user为靠右展示样式,如果uid一致说明是本人 -->
            <div class="li" :class="{user: item.uid == uid}" v-for="(item, index) in currentMessage" :key="index">
              <template v-if="item.type===1">
                <p class="join-tips">{{item.msg}}</p>
              </template>
              <template v-else>
                <p class="message-date">
                  <span class="m-nickname">{{item.nickname}}</span> {{item.date}}</p>
                <p class="message-box">{{item.msg}}</p>
              </template>
            </div>
          </div>
        </div>
        <div class="im-footer">
          <el-input placeholder="请输入你想说的内容..." v-model="msg" class="im-footer_inp"/>
          <el-button class="im-footer_btn" type="primary" @click="send">发送</el-button>
        </div>
      </div>
    </div>

逻辑层:(之前是聊天框数组是messageList,现在改成currentMessage
(注:…表示代码跟之前一致,这里不再多写)

  computed: {
    // 筛选当前brige一致的放到一个聊天数组里,区分单聊和群聊
    currentMessage () {
      const vm = this
      let data = this.messageList.filter(item => {
        return item.brige.sort().join('') === vm.brige.sort().join('')
      })
      return data
    }
  },
  
  ...
  
  // 发送信息给客户端
    sendMessage (type, msg) {
      const data = {
        uid: this.uid,
        type,
        nickname: this.nickname,
        msg,
        users: this.users,
        brige: this.brige
      }
      this.ws.send(JSON.stringify(data))
      this.msg = ''
    },
	
	// 切换到单聊
    triggerUser (itm) {
      this.brige = [this.uid, itm.uid]
      this.title = `和${itm.nickname}聊天`
    },
	
	// 切换到群聊
    triggerGroup () {
      this.brige = []
      this.title = '群聊'
    },

样式层:

.web-im {
  display: flex;
}
  .left {
    width: 200px;
    border: 1px solid #ccc;
    .user {
      width: 100%;
      height: 36px;
      padding-left: 10px;
      border-bottom: 1px solid #ccc;
      line-height: 36px;
      .msgtip {
        display: inline-block;
        width: 20px;
        height: 20px;
        background: #46b0ff;
        margin-left: 5px;
        text-align: center;
        color: #fff;
        line-height: 20px;
        border-radius: 50%;
      }
    }
    }
  .right {
      position: relative;
      flex: 1;
      height: 600px;
      margin: 0 auto;
      .im-title {
        height: 30px;
        padding-left: 20px;
        border-bottom: 1px solid #ccc;
        line-height: 30px;
        font-size: 16px;
      }
      .im-footer {
        position: absolute;
        bottom: 0;
        left: 0;
        display: flex;
        width: 100%;
        .im-footer_inp {
          width: 80%;
        }
        .im-footer_btn {
          width: 20%;
        }
      }
      
      .im-record {
        width: 100%;
        height: 540px;
        overflow-y: auto;
        .join-tips {
          position: relative!important;
          display: block;
          width: 100%;
          left: 0!important;
          transform: none!important;
          color: #cccccc;
          font-size: 15px;
          text-align: center;
        }
        .li {
          position: relative;
          margin-bottom: 15px;
          text-align: left;
          color: #46b0ff;
          &:after {
            content: '';
            display: block;
            clear: both;
          }
          .message-date {
            font-size: 16px;
            color: #b9b8b8;
          }
          .m-nickname {
            color: #46b0ff;
          }
          &.user {
            text-align: right;
          }
        }
        .message-box {
          line-height: 30px;
          font-size: 20px;
        }
      }
    }

参考链接:Nodejs + WebSocket + Vue 一对一、一对多聊天室 – 第三章

Logo

致力于链接即构和开发者,提供实时互动和元宇宙领域的前沿洞察、技术分享和丰富的开发者活动,共建实时互动世界。

更多推荐