vue實(shí)現(xiàn)web在線聊天功能
本文實(shí)例為大家分享了vue實(shí)現(xiàn)web在線聊天的具體代碼,供大家參考,具體內(nèi)容如下
最終實(shí)現(xiàn)的效果

實(shí)現(xiàn)過程
無限滾動(dòng)窗體的實(shí)現(xiàn)之前已經(jīng)介紹過,這里就不在贅述了,不清楚的可以通過文檔前文的傳送門進(jìn)行查看。
實(shí)時(shí)在線聊天主要功能點(diǎn)
- 滾動(dòng)到兩天窗體頂部,自動(dòng)加載歷史跟多信息,數(shù)據(jù)加載的時(shí)候,需要有一個(gè)loading動(dòng)畫;
- 發(fā)送信息是滾動(dòng)條自動(dòng)滑動(dòng)到窗體底部,并且自己發(fā)送的信息出現(xiàn)在聊天窗體中;
- 收到別人發(fā)送信息時(shí),需要判斷滾動(dòng)條處于窗體中的位置,在距離底部一定范圍內(nèi)收到信息需要自動(dòng)滑動(dòng)到窗體底部;
- 收發(fā)的信息在聊天狀態(tài)不能重復(fù)顯示;
- 收發(fā)的信息在聊天窗體中需要以逆序的方式展示,即離窗體底部越近的信息為最新消息;
- 授信最好通過WebSocket與后端建立長連接,有新消息由后端主動(dòng)向前端推送消息方式實(shí)現(xiàn),這里主要介紹前端實(shí)現(xiàn)聊天窗體思路,WebSocket部分就不展開了,采用定時(shí)器輪詢的方式簡單實(shí)現(xiàn)。
話不多說,直接上代碼
后端返回?cái)?shù)據(jù)格式
我覺得所有的設(shè)計(jì)和功能實(shí)現(xiàn)都是基于數(shù)據(jù)的基礎(chǔ)上去實(shí)現(xiàn)的,所以咋們先來看一下后端返回的數(shù)據(jù)格式:
{
"code": 200, // 響應(yīng)編碼
"msg": "OK", // 響應(yīng)消息
"total": 1,
"sysTime": "2020-12-16 15:23:27", // 系統(tǒng)響應(yīng)時(shí)間
"data": [{
"avatar": "", // 用戶頭像
"content": "{\"type\":\"txt\",\"msg\":\"你好!\"}", // 消息內(nèi)容
"isRead": 0, // 是否已讀
"isOneself": 0, // 是否是自己發(fā)送的消息 0否,1是
"msgId": 10, // 消息ID,用來去重
"nickName": "碧海燕魚", // 用戶昵稱
"userCode": "202012162030202232" // 用戶編碼
}]
}
這里需要說明的是,content字段返回的是一個(gè)json格式的字符串?dāng)?shù)據(jù),content內(nèi)容格式如下:
// 文本消息
{
"type": "txt",
"msg":"你好" //消息內(nèi)容
}
// 圖片消息
{
"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":"中國 浙江省 杭州市 網(wǎng)商路 599號(hào)", //地理位置
"longitude":120.1908686708565, // 經(jīng)度
"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="主動(dòng)一點(diǎn),世界會(huì)更大!"
:rows="4"
/>
</div>
<div class="footer-btn">
<Button @click="cancel" type="text">取消</Button>
<Button type="primary" @click="sendMsg">發(fā)送</Button>
</div>
</Modal>
</template>
注:自己發(fā)的信息和別人發(fā)的信息展示樣式不一樣,所以需要通過isOneself字段進(jìn)行展示樣式的區(qū)分。
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'), // 后端沒有返回頭像默認(rèn)頭像,注意:需要用require請(qǐng)求方式才能動(dòng)態(tài)訪問本地文件
data:[],
distincData:[], // 消息去重?cái)?shù)組
offsetMax:0, // 最大偏移位,記錄當(dāng)前獲取的最大id,往后的定時(shí)輪詢數(shù)據(jù)時(shí)每次只獲取比這個(gè)id大的數(shù)據(jù)
offsetMin:0, // 最小偏移位,記錄當(dāng)前獲取的最小id,往上滑動(dòng)時(shí)每次只獲取比這小id大的數(shù)據(jù)
searchForm:{ // 每次定時(shí)獲取數(shù)據(jù)或首次加載數(shù)據(jù)提交的form表單數(shù)據(jù)
pageNumber: 1,
pageSize: 20
},
form:{ // 發(fā)送數(shù)據(jù)提交數(shù)據(jù)表單
content:"",
msg:""
},
timerSwitch:0 // 定時(shí)器開關(guān),默認(rèn)關(guān)閉
};
},
methods: {
init(){
},
loadMsg(){ // 窗體打開默認(rèn)加載一頁數(shù)據(jù),窗體什么周期中值運(yùn)行一次
let that = this;
this.searchForm.offsetMax = this.offsetMax;
listMsg(this.searchForm).then(res=>{
if (res.code == 200) {
res.data.forEach(e => {
// 標(biāo)記最大偏移位
if(that.offsetMax < e.msgId){
that.offsetMax = e.msgId;
}
e.content = JSON.parse(e.content);
that.data.unshift(e)
that.distincData.push(e.msgId);
// 標(biāo)記最大偏移位,后端返回?cái)?shù)據(jù)是逆序,所以最后一條id最新
that.offsetMin = e.msgId;
});
// 數(shù)據(jù)加載完成,滾動(dòng)條滾動(dòng)到窗體底部
this.scrollToBottom();
}
});
},
show(){ // 打開窗體初始化數(shù)據(jù)
// 初始化數(shù)據(jù)
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;
// 開啟定時(shí)器
this.timerSwitch = 1;
this.reloadData();
},
sendMsg(){ // 發(fā)送消息
if(!this.form.msg){
this.$Message.warning("不能發(fā)送空白信息");
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();
// 發(fā)送信息只返回當(dāng)前一條,此時(shí)可能對(duì)方已經(jīng)發(fā)送信息,所以不修改偏移量
}
});
},
scrollToBottom(){ // 滾動(dòng)到窗體底部
this.$nextTick(()=>{
let chatform = document.getElementById("chatform");
chatform.scrollTop = chatform.scrollHeight;
});
},
// 滾動(dòng)到最上方,取歷史數(shù)據(jù),根據(jù)分頁參數(shù)取。不用修改偏移標(biāo)記位,但是需要判重
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(){
// 判斷定時(shí)器開關(guān)是否開啟,如果開啟,則執(zhí)行定時(shí)器
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 => {
// 修改最大偏移位,放到校驗(yàn)重復(fù)之前,防止當(dāng)前發(fā)送信息已經(jīng)放入消息列表,但是偏移值沒該的情況
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);
// 收到新消息,判斷高度,如果當(dāng)前滾動(dòng)條高度距底部小于100,則動(dòng)滑到底部
let chatform = document.getElementById("chatform");
let gap = chatform.scrollHeight -chatform.scrollTop;
if(gap >0 && gap < 400){
this.scrollToBottom();
}
}
});
that.reloadData();
}
});
},1000*2);
}
},
cancel(){ // 關(guān)閉窗體需要把提示任務(wù)開關(guān)一起關(guān)閉調(diào)
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>
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Vue+express+Socket實(shí)現(xiàn)聊天功能
- Vue實(shí)現(xiàn)聊天界面
- vue+web端仿微信網(wǎng)頁版聊天室功能
- Vue.js仿微信聊天窗口展示組件功能
- vue + socket.io實(shí)現(xiàn)一個(gè)簡易聊天室示例代碼
- 基于Vue2實(shí)現(xiàn)的仿手機(jī)QQ單頁面應(yīng)用功能(接入聊天機(jī)器人 )
- Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法
- vue實(shí)現(xiàn)的微信機(jī)器人聊天功能案例【附源碼下載】
- 基于vue和websocket的多人在線聊天室
- Vue+ssh框架實(shí)現(xiàn)在線聊天
相關(guān)文章
Vue中import from的來源及省略后綴與加載文件夾問題
這篇文章主要介紹了Vue中import from的來源--省略后綴與加載文件夾,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
vue實(shí)現(xiàn)表單未編輯或未保存離開彈窗提示功能
這篇文章主要介紹了vue實(shí)現(xiàn)表單未編輯或未保存離開彈窗提示功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
vue 封裝 Adminlte3組件的實(shí)現(xiàn)
這篇文章主要介紹了vue 封裝 Adminlte3組件的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
vue計(jì)算屬性computed--getter和setter用法
這篇文章主要介紹了vue計(jì)算屬性computed--getter和setter用法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
element?ui時(shí)間日期選擇器el-date-picker報(bào)錯(cuò)Prop?being?mutated:"
在日常開發(fā)中,我們會(huì)遇到一些情況,限制日期的范圍的選擇,下面這篇文章主要給大家介紹了關(guān)于element?ui時(shí)間日期選擇器el-date-picker報(bào)錯(cuò)Prop?being?mutated:?"placement"的解決方式,需要的朋友可以參考下2022-08-08
vue實(shí)現(xiàn)數(shù)字動(dòng)態(tài)翻牌的效果(開箱即用)
這篇文章主要介紹了vue實(shí)現(xiàn)數(shù)字動(dòng)態(tài)翻牌的效果(開箱即用),實(shí)現(xiàn)原理是激將1到9的數(shù)字豎直排版,通過translate移動(dòng)位置顯示不同數(shù)字,本文通過實(shí)例代碼講解,需要的朋友可以參考下2019-12-12
el-table-column疊加el-popover使用示例小結(jié)
el-table-column有一列展示多個(gè)tag信息,實(shí)現(xiàn)點(diǎn)擊tag展示tag信息以及tag對(duì)應(yīng)的詳細(xì)信息,本文通過示例代碼介紹el-table-column疊加el-popover使用示例小結(jié),感興趣的朋友跟隨小編一起看看吧2024-04-04

