上一篇介绍了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"draggablefooter-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><Inputv-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>

vue web在线聊天功能实现相关推荐

  1. 【SpringBoot框架篇】18.使用Netty加websocket实现在线聊天功能

    文章目录 1.简介 2.最终功能实现的效果图 2.1.pc端 2.2.移动端 3.实战应用 3.1.引入依赖 3.2.配置文件 3.3.测试demo 3.3.1.消息内容实体类 3.3.2.处理请求的 ...

  2. 在线聊天JAVA后端_java web 在线聊天的基本实现

    随着互联网的发展,http的协议有些时候不能满足需求,比如在现聊天的实现.如果使用http协议必须轮训,或者使用长链接.必须要一个request,这样后台才能发送信息到前端. 后台不能主动找客户端通信 ...

  3. 简单java socket_基于Java Socket实现一个简易在线聊天功能(一)

    最近做了一个项目,其中有一个在线网页交流的需求,好久没写代码了,手都生疏了,于是先写demo练练手,分享到脚本之家平台,以此做个记录,方便自己和大家使用. 先给大家说下实现步骤分这样几大步: 1.使用 ...

  4. 基于PHP实现一个简单的在线聊天功能(轮询ajax )

    基于PHP实现一个简单的在线聊天功能(轮询ajax ) 一.总结 1.用的轮询ajax 二.基于PHP实现一个简单的在线聊天功能 一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻 ...

  5. JavaWeb--使用Websocket实现在线聊天功能

    首先简单介绍下WebSocket,WebSocket是HTML5中内容,是基于TCP的一种新的网络协议,它支持全双工.长连接的通信.在它出现之前,实时消息发送与接收通过轮询实现,但是频繁与服务器建立连 ...

  6. 基于PHP实现一个简单的在线聊天功能

    一直很想试着做一做这个有意思的功能,感觉复杂的不是数据交互和表结构,麻烦的是前端展..于是.. 需求分析 要实现功能,首先要做前端,经过对比其他网站的在线聊天功能,发现除了基本的聊天功能以外,还要注意 ...

  7. Springboot+Vue实现在线聊天室项目-修改头像、添加好友接口的实现

    Springboot+Vue实现在线聊天室项目 该聊天室为大二上学期计算机网络大作业,并且是本人第一次使用vue实现前后端分离的项目,前端架构尚未熟悉可能会出现一些不妥之处,还请大佬们指出.(本文章写 ...

  8. web网页聊天功能(可用)

    web网页聊天功能页面可正常使用 服务器搭建 MySQL=5.6  PHP>=7.0 下载链接: https://nczx.lanzouw.com/iFKqs0ke0uxi

  9. 【云原生之Docker实战】使用Docker部署Web在线聊天室Rocket.Chat

    [云原生之Docker实战]使用Docker部署Web在线聊天室Rocket.Chat 一.Rocket.Chat介绍 二.检查本地系统环境 1.检查系统版本 2.检查docker版本 3.检查doc ...

  10. Springboot+Vue实现在线聊天(通用版)

    只需简单几步,就可以实现在线聊天室! 集成步骤: 后端Springboot Springboot 添加Pom依赖: <!-- websocket --> <dependency> ...

最新文章

  1. 200 switching to ascii mode_王者荣耀:小伙200买V8号,146款皮肤还有1神秘道具,一封邮件哭了...
  2. UAC 实现原理及绕过方法
  3. maximo 自定义高级数据选择对话框(非表域实现)
  4. 串口之GetCommState、SetCommState函数详解
  5. 继爱奇艺后,腾讯视频会员也要涨价了
  6. 强化学习 ---baseline项目之 TensorFlow的训练参数的存储和加载
  7. ubuntu16 kickstart pxe 安装系统
  8. QT实现appendSheet
  9. CodeIgniter学习笔记(六)——CI超级对象中的input输入类
  10. python制图一元迭代函数_Python中 生成器、迭代器、闭包、装饰器、元类实例分析...
  11. Dynamics Ax 2012中调用外部web服务
  12. 软件项目需求分析报告模板
  13. 台式电脑主机前面耳机插孔没声音的解决方法
  14. MATLAB入门之旅
  15. android fragment 抽屉,android – Actionbar和Navigation抽屉 – 使用Activity / Fragment滑动Actionbar...
  16. ping命令显示的TTL是什么意思
  17. 拼多多店铺日销量100+怎么实现?直通车怎么开?
  18. python 比较两种包装的大米的价钱
  19. 双系统(ubuntu系统与window系统)时间不一致的解决办法
  20. 申宝股票-股指超跌反弹

热门文章

  1. 2021年大学生可以参加的科创竞赛-最全信息汇总
  2. iOS常用三方库、插件、知名技术博客、常用开发工具使用介绍等等,大家可以一次性下载了!
  3. zabbix3.4详细安装教程
  4. Mujoco平面双足机器人模拟
  5. 英文版 office 中的中文字体
  6. 范德波振子的李雅普诺夫指数
  7. 数据结构---哈希表的C语言实现
  8. 圆形led屏幕_一种简单的圆形LED显示屏的制作方法
  9. 一、zabbix与nagios对比
  10. 简历编辑导出工具(类似wps简历助手)