vue web在线聊天功能实现

2023-11-11

上一篇介绍了vue怎么实现无限滚动窗体,这一篇就具体怎么使用vue实现web在线聊天功能展开深入讨论。

对尚且不清楚怎么实现无限滚动窗体的,可前往这里查看《vue和iview实现无限滚动的正确解法》

 先看看最终实现的效果

 实现过程


无限滚动窗体的实现之前已经介绍过,这里就不在赘述了,不清楚的可以通过文档前文的传送门进行查看。

实时在线聊天主要功能点

  • 滚动到两天窗体顶部,自动加载历史跟多信息,数据加载的时候,需要有一个loading动画;
  • 发送信息是滚动条自动滑动到窗体底部,并且自己发送的信息出现在聊天窗体中;
  • 收到别人发送信息时,需要判断滚动条处于窗体中的位置,在距离底部一定范围内收到信息需要自动滑动到窗体底部;
  • 收发的信息在聊天状态不能重复显示;
  • 收发的信息在聊天窗体中需要以逆序的方式展示,即离窗体底部越近的信息为最新消息;
  • 授信最好通过WebSocket与后端建立长连接,有新消息由后端主动向前端推送消息方式实现,这里主要介绍前端实现聊天窗体思路,WebSocket部分就不展开了,采用定时器轮询的方式简单实现。

话不多说,直接上代码


后端返回数据格式

我觉得所有的设计和功能实现都是基于数据的基础上去实现的,所以咋们先来看一下后端返回的数据格式:

{
	"code": 200, // 响应编码
	"msg": "OK", // 响应消息
	"total": 1, 
	"sysTime": "2020-12-16 15:23:27", // 系统响应时间
	"data": [{
		"avatar": "",  // 用户头像
		"content": "{\"type\":\"txt\",\"msg\":\"你好!\"}", // 消息内容
		"isRead": 0, // 是否已读
		"isOneself": 0,  // 是否是自己发送的消息 0否,1是
		"msgId": 10, // 消息ID,用来去重
		"nickName": "碧海燕鱼", // 用户昵称
		"userCode": "202012162030202232" // 用户编码
	}]
}

 这里需要说明的是,content字段返回的是一个json格式的字符串数据,content内容格式如下:

// 文本消息
{
  "type": "txt",
  "msg":"你好" //消息内容
}
// 图片消息
{
  "type": "img",
  "url": "图片地址",
  "ext":"jpg",
  "width":360,    //宽
  "height":480,    //高
  "size": 388245
}
// 视频消息
{
  "type": 'video',
  "url": "http://nimtest.nos.netease.com/cbc500e8-e19c-4b0f-834b-c32d4dc1075e",
  "ext":"mp4",
  "width":360,    //宽
  "height":480,    //高
  "size": 388245
}
// 地理位置消息
{
  "type": "local",
  "address":"中国 浙江省 杭州市 网商路 599号",    //地理位置
  "longitude":120.1908686708565,        // 经度
  "latitude":30.18704515647036            // 纬度
}

HTML代码

<template>
  <Modal title="在线沟通" v-model="chatVisible"
   draggable
   footer-hide
   :width="580" @on-cancel="cancel">
   <div class="chat">
     <div  class="chat-message-body" id ="chatform" @scroll="scroll"
      >
      <Spin v-if="loading">
        <Icon type="ios-loading" size=18 class="spin-icon-load"></Icon>
      </Spin>
        <div  dis-hover v-for="(item,index) in data"
         :key="index" class="message-card">
         <div :class="item.isOneself == 1?'message-row-right': 'message-row-left'">
           <img :src="item.avatar?item.avatar:defualtAvatar" 
            height="35" width="35" >
            <div class="message-content"> 
              <div :style="item.isOneself == 1?'text-align:right;display: flex;flex-direction:row-reverse':''">
                {{item.nickName}}
                <span class="message-time">
                   {{item.createTime}}</span>
                </div>
              <div class="message-body">
                {{item.content.msg}}
                </div>
             </div> 
          </div>
         </div>
      </div>
        <Input
        v-model="form.msg"
        type="textarea"
        style="margin:10px 0;"
        placeholder="主动一点,世界会更大!"
        :rows="4"
      />
     </div>
     <div class="footer-btn">
        <Button @click="cancel" type="text">取消</Button>
        <Button type="primary" @click="sendMsg">发送</Button>
      </div>
  </Modal>
</template>

 注:自己发的信息和别人发的信息展示样式不一样,所以需要通过isOneself字段进行展示样式的区分。

JavaScript代码

<script>
import {listMsg,sendMsg } from "@/api/index";
export default {
  name: "chat",
  props: {
    value: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      chatVisible:this.value,
      loading:false,
      defualtAvatar:require('../../assets/defult-avatar.svg'), // 后端没有返回头像默认头像,注意:需要用require请求方式才能动态访问本地文件
      data:[],
      distincData:[], // 消息去重数组
      offsetMax:0, // 最大偏移位,记录当前获取的最大id,往后的定时轮询数据时每次只获取比这个id大的数据
      offsetMin:0,  // 最小偏移位,记录当前获取的最小id,往上滑动时每次只获取比这小id大的数据
      searchForm:{ // 每次定时获取数据或首次加载数据提交的form表单数据
        pageNumber: 1,
        pageSize: 20
      },
      form:{ // 发送数据提交数据表单
        content:"",
        msg:""
      },
      timerSwitch:0 // 定时器开关,默认关闭
    };
  },
  methods: {
    init(){
      
    },
    loadMsg(){ // 窗体打开默认加载一页数据,窗体什么周期中值运行一次
      let that = this;
      this.searchForm.offsetMax = this.offsetMax;
      listMsg(this.searchForm).then(res=>{
        if (res.code == 200) {
          res.data.forEach(e => {
            // 标记最大偏移位
            if(that.offsetMax < e.msgId){
                that.offsetMax = e.msgId;
            }
            e.content = JSON.parse(e.content);
            that.data.unshift(e)
            that.distincData.push(e.msgId);
            // 标记最大偏移位,后端返回数据是逆序,所以最后一条id最新
            that.offsetMin = e.msgId;
           });
          // 数据加载完成,滚动条滚动到窗体底部
          this.scrollToBottom();
        }
      });
       
        
    },
    show(){ // 打开窗体初始化数据
      // 初始化数据
      this.data =[];
      this.distincData =[];
      this.offsetMax = 0;
      this.offsetMin = 0;
      this.searchForm.pageNumber = 1;
      this.searchForm.pageSize = 20;
      this.form ={
        content:"",
        msg:""
      };
      this.loadMsg();
      this.chatVisible = true;
      // 开启定时器
      this.timerSwitch = 1;
      this.reloadData();
    },
    sendMsg(){ // 发送消息
      if(!this.form.msg){
         this.$Message.warning("不能发送空白信息");
        return;
      }
      let content = { // 封装消息体
        type:"txt",
        msg:this.form.msg
      }; 
      this.form.content = JSON.stringify(content);
      sendOrderMsg(this.form).then(res=>{
        if (res.code == 200) {
          res.data.content = JSON.parse(res.data.content);
          this.data.push(res.data)
          this.form.msg="";
          this.distincData.push(res.data.msgId);
          this.scrollToBottom();
          // 发送信息只返回当前一条,此时可能对方已经发送信息,所以不修改偏移量
        }
      });
    },
    scrollToBottom(){ // 滚动到窗体底部
      this.$nextTick(()=>{
          let chatform = document.getElementById("chatform");
          chatform.scrollTop = chatform.scrollHeight;
      });
    },
    // 滚动到最上方,取历史数据,根据分页参数取。不用修改偏移标记位,但是需要判重
    scroll(){
      let chatform = document.getElementById("chatform");
      let scrollTop = chatform.scrollTop;
      if(scrollTop == 0){
        this.loading =true;
        let that = this;
        this.searchForm.offsetMin = this.offsetMin;
        this.searchForm.offsetMax = "";
        listMsgByOrder(this.searchForm).then(res=>{
           this.loading =false;
            if (res.code == 200) {
              res.data.forEach(e => {
                if(that.distincData.indexOf(e.msgId) <0){
                  e.content = JSON.parse(e.content);
                  that.data.unshift(e);
                  that.distincData.push(e.msgId);
                  // 修改最小偏移位
                  if(that.offsetMin > e.msgId){
                      that.offsetMin = e.msgId;
                  }
                }
              });
            }
        });
      }
    },
   reloadData(){
    // 判断定时器开关是否开启,如果开启,则执行定时器
    if(this.timerSwitch){
      setTimeout(() => {
        let params = {};
        params.pageNumber = 1;
        params.pageSize = 20;
        params.offsetMax = this.offsetMax;
        let that = this;
        listMsgByOrder(params).then(res=>{
          if (res.code == 200) {
            res.data.forEach(e => {
              // 修改最大偏移位,放到校验重复之前,防止当前发送信息已经放入消息列表,但是偏移值没该的情况
              if(that.offsetMax < e.msgId){
                  that.offsetMax = e.msgId;
              }
              if(that.distincData.indexOf(e.msgId) <0){
                e.content = JSON.parse(e.content);
                that.data.push(e)
                that.distincData.push(e.msgId);
                // 收到新消息,判断高度,如果当前滚动条高度距底部小于100,则动滑到底部
                let chatform = document.getElementById("chatform");
                let gap = chatform.scrollHeight -chatform.scrollTop;
                if(gap >0 && gap < 400){
                  this.scrollToBottom();
                }
              }
            });
            that.reloadData();
          }
        });
      },1000*2);
    }
    
   },
   cancel(){ // 关闭窗体需要把提示任务开关一起关闭调
     this.chatVisible = false;
     this.timerSwitch = 0;
   }
  },
  mounted() {
  }
};
</script>

 

CSS代码

 

<style lang="less">
   .message {
        height: 350px;
    }
  .ivu-card-body {
    padding:5px;
  }
  .ivu-modal-body{
    padding: 0px 16px 16px  16px;
  }
  .chat-message-body {
   background-color:#F8F8F6;
   width:545px;
   height: 350px;
   overflow: auto;
  }
  .message-card {
   margin:5px;
  }
  .message-row-left {
   display: flex;
   flex-direction:row;
  }
  .message-row-right {
   display: flex;
   flex-direction:row-reverse;
  }
  .message-content {
    margin:-5px 5px 5px 5px;
    display: flex;
    flex-direction:column;
  }
  .message-body {
    border:1px solid #D9DAD9;
    padding:5px;
    border-radius:3px;
    background-color:#FFF;
  }
  .message-time {
    margin:0 5px;
    font-size:5px;
    color:#D9DAD9;
  }
  .footer-btn {
    float:right;
    margin-bottom: 5px;
  }
  .spin-icon-load {
    animation:ani-spin 1s linear infinite;
  }
  @keyframes ani-spin{
    form{transform: rotate(0deg);}
    50% {transform: rotate(180deg);}
    to  {transform: rotate(360deg);}
  }
</style>

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vue web在线聊天功能实现 的相关文章

  • 如何以编程方式将图片上传到 Facebook?

    好的 这是我的第一个问题 如何允许我网站上的访问者在他们的 Facebook 新闻源上分享我的照片 https webapps stackexchange com questions 10140 how do i allow visitor
  • MongoDB $geoIntersects 不适用于包含负顶点的多边形

    我已在数据库中存储了跨越 x 轴和 y 轴的多边形 并且我想搜索包含给定点的多边形 为此 我使用 geoIntersects 运算符来指定一个点 但是 当多边形穿过轴时 MongoDB 不会返回任何多边形 我可以对查询或架构进行任何小的更改
  • 如何从浏览器打印 PDF

    在Web应用程序中 是否可以强制在客户端上打印PDF文件 如果浏览器配置为在窗口内打开 PDF 我想调用 window print 会起作用 但某些浏览器 例如我的 被配置为在外部打开 PDF 谷歌文档的做法是将 JavaScript 嵌入
  • CSS 样式直到刷新才应用

    我有一个具有以下 CSS 样式的网页 该样式覆盖了一些更高级别的样式 pnlActions background image webkit gradient linear left top left bottom from 000 to 0
  • Bootstrap - 表单内联元素之间的间距

    在 Bootstrap 3 中 内联表单 http getbootstrap com css forms inline http getbootstrap com css forms inline 我似乎找不到间距的原因 form grou
  • 将 PictureStream 转换为 HTML5 画布

    我正在从 net Web 服务检索图片流的字节数组 JSON 字节数组响应如下所示 137 80 78 372 617 more 我正在尝试转换这个字节数组并将其绘制到 HTML 画布中 如下所示 var context document
  • WPF WebBrowser (3.5 SP1) 始终位于顶部 - 在 WPF 中显示 HTML 的其他建议

    我一直在拼命寻找一种在 WPF 应用程序中显示 HTML 的简单方法 有一些选项 1 使用WPF Web浏览器控件2 使用帧控制3 使用第三方控件 但是 我遇到了以下问题 1 WPF WebBrowser Control不是真正的WPF 它
  • 左侧为文字,右侧为图像

    我正在尝试使 div 中的文本位于左侧 图像位于右侧 我在 Stackoverflow 中发现了很多例子 其中图像在左 文本在右 然后我尝试切换它们 但无法使其工作 我发现堆栈溢出上的那个 https stackoverflow com q
  • 如何在js中将嵌套的html元素提取到同一级别

    我使用 javascript 并有一个 div 元素 里面有一些 html 标签 如下所示 一些元素是嵌套的 我想将它们保持在同一级别 第一个 html 是这样的 div p some text on here 1 i some itali
  • 将两个 div 依次放置在另一个之下

    我在将两个 div 置于另一个之下时遇到一些问题 我尝试了 Stackoverflow 中找到的一些解决方案 如下所示 但似乎没有任何效果 Code wrapper position absolute up position absolut
  • 使用 HTML5 启用后置摄像头

    我正在开发一个使用 MVC ASP Net 4 HTML5 的项目 默认浏览器是 google chrome v29 0 1547 57 我可以与这些工具交互并拍照 但只能使用前置摄像头 如何启用后置摄像头 平板电脑的特点 三星 Galax
  • MeteorJS:用户集合如何公开新字段

    我在用户集合中添加了新字段 但是当我在客户端 Meteor user cart 中访问它时 cart 是我的新字段 其未定义 如何公开新字段 购物车 以便可以在客户端中访问 最简单的方法是添加一个null发布者将自动发送数据 无需订阅 请务
  • 在 HTML5 画布上绘制一个点 [重复]

    这个问题在这里已经有答案了 使用以下命令在 HTML5 画布上绘制一条线非常简单context moveTo and context lineTo 功能 我不太确定是否可以绘制一个点 即为单个像素着色 lineTo 函数不会绘制单个像素线
  • 使文本“bbb”位于图标旁边,而不是压在一起

    Goal 使字母 bbb 可见 并且文本应位于图标旁边 Problem 我尝试将文本移到右侧 但不起作用 你们知道该怎么做吗 JSfiddle https jsfiddle net 5f7qjLgf 1 Thanks result filt
  • 如何获取与值匹配或存在于另一个表中的记录?

    我试图弄清楚在这种情况下如何获取所有任务 其中两个字段等于某个值或者它们存在于另一个表中 这是查询 SELECT TASKS task id TASKS task title TASKS task description TASKS tas
  • 如何将 Jitsi Meet 添加到 Vuejs

    我已在 public html 的正文中加载了 jitsi meet 脚本 并且我有一个组件 如下所示
  • 使用 Bootstrap 3 具有固定高度、标题和列宽度的表格

    我想要一张具有以下功能的表 固定标题 固定高度 带有可见的滚动条 3个固定宽度的列 一个大列和两个窄列 我开始使用 tbody display block overflow auto height 100px 为了至少获得固定高度 但此规则
  • 是否可以为 NodeJS 应用程序设置基本 URL?

    我希望能够在同一域下托管多个 NodeJS 应用程序 而不使用子域 例如 google com reader 而不是 images google com 问题是我总是输入网址的第一部分 例如Express NodeJS 中的 reader
  • 服务器端包括中断布局

    我终于完善了我的网页 它在每个浏览器中都能完美运行 然而 当我将页眉和页脚内容抽象到服务器端包含时 布局在 Firefox Opera Safari 中略有变化 但在 IE 中 布局变化使页面看起来破碎 使用 SSI 时是否存在可能导致布局
  • jQuery / Javascript - 检测 WooCommerce 商店通知 html 是否可见

    我正在寻找一种方法来确定使用 JavaScript jQuery 是否显示 WooCommerce 商店通知 商店通知的 HTML 看起来像这样 p class woocommerce store notice demo store sty

随机推荐

  • 如何解决vue项目打包上线chunk-vendors.js文件过大?以及TypeError: Cannot read property ‘tapPromise‘ of undefined报错

    最近做一个vue项目遇到了一个问题 项目打包上线发现chunk vendors js文件也太大了点 第一次加载页面需要将近30s 这肯定是无法容忍的 于是 调研 了一番 记录下解决方法 因app首页加载不流畅 于是去检查首页加载项的耗时 最
  • 实现音乐播放器,多线程处理进度条

    protected void onCreate Bundle savedInstanceState super onCreate savedInstanceState setContentView R layout activity mai
  • STM32F103c8t6+ESP8266(esp-01s)+MQTT固件+HAL库 连接阿里云(最详细+可移植)教程

    文章目录 概要 一 MQTT固件 二 阿里云账号注册 三 stm32f103的配置 三 esp8266的接收和发送 1 printf 2 串口2的DMA接收中断 3 esp8266的函数方法 四 连接阿里云 五 数据上报和数据解析 1 发送
  • Windows子系统的安装与使用

    引言 好久没有更新技术博客了 这段时间一直在学习新的知识 涉足大数据技术领域 最近有一些收获 来和大家分享一下 笔者一直用的是windows系统的电脑 但是有时候又需要用到linux系统 对此有3种方案可以实现 电脑安装双系统 安装虚拟机
  • 记录一下kibana启动连接报错问题(kibana server is not ready yet)

    记录一下kibana启动连接报错问题 kibana server is not ready yet 今天启动kibana出现该问题 先去看了看是否是elasticsearch连接出错 启动了容器 docker start elasticse
  • python错误与异常

    一 错误 1 语法错误 num 1 if num gt 1 print num gt 1 系统报错提示 SyntaxError expected 2 逻辑错误 编写业务逻辑错误 3 系统错误 二 异常 1 程序执行过程中出现的未知错误 2
  • 1019 数字黑洞

    给定任一个各位数字不完全相同的 4 位正整数 如果我们先把 4 个数字按非递增排序 再按非递减排序 然后用第 1 个数字减第 2 个数字 将得到一个新的数字 一直重复这样做 我们很快会停在有 数字黑洞 之称的 6174 这个神奇的数字也叫
  • 回文质数Prime Palindromes

    题目描述 因为 151 既是一个质数又是一个回文数 从左到右和从右到左是看一样的 所以 151 是回文质数 写一个程序来找出范围 a b 一亿 间的所有回文质数 输入输出格式 输入格式 第 1 行 二个整数 a 和 b 输出格式 输出一个回
  • Locust压力测试使用总结

    上次做接口压力测试前一直研究使用jmeter 本以为可以拿来使用了 但是真正进行并发接口时 发现jmeter在单机下并发1000个时 台式电脑单机资源早就被使用完 整个jmeter卡得死死的 结果那晚使用jmeter并发失败 幸好之前也准备
  • FFmpeg学习笔记--视频传输的基本概念

    目录 1 容器 container 和文件 file 2 媒体流 stream 3 数据帧 frame 和数据包 packet 4 编解码器 Codec 5 复用 mux 6 解复用 demux 7 码率 bps 和帧率 fps 8 ffm
  • 【Android -- 写作工具】Markdown 代码块

    1 前言 关于代码块 Markdown 作者给出的定义如下 预格式化代码块主要用于在 Markdown 文档中显示源代码风格的内容 相比普通的文本段落 代码块可以保留文字内容的多行换行 缩进等格式 在 Markdown 文档中生成代码块 需
  • Numpy中的(一维)数组和(行列)向量

    Numpy中的 一维 数组和 行列 向量 随笔记录 Numpy的数组和行列向量的区别 随笔记录 Numpy的数组和行列向量的区别 今天做sklearn的datasets diabetes 的实验 做了个操作 diabetes是一个442 1
  • 【FPGA的基础快速入门17------频率计】

    FPGA的基础学习 频率计 频率计简介 等精度频率计 频率计简介 频率计又称为频率计数器 是一种专门对被测信号频率进行测量的电子测量仪器 计数法 直接计数单位时间内被测信号的脉冲数 这种方法测量精度高 速度快 适合不同频率 不同精确度测频的
  • 输入一个四位整数,分别输出组成该四位数的各位数字

    一 代码实现 1 include
  • Spring框架支持哪几种Bean作用域?自动装配Bean有哪些方式?

    Spring框架支持哪几种Bean作用域 spring支持五种Bean作用域 singleton 单例 就是每个spring容器只有一个 实例对象 prototype 多例 一个bean可以定义多个实例 另外三个是在web的Spring A
  • dell服务器启动顺序如何设置_戴尔品牌机怎么设置启动顺序(按F12进bios的)?

    展开全部 这主板非常麻烦 可关了保护 并切换 Legacy启动模式 U盘PE 装完系统 要改回uefi模式 DELL bios操作一32313133353236313431303231363533e59b9ee7ad943133343137
  • 传输线的物理基础(二):信号在传输线中的速度

    铜中电子的速度 信号在传输线上传输的速度有多快 如果人们经常错误地认为信号在传输线上的速度取决于导线中电子的速度 凭着这种错误的直觉 我们可能会想象降低互连的电阻会提高信号的速度 事实上 典型铜线中电子的速度实际上比信号速度慢约 100 亿
  • NLP中的数据增强方法!

    作者简介 大家好我是 uu 人工智能硕博在读 精通python 某大厂nlp算法经历 机器学习 深度学习 自然语言处理 计算机视觉 个人主页 uu主页 觉得uu写的不错的话 麻烦动动小手 点赞 收藏 评论 今天给大家带来的刷题系列是 NLP
  • BUS creator & selector、Mux&Demux

    2 3 总线BUS creator selector Bus Creator 由几路输入信号合成为一条总线信号 Bus Selector 由总线信号中选取需要的一路或几路信号输出 Mux 信号合成 Demux 信号分解 区别 Bus的可选择
  • vue web在线聊天功能实现

    上一篇介绍了vue怎么实现无限滚动窗体 这一篇就具体怎么使用vue实现web在线聊天功能展开深入讨论 对尚且不清楚怎么实现无限滚动窗体的 可前往这里查看 vue和iview实现无限滚动的正确解法 先看看最终实现的效果 实现过程 无限滚动窗体