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 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>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

以上是 vue实现web在线聊天功能 的全部内容, 来源链接: utcz.com/p/239361.html

回到顶部