如何在微信小程序中实现实时会话(聊天)系统

如何在微信小程序中实现实时会话(聊天)系统系统分析设计期末大项目——闲得一币TimeForCoin小程序前端小程序项目地址_闲得一币概述实时会话系统包括两大部分:消息管理系统和会话系统。其中“消息”可以定义为一系列会话(即双方之间会话)的集合。通过获取消息列表,我们可以看到很多个消息,每一个消息都有对应的消息id。而通过消息的id,我们可以获取一系列会话,如下:消息管理页消息管理页将获取的消息分为个人消息以及系统消息。系统消…

系统分析设计期末大项目——闲得一币TimeForCoin小程序前端
小程序项目地址_闲得一币

概述

实时会话系统包括两大部分:消息管理系统和会话系统。其中“消息”可以定义为一系列会话(即双方之间会话)的集合。通过获取消息列表,我们可以看到很多个消息,每一个消息都有对应的消息id。而通过消息的id,我们可以获取一系列会话,如下:
消息管理页
会话列表

消息管理页

消息管理页将获取的消息分为个人消息以及系统消息。系统消息不能够被回复而个人消息可以。
Message.json中我们需要添加:"enablePullDownRefresh": true以允许下拉刷新。而对于下拉刷新,我们可以通过onPullDownResfresh()函数实现。除此之外,我们还需要在onShow函数中添加信息获取的功能(还要有登录状态的判断)。

onShow函数

 onShow: async function() { 
   
    this.setData({ 
   
      hasUserInfo: app.globalData.hasUserInfo
    })
    //登录判断
    if (!this.data.hasUserInfo) { 
   
      wx.showToast({ 
   
        title: '您未登录~',
        image: '/images/icons/error.png'
      })
      setTimeout(function() { 
   
        // 返回
        wx.switchTab({ 
   
          url: '/pages/index/index',
          success: function(res) { 
   },
          fail: function(res) { 
   },
          complete: function(res) { 
   },
        })
      }, 1000);

    }

    await this.loadMessage(1)
  },

onPullDownRefresh函数

onPullDownRefresh: async function () { 
   
    if(!this.data.isLoading){ 
   
      await this.loadMessage(null)
    }
  },

至于为什么是onShow而不是onLoad,因为onShow函数在你离开该页面重新进入(可以通过onNavigateBack返回)该页面的时候也会执行一次。消息的状态包括未读已读,如果使用onLoad函数从会话列表页面返回时未读不会更新为已读
在获取消息的函数loadMessage内,我们需要进行https请求,并根据请求的结果(状态码以及数据)进行消息的显示。如果状态码不为200,需要进行异常处理,这里是显示toast。而对于获取的消息,我们根据当前用户选择的是个人消息还是系统消息进行分类,如果选择个人消息就只显示个人消息。除此之外,还需要moment.js进行时间数据的解析。当然,除了时间,所有的数据都要进行正确显示。

当然了,这是个异步函数,因为其中需要等待https请求的结果。
消息页比较有挑战性的是自动刷新ScrollView到底端,使得有新的信息来的时候会自动显示。

loadMessage函数

loadMessage: async function(page) { 
   
    this.setData({ 
   
      isLoading: true
    })
    if(page != null){ 
   
      this.data.currentPage = page
      this.data.systemMessage = []
      this.data.chatMessage = []
    } else{ 
   
      this.data.currentPage++
    }
    const res = await server.request('GET', 'messages',{ 
   
      page: this.data.currentPage,
      size: 10
    })
    this.setData({ 
   
      isLoading :false
    })
    if(res.statusCode !== 200){ 
   
      this.setData({ 
   hasUserInfo: false})
      wx.showToast({ 
   
        title: '网络错误',
        icon: '',
        image: '/images/icons/error.png',
        duration: 0,
        mask: true,
        success: function(res) { 
   },
        fail: function(res) { 
   },
        complete: function(res) { 
   },
      })
      this.setData({ 
   
        noMore: true
      })
      return
    }
    if(!res.data.data || res.data.data.length === 0){ 
   
      this.setData({ 
   noMore: true})
      this.data.currentPage--
      return
    }
    moment.locale('en', { 
   
      longDateFormat: { 
   
        l: "YYYY-MM-DD",
        L: "YYYY-MM-DD HH:mm"
      }
    })
    for (let i in res.data.data) { 
   
      res.data.data[i].string_last_time =
        moment(res.data.data[i].last_message.time * 1000).format('L');
      if (res.data.data[i].target_user.nickname.length > 10) { 
   
        res.data.data[i].target_user.nickname = res.data.data[i].target_user.nickname.substr(0, 10) + '...'
      }
      if (res.data.data[i].type === 'chat') { 
   
        this.data.chatMessage.push(res.data.data[i])
      } else { 
   
        res.data.data[i].target_user.avatar = '/images/icon.png'
        this.data.systemMessage.push(res.data.data[i])
      }

    }
    if(!this.data.showSystemInfo){ 
   
      this.setData({ 
   
        testMessage: { 
   
          data: this.data.chatMessage
        }
      })
    } else{ 
   
      this.setData({ 
   
        testMessage:{ 
   
          data: this.data.systemMessage
        }
      })
    }
  },

当然,我们还需要添加单击事件。在标签中添加对应的data-item为消息id,之后根据对应的消息id进行跳转(到会话列表中)。

// 跳转详情
  navigateToMessageDetail: function(e) { 
   
    var id = e.currentTarget.dataset.id;
    wx.navigateTo({ 
   
      url: '/pages/MessageDetail/MessageDetail?session_id=' + id + '&status=message',
    })
  },

会话列表页

在会话列表页中,根据消息id进行会话列表的获取。

获取新会话

通过setTimeOut实现定时获取新消息。当然与其他数据一样,列表是分页进行获取的。但与其他数据不同,越接近底部会话的时间是更新的,而通过api获取的页面下标越小是越新的,所以通过调用reverse函数实现反转。如果获取多个页面,还需要进行反转后叠加。

if(isMore){ 
   
        var arr = []
        res.data.messages = res.data.messages.reverse()
        for(var val of res.data.messages){ 
   
          arr.push(val)
        }
        for(var val of this.data.testMessageDetail.data){ 
   
          arr.push(val)
        }
       if(res.data.type !== 'chat'){ 
   
         // 系统消息不进行整点判断
         for (var i = 0; i < arr.length; i = i + 1) { 
   
           arr[i].string_time = moment(arr[i].time * 1000).format('L');
           arr[i].showTime = true;
         }

时间的解析

与之前的时间解析不同,如果对于每一条会话的时间都进行显示,就显得特别繁琐和难看了。这里模仿微信,在每一分钟才进行一次的会话时间的显示。这就需要判断每两个会话之间的时间差是否大于一分钟的。除此之外,第一条会话一定要显示其时间,否则用户无从判断。
实现如下:

// 用于时间显示
    moment.locale('zh-cn', { 
   
      longDateFormat: { 
   
        l: "YYYY-MM-DD",
        L: "YYYY-MM-DD HH:mm"
      }
    })
...
         // 系统消息不进行整点判断
         for (var i = 0; i < arr.length; i = i + 1) { 
   
           arr[i].string_time = moment(arr[i].time * 1000).format('L');
           arr[i].showTime = true;
         }
       } else{ 
   
         // 整点判断
         for (var i = 0; i < arr.length - 1; i = i + 1) { 
   
           if ((arr[i + 1].time - arr[i].time) > 60 || i === 0) { 
   
             arr[i].string_time = moment(arr[i].time * 1000).format('L');
             arr[i].showTime = true;
           }
         }
       }
        
        this.setData({ 
   
          testMessageDetail: { 
   
            data: arr
          },
        });

判断自己和对方

这里通过全局变量中的用户id进行己方和对方的判断。如果是对方则显示白底黑字,而己方则显示主题色底和白色字。己方在右边而对方在左边。通过两个不同的view标签并且通过wx:if实现这种效果。

 <view wx:if="{ 
    {!item.self}}" style="display:flex; flex-direction: row; padding: 10rpx 30rpx; margin-top: 20rpx; width: 100%">
          <image class="ava" src="{ 
    {item.target_user.avatar}}" />
          <view class="details">
            <text class="username" style="text-align:left;">{
  
  {item.target_user.nickname}}</text>
            <text class="content" style=" opacity: 0.8;max-width:70%;">{
  
  {item.content}}</text>
          </view>
        </view>
        <view wx:if="{ 
    {item.self}}" style="display:flex; flex-direction: row; padding: 10rpx 30rpx; margin-top: 20rpx;justify-content:flex-end;">
          <view class="details" style="align-items:flex-end;">
            <text class="username" style="text-align:right;">{
  
  {item.target_user.nickname}}</text>
            <text class="content" style="background-color:#ff7e67; color:white;">{
  
  {item.content}}</text>
          </view>
          <image class="ava" src="{ 
    {item.target_user.avatar}}" />
        </view>
      </view>
}
      // 记录会话ID
      this.data.session_id = res.data.id
      this.data.status = 'message'
      this.data.target_user_id = res.data.target_user.id
      for (let i in res.data.messages) { 
   
        if (res.data.messages[i].user_id === res.data.target_user.id) { 
   
          res.data.messages[i].target_user = res.data.target_user
          res.data.messages[i].self = false
        } else { 
   
          res.data.messages[i].target_user = { 
   
            nickname: app.globalData.userInfo.info.nickname,
            avatar: app.globalData.userInfo.info.avatar
          }
          res.data.messages[i].self = true
        }
      }

自动获取新消息

这里通过setTimeOut实现循环获取消息,延时设为5s,避免过多的请求。在每一次获取新消息后,都需要将ScrollView调整到最底部。下面会叙述其方法。
自动获取消息的时候,与第一次获取类似,只获取第一页,历史记录的获取使用触顶触发。

autoLoadMessage: async function(){ 
   
    var res = await server.request('GET', 'messages/' + this.data.session_id, { 
   
      page: 1,
      size: 1
    })
    if(res.data.length === 0) { 
   }
    else{ 
   
      
      var last = this.data.testMessageDetail.data[this.data.testMessageDetail.data.length - 1]
      var getLast = res.data.data.messages[0]
      if (getLast.content === last.content && getLast.time === last.time){ 
   

      }else{ 
   
        if (getLast.user_id === res.data.data.target_user.id) { 
   
          getLast.target_user = res.data.data.target_user
          getLast.self = false
        } else { 
   
          getLast.target_user = { 
   
            nickname: app.globalData.userInfo.info.nickname,
            avatar: app.globalData.userInfo.info.avatar
          }
          getLast.self = true
        }
        moment.locale('en', { 
   
          longDateFormat: { 
   
            l: "YYYY-MM-DD HH:mm",
            L: "YYYY-MM-DD HH:mm:ss"
          }
        })
        getLast.string_time = moment(getLast.time * 1000).format('L')
        getLast.showTime = false
        this.data.testMessageDetail.data.push(getLast)
        this.setData({ 
   
          testMessageDetail:{ 
   
            data: this.data.testMessageDetail.data
          }
        })
        this.setData({ 
   
          top_value: 100 * this.data.testMessageDetail.data.length,
        })
      }
      // if (getLast.content === (this.data.testMessageDetail.data.reserve()[0]).content){ 
   

      // }else{ 
   
      // // this.data.testMessageDetail.data.push(res.data.data.)
      // }
    }
    setTimeout(this.autoLoadMessage, 5000)
  }

ScrollView与触顶触发

触顶触发中,wxml使用如下:

 <scroll-view bindscrolltoupper="onMsgRefresh" id="dialog_list" scroll-y="{ 
    {true}}" scroll-with-animation='{ 
    {true}}' scroll-top='{ 
    {top_value}}'>

onMsgRefesh函数中,需要进行另一页面的获取,得到新的页面数据后,需要与之前的数据整合,并对整合后的数据进行时间显示的判断。

    if (!this.data.isLoading && !this.data.noMore){ 
   
      this.data.page = this.data.page + 1
      this.loadMessage(this.data.session_id, this.data.status, true)
    }
  },

除此之外,当然还需要正在加载中没有更多的显示。这里不多赘述。

在这里插入图片描述

ScrollView自动触底

这里需要考虑另外一个问题,新的会话已经获取,但是ScrollView并没有滚动到最底部,新的消息被遮挡了。这就需要让ScrollView滚动到最底部。对于这个标签,需要设定固定的大小,并且将scroll-top双向绑定。在获取新的数据后,进行如下设定:

 this.setData({ 
   
          top_value: 100 * this.data.testMessageDetail.data.length,
        })

去其中100需要根据实际情况而设定。

底部对话栏

这个的实现与之前详情页的评论栏/回复栏类似,不多赘述,欢迎大家看我的另一篇博客(地址在最上面)。
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/34530.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注