您的位置:首页技术文章
文章详情页

vue实现web在线聊天功能

【字号: 日期:2022-09-28 15:25:25浏览:46作者:猪猪

本文实例为大家分享了vue实现web在线聊天的具体代码,供大家参考,具体内容如下

最终实现的效果

vue实现web在线聊天功能

实现过程

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

实时在线聊天主要功能点 滚动到两天窗体顶部,自动加载历史跟多信息,数据加载的时候,需要有一个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 v-model='chatVisible' draggable footer-hide : @on-cancel='cancel'> <div class='chat'> <div @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='https://www.haobala.com/bcjs/item.avatar?item.avatar:defualtAvatar' > <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' 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
相关文章: