溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue如何實(shí)現(xiàn)web在線聊天功能

發(fā)布時(shí)間:2021-06-28 11:38:30 來源:億速云 閱讀:300 作者:小新 欄目:開發(fā)技術(shù)

這篇文章主要介紹vue如何實(shí)現(xiàn)web在線聊天功能,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!

具體內(nèi)容如下

最終實(shí)現(xiàn)的效果

vue如何實(shí)現(xiàn)web在線聊天功能

實(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與后端建立長(zhǎng)連接,有新消息由后端主動(dòng)向前端推送消息方式實(shí)現(xiàn),這里主要介紹前端實(shí)現(xiàn)聊天窗體思路,WebSocket部分就不展開了,采用定時(shí)器輪詢的方式簡(jiǎn)單實(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":"中國(guó) 浙江省 杭州市 網(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 :>
                {{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"
        
        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>

以上是“vue如何實(shí)現(xiàn)web在線聊天功能”這篇文章的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對(duì)大家有幫助,更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI