vue實現web線上聊天功能
阿新 • • 發佈:2021-06-29
本文例項為大家分享了實現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欄位返回的是一個on格式的字串資料,content內容格式如下:
// 文字訊息 { "type": "txt","msg":"你好" //訊息內容 }
// 圖片訊息 { "type": "img","url": "圖片地址","ext":"jpg","width":360,//寬 "height":480www.cppcns.com,//高 "size": 388245 }
// 視訊訊息 { "type": 'video',"url": "http://nimtest.nos.netease.com/cbc500e8-e19c-4b0f-834b-c32d4dc1075e","ext":"mp4",//寬 "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欄位進行展示樣式的區分。
程式碼
<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>
程式碼
<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>
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。