微信小程序websocket实现即时聊天功能


项目场景:

微信小程序中实现websocket,即时聊天功能

核心包含以下内容

1.聊天功能实现

2.发送消息后滚动到底部

3.历史记录查询及下拉加载更多功能实现


代码示例:

wxml

<!--pages/wechat2/wechat2.wxml-->
 <view class="chat-header">
  <view class="header-image-box">
    <image class="header-image" src="{{receivebaseInfo.avatar}}"></image>
  </view>
  <view>
    <view class="chat-name">{{receivebaseInfo.nickname}}</view>
    <view class="chat-company">{{receivebaseInfo.company_name}} - {{receivebaseInfo.department_name}}</view>
  </view>
 </view>
<view class='news'>
 <view class="historycon">
 <scroll-view 
  scroll-y="true" 
  scroll-into-view="{{scrollid}}" 
  scroll-with-animation = "{{true}}"  
  style="height:{{scrollHeight}}" 
  class="history"
  refresher-enabled="true"
  bindrefresherrefresh="refresh"
  refresher-triggered="{{triggered}}"
  >
  <!-- 历史记录部分start -->
  <view wx:if="{{historyList.length > 0}}">
    <view class="historyText">历史消息</view>
    <block wx:for="{{historyList}}" wx:key="index">
      <!--此处为other -->
      <view wx:if="{{item.type==1}}" id="historyscrollid{{index}}">
      <view>
      <text class='chat-time' style="display:none;">{{item.date}}</text>
      </view>
      <view class='other-record'>
      <image class='other-head-img' src='{{receivebaseInfo.avatar}}'></image>
      <view class='other-record-content-triangle'></view>
      <view class='other-record-content'>
      {{item.content_msg}}</view>
      </view>
      </view>
      <!--此处为结尾 -->
      <!--此处为own -->
      <view id="historyscrollid{{index}}" wx:else>
      <view>
      <text class='chat-time' style="display:none;">{{item.date}}</text>
      </view>
      <view class='own-record'>
      <view class='own-record-content'>{{item.content_msg}}</view>
      <view class='own-record-content-triangle'></view>
      <image class='own-head-img' src='{{sendAvatar}}'></image>
      </view>
      </view>
      <!-- own结尾 -->
    </block>
    <view class="historyText">以上为历史消息</view>
  </view>
  <!-- 历史记录部分end -->
  <block wx:for="{{newsList}}" wx:key="index">
  <!--此处为other -->
  <view wx:if="{{item.type==1}}" id="scrollid{{index}}">
   <view>
   <text class='chat-time' style="display:none;">{{item.date}}</text>
   </view>
   <view class='other-record'>
   <image class='other-head-img' src='{{receivebaseInfo.avatar}}'></image>
   <view class='other-record-content-triangle'></view>
   <view class='other-record-content'>
   {{item.message}}</view>
   </view>
  </view>
  <!--此处为结尾 -->
  <!--此处为own -->
  <view id="scrollid{{index}}" wx:else>
   <view>
   <text class='chat-time' style="display:none;">{{item.date}}</text>
   </view>
   <view class='own-record'>
   <view class='own-record-content'>{{item.message}}</view>
   <view class='own-record-content-triangle'></view>
   <image class='own-head-img' src='{{sendAvatar}}'></image>
   </view>
  </view>
  <!-- own结尾 -->
  </block>
 </scroll-view>
 </view>
</view>
<view class="sendmessage">
 <view class="send-message">
  <input class="chat-input" type="text" bindinput="bindChange" confirm-type="done" value='{{input}}' cursor-spacing="16px" hold-keyboard="{{true}}" placeholder="" />
  <image class="chat-emotion" src="../../static/img/chat-emotion.svg" bindtap="emotionChange"></image>
  <button class="btn" bindtap='send'>发送</button>
 </view>
 <view class="emotions" wx:if="{{emotionVisible}}">
  <view class="emotions-item"  wx:for="{{connectemoji}}" wx:key="index" bindtap="addemotion" data-index="{{index}}">{{item}}</view>
 </view>
</view>


**说明:**结构主要包括头部聊天方的信息部分,历史记录部分,即时聊天内容部分,发送消息部分

wxss

/* pages/wechat2/wechat2.wxss */
page { 
  background-color: #f3f3f4; 
 
 } 

 /* 新增样式 */
 .chat-header {
   display: flex;
   align-items: center;
   justify-content: flex-start;
   height: 88px;
   padding: 0 12px;
   background-color: #fff;
   border-radius: 0px 0px 10px 10px;
 }

 .header-image-box {
   width: 64px;
   height: 64px;
   border-radius: 50%;
   margin-right: 12px;
 }

 .header-image {
   width: 100%;
   height: 100%;
   border-radius: 50%;
 }

 .chat-name {
   color: #333;
   font-size: 16px;
   font-weight: 700;
 }

 .chat-company {
  font-size: 14px;
  color: rgba(81, 81, 81, 100);
 }
 
 .tab { 
  padding: 20rpx 20rpx 40rpx 50rpx; 
  height: 20%; 
  background-color: white; 
 } 
 
 .tab .tent { 
  font-size: 33rpx; 
  margin-bottom: 30rpx; 
 } 
 .jia_img{ 
  height: 80rpx; 
  width: 90rpx; 
 } 
 .new_imgtent{ 
  height: 180rpx; 
  width: 190rpx; 
 } 
 .tab .fabu { 
  font-size: 33rpx; 
  margin-top: 30rpx; 
  margin-bottom: 30rpx; 
 } 
 
 .xiahuaxia { 
  width: 80%; 
  text-align: center; 
  margin: 0 auto; 
  position: relative; 
  top: 60rpx; 
 } 
 
 .chat-time { 
  text-align: center; 
  padding: 5rpx 20rpx 5rpx 20rpx; 
  width: 200rpx; 
  font-size: 26rpx; 
  background-color: #e6e6e6; 
 } 
 
 .new_top_txt { 
  width: 50%; 
  position: relative; 
  top: 38rpx; 
  text-align: center; 
  margin: 0 auto; 
  font-size: 30rpx; 
  color: #787878; 
  background-color: #f7f7f7; 
 } 
 
 /* 聊天内容 */ 
 
 .news { 
  margin-top: 30px; 
  text-align: center; 
  margin-bottom: 98px; 
 } 
 
 .img_null { 
  height: 60rpx; 
 } 
 
 .l { 
  height: 5rpx; 
  width: 20%; 
  margin-top: 30rpx; 
  color: #000; 
 } 
 
 /* 聊天 */ 
 
 .my_right { 
  float: right; 
  position: relative; 
  right: 40rpx; 
 } 
 
 .you_left { 
  float: left; 
  position: relative; 
  left: 5rpx; 
 } 
 
 .new_img { 
  width: 100rpx; 
  height: 100rpx; 
  border-radius: 50%; 
 } 
 
 .sanjiao { 
  top: 20rpx; 
  position: relative; 
  width: 0px; 
  height: 0px; 
  border-width: 10px; 
  border-style: solid; 
 } 
 
 .my { 
  border-color: transparent transparent transparent #95d4ff; 
 } 
 
 .you { 
  border-color: transparent #95d4ff transparent transparent; 
 } 
 
 .sendmessage { 
   /* display: flex;
   align-items: center;
  flex-direction: row; */
  width: 100%; 
  min-height: 60px;
  position: fixed; 
  bottom: 0px; 
  padding: 0 16px;
  background-color: rgba(242, 242, 242, 100); 
  box-shadow: 0px -1px 5px 1px rgba(57, 57, 57, 0.1);
 } 

 .send-message {
  display: flex;
  align-items: center;
  padding: 16px;
 }
 
 .sendmessage input { 
  height: 80rpx; 
  background-color: white; 
  line-height: 80rpx; 
  font-size: 28rpx; 
  padding-left: 20rpx; 
 } 
 
 .sendmessage button { 
  width: 52px !important; 
  height: 32px; 
  line-height: 32px;
  background: #169171 !important; 
  color: #fff !important; 
  font-size: 14px !important;
  text-align: center;
  border: 0 !important;
  padding: 0 !important;
  margin: 0 !important;
 } 
 
 .historycon { 
  height: 90%; 
  /* background-color: pink; */
  width: 100%; 
  flex-direction: column; 
  display: flex; 
  /* margin-top: 100rpx;  */
  border-top: 0px; 
 } 
 .hei{ 
  margin-top: 50px; 
  height: 20rpx; 
 } 
 .history { 
  /* height: 300px;  */
  margin-top: 30rpx; 
  margin: 20rpx; 
  font-size: 28rpx; 
  line-height: 80rpx; 
  word-break: break-all; 
 } 
 .chat-input{
   width: 60%;
   height: 40px;
   border: 0;
   border-radius: 8px;
   margin-left: 5rpx;
 }

 .back-icon{
  margin-top: 25rpx;
  margin-left: 25rpx; 
  width:40rpx;
  height:40rpx;
 }
 .other-record-content{
  background-color: #fff;
  width: 180px; 
  border-radius: 7px; 
  padding: 0 20px;
  text-align: left;
  margin: 6px 0;
 }
 .other-record{
 
  display: flex;
  justify-content:flex-start;
 }
 .other-head-img{
  width:70rpx;
  height:70rpx;
  border-radius: 50%;
  margin: 10rpx 10rpx 10rpx 10rpx;
 }
 .other-record-content-triangle{ 
 width: 0; 
 height: 0; 
 border-top: 10rpx solid transparent; 
 border-right: 15rpx solid #fff; 
 border-bottom: 10rpx solid transparent;
 margin-top: 36rpx; 
 }
 .own-record{
  display: flex;
  justify-content:flex-end;
  padding-right:30rpx; 
 }
 .own-record-content{
  background-color: #209072;
  width: 180px; 
  border-radius: 8px; 
  padding: 0 20px;
  color: #fff;
  text-align: left;
  margin: 6px 0;
 }
 .own-record-content-triangle { 
 width: 0; 
 height: 0; 
 /* border-top: 20rpx solid transparent; 
 border-left: 40rpx solid #F0F0F0; 
 border-bottom: 20rpx solid transparent;  */
 border-top: 10rpx solid transparent; 
 border-left: 15rpx solid #209072; 
 border-bottom: 10rpx solid transparent; 
 margin-top: 36rpx; 
 }
 .own-head-img{
  width:70rpx;
  height:70rpx;
  border-radius: 50%;
  margin: 10rpx 10rpx 10rpx 10rpx;
 }
 ::-webkit-scrollbar{
  width: 0;
  height: 0;
  color: transparent;
 }

 .chat-emotion {
   width: 28px;
   height: 28px;
   margin: 0 12px;
 }

 .emotions {
   display: flex;
   align-items: flex-start;
   justify-content: flex-start;
   width: 200px;
   height: 36px;
   margin: 6px;
 }

 .emotions-item {
   width: 24px;
   height: 24px;
   margin: 0 8px;
 }

 .historyText {
   color: #ccc;
 }

js


var utils = require("../../utils/util.js")
const app = getApp()
const api = require('../../utils/request.js'); //相对路径
Page({

 /**
 * 页面的初始数据
 */
 data: {
  receivebaseInfo:{},
  sendAvatar:'',
 newsList:[
   {
     date: "2020.10.19",
     message:'哈喽,好久不见',
     type: 0
   },
   {
    date: "2020.10.20",
    message:'是呀,好久不见',
    type: 1
  },
  {
    date: "2020.10.20",
    message:'是呀,好久不见',
    type: 1
  },
 ],//消息列表
 historyList:[],
 input:null,
 openid:null,
 connectemoji: ["😘","😡","😔","😄","❤"],
 emoji_list: ['emoji1i1', 'emoji2i2', 'emoji3i3', 'emoji4i4', 'emoji5i5'],
 emotionVisible: false,
 inputShowed: false,
 scrollTop: 0,
 inputBottom: '0px',
 receiveMemberId:null,
 sendMemberId:null,
 scrollid:'scrollid',
 scrollHeight:'300px',
//  下拉刷新
 triggered:true,
//  历史记录当前页
 pageNo: 1,
 },

 /**
 * 生命周期函数--监听页面加载
 */
 onLoad: function (options) {
var receiveMemberId = options.receiveMemberId
var sendMemberId = app.globalData.open_id
var sendAvatar = app.globalData.sendAvatar
 var _this = this;
 _this.setData({
  receiveMemberId,
  sendMemberId,
  sendAvatar
 })
 console.log(app.globalData.sendAvatar,'hahha')
//  获取内存中的数据
this.getStorageBaseInfo()
//  设置滚动区域的高度
this.setScrollHeight()
//  获取历史记录 
this.getHistory()
// 初始化websocket
 this.initWebSocket()
//  页面进入滚动到底部
 this.scrollBottom()
 },

 /**
 * 生命周期函数--监听页面初次渲染完成
 */
 onReady: function () {

 },

//  websocket初始化
initWebSocket: function(){
  var _this = this;
  var {receiveMemberId, sendMemberId} = this.data
  //建立连接
  wx.connectSocket({
    url: `ws://10.200.18.18:1818/zxxt/${sendMemberId}/${receiveMemberId}`,//本地
    success: function () {
      console.log('websocket连接成功~')
    },
    fail: function () {
      console.log('websocket连接失败~')
    },
  })

  //连接成功
  wx.onSocketOpen(function () {
    console.log('onSocketOpen','连接成功,真正的成功');
  })

//  接收服务器的消息事件
 wx.onSocketMessage(function(res){

// 接收到的消息{date,message,type}  type类型为 1 是对方的消息 为 0 是自己的消息

  var list = [];
  list = _this.data.newsList;
  var _data = JSON.parse(res.data);

  list.push(_data);
  console.log(list)
  _this.setData({
   newsList:list
  })
  _this.scrollBottom()
 },
)

//  监听连接关闭
wx.onSocketClose(function(){
  console.log('监听 WebSocket 连接关闭事件')
})

},

// 获取历史记录
getHistory: function(){
  var {receiveMemberId, sendMemberId,pageNo} = this.data
  var params = {
    receiveMemberId,
    sendMemberId,
    pageNo,
    pageSize:5,
  }
  api.get("/zxxt/chat/msg/list", params, (res) => {
    if (res.code == 'success') {
      // var historyList = res.data.data
      var historyList =  [...res.data.data,...this.data.historyList]
      if (historyList && historyList.length > 0) {
        historyList.forEach(item => {
          if (item.send_member_id == sendMemberId) {
            item.type = 0
          }else {
            item.type = 1
          }
        });
        this.setData({
          historyList
        })
        console.log(this.data.historyList,'历史记录数据')
      }else {
        // 判断是否是第一次进入查看历史记录:是(不显示弹框,不是则显示弹框)
        if(this.data.pageNo > 1) {
          wx.showToast({
            title: "没有更多历史记录了",
            icon: 'none',
            duration: 2000
          })
        }
      }
    } else {
      if (res.message) {
        wx.showToast({
          title: res.message,
          icon: 'none',
          duration: 2000
        })
      }
    }
  }, (res) => {
    if (res.message) {
      wx.showToast({
        title: res.message,
        icon: 'none',
        duration: 2000
      })
    }
  })
},

// 滚动到底部
scrollBottom:function() {
  var {newsList} = this.data
  var scrollid = `scrollid${newsList.length - 1}`
  this.setData({
    scrollid
  })
},

// 设置滚动区域的高度
setScrollHeight:function(){
  const client = wx.getSystemInfoSync().windowHeight    // 获取当前窗口的高度
  var scrollHeight = (client - 236) + 'px'
  this.setData({
    scrollHeight
  })
},

// 获取内存中聊天列表的用户信息
getStorageBaseInfo: function(){
  //获取存储信息
  wx.getStorage({
    key: 'receivebaseInfo',
    success: (res)=>{
      this.setData({
        receivebaseInfo:res.data
      })
    }
  })
},

// 自定义下拉刷新
refresh: function(){
  // 下拉的实际操作
  var pageNo = this.data.pageNo + 1
  this.setData({
    pageNo
  })
  if (this.timer) {
    clearTimeout(this.timer)
  }
  this.timer = setTimeout(()=>{
    this.setData({
      triggered:false
    })
    this.getHistory()
  },2000)
},

 /**
 * 生命周期函数--监听页面显示
 */
 onShow: function () {

 },

 /**
 * 生命周期函数--监听页面隐藏
 */
 onHide: function () {

 },

 /**
 * 生命周期函数--监听页面卸载
 */
 onUnload: function () {

 },

 /**
 * 页面相关事件处理函数--监听用户下拉动作
 */
 onPullDownRefresh: function () {

 },

 /**
 * 页面上拉触底事件的处理函数
 */
 onReachBottom: function () {

 },

 /**
 * 用户点击右上角分享
 */
 onShareAppMessage: function () {

 },
 send :function(){
 var _this = this;
 if(_this.data.input){
 wx.sendSocketMessage({
  data: _this.data.input,
  success: (res) =>{
    console.log(res)
  },
  fail: (err)=>{
      console.log('sendSocketMessage','失败')
  }
 })
 var list = [];
 list = this.data.newsList;
 var temp = { 'message': _this.data.input, 'date': utils.formatTime(new Date()), type: 0 };
 list.push(temp);
 this.setData({
  newsList:list,
  input:null
 })

 this.scrollBottom()
//  表情选择隐藏
this.setData({
  emotionVisible:false,
})

}

//  this.bottom()
const client = wx.getSystemInfoSync().windowHeight    // 获取当前窗口的高度
console.log(client,'shurugaodu')

 },
 bindChange:function(res){
 this.setData({
  input: res.detail.value,
 })
 },
 back:function(){
 wx.closeSocket();
 console.log('连接断开');
 },
 emotionChange:function(){
   this.setData({
     emotionVisible:!this.data.emotionVisible
   })
 },
 addemotion:function(e){
   console.log(e.currentTarget.dataset.index,"点了设默默")
    let {connectemoji,input} = this.data
    if (input) {
      input = input + connectemoji[e.currentTarget.dataset.index];
    }else {
      input = connectemoji[e.currentTarget.dataset.index]
    }
    
    console.log(input,'输入框额值')
    this.setData({
      input
    })
 },
  // 公共聚焦方法,方法比较笨,但是过度效果平滑流畅
  bottom: function() {

    var that = this;

  // 获取元素的高度
  let query = wx.createSelectorQuery();
  query.select('.news').boundingClientRect(rect => {
    //获取到元素
    let scrollTop = rect.height;
    this.setData({
      scrollTop
    })
  }).exec();

  console.log(this.data.scrollTop,'hahah')
  
    wx.pageScrollTo({
      // scrollTop: this.data.scrollTop + 30,
      scrollTop: 10000,
      // duration: 0
    })
  },
})



一、聊天功能

核心api

1.wx.connectSocket

2.wx.onSocketOpen

3.wx.onSocketMessage

4.wx.onSocketClose

5.链接微信开发文档机票


二、发送消息后滚动到底部

核心方法

1.scroll-view 的使用(wxml)

 <scroll-view 
  scroll-y="true" 
  scroll-into-view="{{scrollid}}" 
  scroll-with-animation = "{{true}}"  
  style="height:{{scrollHeight}}" 
  class="history"
  refresher-enabled="true"
  bindrefresherrefresh="refresh"
  refresher-triggered="{{triggered}}"
  >
</scroll-view>

2.scrollBottom方法(js)


// 滚动到底部
scrollBottom:function() {
  var {newsList} = this.data
  var scrollid = `scrollid${newsList.length - 1}`
  this.setData({
    scrollid
  })
},


三、历史记录查询及下拉加载更多功能实现

核心部分

1.scroll-view 的使用(wxml)

 <scroll-view 
  scroll-y="true" 
  scroll-into-view="{{scrollid}}" 
  scroll-with-animation = "{{true}}"  
  style="height:{{scrollHeight}}" 
  class="history"
  refresher-enabled="true"
  bindrefresherrefresh="refresh"
  refresher-triggered="{{triggered}}"
  >
</scroll-view>

2.getHistory方法(js)


// 获取历史记录
getHistory: function(){
  var {receiveMemberId, sendMemberId,pageNo} = this.data
  var params = {
    receiveMemberId,
    sendMemberId,
    pageNo,
    pageSize:5,
  }
  api.get("/zxxt/chat/msg/list", params, (res) => {
    if (res.code == 'success') {
      // var historyList = res.data.data
      var historyList =  [...res.data.data,...this.data.historyList]
      if (historyList && historyList.length > 0) {
        historyList.forEach(item => {
          if (item.send_member_id == sendMemberId) {
            item.type = 0
          }else {
            item.type = 1
          }
        });
        this.setData({
          historyList
        })
        console.log(this.data.historyList,'历史记录数据')
      }else {
        // 判断是否是第一次进入查看历史记录:是(不显示弹框,不是则显示弹框)
        if(this.data.pageNo > 1) {
          wx.showToast({
            title: "没有更多历史记录了",
            icon: 'none',
            duration: 2000
          })
        }
      }
    } else {
      if (res.message) {
        wx.showToast({
          title: res.message,
          icon: 'none',
          duration: 2000
        })
      }
    }
  }, (res) => {
    if (res.message) {
      wx.showToast({
        title: res.message,
        icon: 'none',
        duration: 2000
      })
    }
  })
},

注:此方法可写在初始化websocket的回掉函数中

3.scroll-view开发文档及配置机票


四、微信小程序即时聊天功能其他注意事项

1.ws://10.200.157.18:8005/ 与wss的区别

2.微信小程序input使用hold-keyboard保持键盘不收起配置 hold-keyboard="{{true}}"

3.键盘遮挡input及textarea配置 cursor-spacing=“16px”


<input class="chat-input" type="text" bindinput="bindChange" confirm-type="done" value='{{input}}' cursor-spacing="16px" hold-keyboard="{{true}}" placeholder="" />


五、本文参考文章

文章参考机票

包含前端后代码很有参考价值

相关git推荐

前后端代码机票

websocket在线 测试工具机票

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值