Vue+WebSocket实现简单的客服聊天工具(1)

最近旧项目上需要新增客服聊天功能,原本有一个独立的聊天工具项目嵌套进去使用,打包时出现各种问题无法解决,只能重新写一个。开始之前,对于还没有接触过WebSocket的前端菜鸟的我来说还是有点恐惧的 ,ennn记录一下我的成果。

总结思路:

  1. 搜索查找WebSocket的介绍和使用
  2. 构思页面结构并落实到项目上
  3. 与后端沟通逻辑对接与实现

一、了解WebSocket

WebSocket是一种在客户端和服务器之间进行全双工网络通信的协议。它与传统的HTTP协议不同,WebSocket最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等的对话,而不需要频繁地建立和断开连接,而HTTP通信只能由客户端发起。聊天是需要双方互相沟通完成的事情,所以需要使用WebSocket来进行双向地发送和接收数据。

  • 客户端的使用(我项目上仅使用到的这些):
//首先创建WebSocket实例(执行下面语句之后客户端就会和服务器进行连接)
var ws = new WebSocket(URL)
//连接状态
ws.addEventListener('open', function (event) {
 console.log"连接成功后的回调函数")
});
ws.addEventListener('close', function (event) {
 console.log"连接关闭后的回调函数")
});
ws.addEventListener('error', function (event) {
 console.log"连接报错时的回调函数")
});
ws.addEventListener('message', function (event) {
 console.log"收到服务器数据后的回调函数")
});
//向服务端发送消息
ws.send('XXX')

二、页面结构整理与编写

布局为左侧使用了默认头像及用户列表,右侧为对话框及输入框(开始还以为聊天记录样式很难写紧张。。。)原本最左侧还有一列分类用不上就去掉了。 image.png 结构代码

<template>
  <div class="mess">
    <!--用户列表-->
    <div class="mess_user_list">
      <!--用户本人-->
      <div class="user">
        <img
          src="./asset/kefu.png"
          alt=""
          style="width: 40px; height: 40px; border-radius: 50%"
        />
        <span>{{ userName }}</span>
      </div>
      <!--其他用户-->
      <div class="user_list">
        <div
          v-for="(item, index) in userList"
          :key="index"
          class="user_list_item"
          @click="showmessdlog(item)"
          :style="{
            background: item.id === userIdActive ? '#c8c7c6' : '#ffffff',
          }"
        >
          <span class="user_name">{{ item.name }}</span>
          <div
            class="unreadMessages"
            v-if="item.unreadChat > 0 && userIdActive != item.id"
          ></div>
        </div>
      </div>
    </div>
    <!--有对话时,对话框-->
    <div v-if="acceptUser !== ''" class="mess_dialog">
      <!--对话框头部-->
      <div class="dlog_header">
        <span style="margin-left: 25px">{{ acceptUser }}</span>
      </div>
      <!--对话框内容-->
      <div class="dlog_content" id="mess_content" @scroll="scrollToBottom">
        <div
          v-for="(item, index) in messnowList"
          :key="index"
          class="dlog_content_item"
          style="margin-left: 5px"
        >
          <!--其他用户的消息展示-->
          <div v-if="item.msgType == 1" class="content_other">
            <span style="font-size: 18px"
              >{{ item.sendName }}
              <span style="font-size: 14px"> ( {{ item.time }} ) </span></span
            >
            <p
              style="margin: 0px; margin-top: 4px"
              class="content_other_bgd"
              v-if="item.msg"
            >
              {{ item.msg }}
            </p>
            <p>
              <img
                v-if="item.imgUrl"
                :src="item.imgUrl"
                alt=""
                style="width: 120px; height: 120px; border: 1px solid #e5e5e5"
              />
            </p>
          </div>
          <!--本用户的消息展示-->
          <div v-else class="content_me">
            <span style="font-size: 18px"
              ><span style="font-size: 14px"> ( {{ item.time }} ) </span
              >{{ item.sendName }}</span
            >
            <p
              style="margin: 0px; margin-top: 4px"
              class="content_me_bgd"
              v-if="item.msg"
            >
              {{ item.msg }}
            </p>
            <p>
              <img
                v-if="item.imgUrl"
                :src="item.imgUrl"
                alt=""
                style="width: 120px; height: 120px; border: 1px solid #e5e5e5"
              />
            </p>
          </div>
        </div>
      </div>
      <!--对话框底部-->
      <div class="dlog_footer">
        <div class="footer_content">
          <div class="footer_content_menu">
            <el-dropdown placement="top-start">
              <span class="el-dropdown-link">
                <img
                  src="./asset/face.png"
                  alt=""
                  style="width: 26px; height: 26px; margin-top: 2px"
                />
              </span>
              <el-dropdown-menu slot="dropdown">
                <div class="emojis">
                  <span v-for="key in emojis" :key="key" @click="mess += key">{{
                    key
                  }}</span>
                </div>
              </el-dropdown-menu>
            </el-dropdown>
            <div class="footer_content_menu_img">
              <el-upload
                class="avatar-uploader"
                :action="uploaderUrl"
                :show-file-list="false"
                :on-success="handleAvatarSuccess"
                :on-error="handleAvatarError"
                :before-upload="beforeAvatarUpload"
              >
                <i class="el-icon-picture-outline" color="#666666" />
              </el-upload>
            </div>
          </div>
          <el-input v-model="mess" type="textarea" :rows="5" />
          <el-button
            type="primary"
            style="float: right; margin-top: 5px"
            @click="Wssendmess"
            >发送</el-button
          >
        </div>
      </div>
    </div>
    <!--无对话时,对话框-->
    <div v-else class="mess_dialog_false">
      <span>暂无消息,请选择用户对象</span>
    </div>
  </div>
</template>

表情包的使用:(在网上找的表情包素材保存json引入使用)

<el-dropdown placement="top-start">
  <span class="el-dropdown-link">
    <img
      src="./asset/face.png"
      alt=""
      style="width: 26px; height: 26px; margin-top: 2px"
    />
  </span>
  <el-dropdown-menu slot="dropdown">
    <div class="emojis">
      <span v-for="key in emojis" :key="key" @click="mess += key">{{
        key
      }}</span>
    </div>
  </el-dropdown-menu>
</el-dropdown>

三、进行WebSocket连接及逻辑实现

  • 一进入页面就创建与WebSocket的连接
  • 连接成功后向服务端发送消息获取用户列表
  • 获取与某一用户聊天的记录列表
  • 将以上获取到服务端的数据进行处理及渲染

代码实现:

created() {
    this.ws = null;
    let token = `Bearer ${store.state.user.token.access_token}`;
    if (token) {
      this.ws = new WebSocket(
        "ws://" +
          process.env.VUE_APP_URL_chat +
          "/websocket?Authorization=" +
          token +
          "&wsType=2"
      );
    } else {
      Message.error(this.$t("errorMsg2"));
    }
  },
  mounted() {
    this.ws.addEventListener("open", this.handleWsOpen.bind(this), false);
    this.ws.addEventListener("close", this.handleWsClose.bind(this), false);
    this.ws.addEventListener("error", this.handleWsError.bind(this), false);
    this.ws.addEventListener("message", this.handleWsMessage.bind(this), false);
  },
  methods: {
    handleWsOpen() {
      console.log("WebSocket连接成功");
      let data = {
        fun_name: "CustomerList",
      };
      this.ws.send(JSON.stringify(data));
    },
    handleWsClose(e) {
      console.log("WebSocket2关闭", e);
    },
    handleWsError(e) {
      console.log("WebSocket2发生错误", e);
    },
    handleWsMessage(e) {
      console.log("WebSocket2收到消息:", JSON.parse(e.data));
      const data = JSON.parse(e.data);
      this.messnowList.push(data.data);       
    },
  }

注意:聊天记录数据的渲染,最新消息需要渲染到对话框的最底部并且将页面自动滚动到最新消息处,我通过获取对话框的高度来进行滚动设置

let div = document.getElementById("mess_content");
setTimeout(() => {
  div.scrollTop = div.scrollHeight;
}, 100);

以上操作就基本完成一个简单的聊天功能啦,这篇笔记记录的是后台的聊天工具,前台的功能只是样式不同,逻辑写法基本类似。以前没有接触过总觉得很难,还是现在我把内容想得太简单了?