微信小程序原生接入腾讯云im(单聊,列表,聊天界面,自定义消息,自动回复)

发送图片语音消息传送→

文章目录

1.项目需求
2.参考文档
3.效果图
4.初始化 集成SDK
5.登录
6.会话列表
7.聊天页面
8.遇到的问题

项目需求

公司需要在已上线的小程序中新增聊天功能(房源租赁小程序,跟置业顾问沟通)(ÒωÓױ)!,最后决定使用腾讯云im,无奈 只能硬着头皮上 o(╥﹏╥)o,接下来记录一下整个开发步骤,后期还会不断优化聊天功能 。╮(╯▽╰)╭

参考文档

https://cloud.tencent.com/document/product/269/37413 (腾讯im 集成文档)
https://imsdk-1252463788.file.myqcloud.com/IM_DOC/Web/SDK.html?=_ga=1.205222681.809978884.1544594125#createTextMessage(sdk
客户端api文档)

由于腾讯云的demo使用了mpvue框架 我这儿是用原生写的 o(╥﹏╥)o,只能参考文档自己采坑。

效果图

聊天页面 会话列表

初始化

参考腾讯云im集成文档 小程序项目集成方法如下(若同步依赖过程中出现问题,请切换 npm 源后再次重试(使用cnpm))在终端进入到小程序项目根目录执行:npm install 未初始化的情况下要先 npm init 在“工具”-“npm构建”完成后

1,集成SDK

// IM 小程序 SDK
npm install tim-wx-sdk --save
// 发送图片、文件等消息需要的 COS SDK
npm install cos-wx-sdk-v5 --save

2,在项目脚本里引入模块,并初始化
这里的初始化代码写在了app.js文件里(里面包含各种监听事件,这里暂时还未对各个监听函数进行封装)(目前阶段主要用到了 收到消息,发送消息 )
// 这里引入了一个监听器 (因为小程序没有类似vuex的状态管理器 当global里面的数据变化时不能及时同步到聊天页面 因此 这个监听器可以emit一个方法 到需要更新会话数据的页面 在那里进行赋值)后面会在遇到的问题里说这个方法
引入

import TIM from 'tim-wx-sdk'
import COS from "cos-wx-sdk-v5"
App({onLaunch: function () {this.iminit()},iminit() {let options = {SDKAppID: ****** // 接入时需要将0替换为您的即时通信 IM 应用的 SDKAppID}var that = this// 创建 SDK 实例,`TIM.create()`方法对于同一个 `SDKAppID` 只会返回同一份实例let tim = TIM.create(options);// SDK 实例通常用 tim 表示// 设置 SDK 日志输出级别,详细分级请参见 setLogLevel 接口的说明// tim.setLogLevel(0); // 普通级别,日志量较多,接入时建议使用tim.setLogLevel(1); // release 级别,SDK 输出关键信息,生产环境时建议使用// 注册 COS SDK 插件tim.registerPlugin({'cos-wx-sdk': COS})// 监听事件,例如:tim.on(TIM.EVENT.SDK_READY, function(event) {console.log('SDK_READY')that.globalData.isImLogin = truewx.setStorageSync('isImLogin', true)// 收到离线消息和会话列表同步完毕通知,接入侧可以调用 sendMessage 等需要鉴权的接口// event.name - TIM.EVENT.SDK_READY});tim.on(TIM.EVENT.MESSAGE_RECEIVED, function(event) {console.log('收到消息')// 若同时收到多个会话 需要根据conversationID来判断是哪个人的会话var msgarr = []var newMsgForm = event.data[0].conversationID // 定义会话键值console.log(msgarr[newMsgForm])if(msgarr[newMsgForm]) {msgarr[newMsgForm].push(event.data[0])} else {msgarr[newMsgForm] = [event.data[0]]}console.log(msgarr[newMsgForm])that.globalData.myMessages = msgarr// 这里引入了一个监听器 (因为小程序没有类似vuex的状态管理器 当global里面的数据变化时不能及时同步到聊天页面 因此 这个监听器可以emit一个方法 到需要更新会话数据的页面 在那里进行赋值)wx.event.emit('testFunc',that.globalData.myMessages,newMsgForm) // 详情页的函数wx.event.emit('conversation') // 会话列表的监听函数// 未读消息数var number = wx.getStorageSync('number_msg') || 0// 根据isRead判断是否未读 否则加1if(!event.data[0].isRead) {number = number++}console.log(number)wx.setStorageSync('number_msg', number)// 如果有未读数 需要设置tabbar的红点标志 反之去掉红点标志if(number>0) {wx.setTabBarBadge({index: 2,text: number.toString()})} else {wx.hideTabBarRedDot({index: 2})}// 收到推送的单聊、群聊、群提示、群系统通知的新消息,可通过遍历 event.data 获取消息列表数据并渲染到页面// event.name - TIM.EVENT.MESSAGE_RECEIVED// event.data - 存储 Message 对象的数组 - [Message]})tim.on(TIM.EVENT.MESSAGE_REVOKED, function(event) {// 收到消息被撤回的通知// event.name - TIM.EVENT.MESSAGE_REVOKED// event.data - 存储 Message 对象的数组 - [Message] - 每个 Message 对象的 isRevoked 属性值为 true});tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, function(event) {// 更新当前所有会话列表// 注意 这个函数在首次点击进入会话列表的时候也会执行 因此点击消息 可以显示当前的未读消息数(unreadCount表示未读数)console.log('发送了消息')console.log('更新当前所有会话列表')var conversationList = event.datavar number =  0conversationList.forEach(e => {number = number + e.unreadCount})wx.setStorageSync('number_msg', number)if(number>0) {wx.setTabBarBadge({index: 2,text: number.toString()})} else {wx.hideTabBarRedDot({index: 2})}// 收到会话列表更新通知,可通过遍历 event.data 获取会话列表数据并渲染到页面// event.name - TIM.EVENT.CONVERSATION_LIST_UPDATED// event.data - 存储 Conversation 对象的数组 - [Conversation]});tim.on(TIM.EVENT.GROUP_LIST_UPDATED, function(event) {// 收到群组列表更新通知,可通过遍历 event.data 获取群组列表数据并渲染到页面// event.name - TIM.EVENT.GROUP_LIST_UPDATED// event.data - 存储 Group 对象的数组 - [Group]});tim.on(TIM.EVENT.GROUP_SYSTEM_NOTICE_RECEIVED, function(event) {// 收到新的群系统通知// event.name - TIM.EVENT.GROUP_SYSTEM_NOTICE_RECEIVED// event.data.type - 群系统通知的类型,详情请参见 GroupSystemNoticePayload 的 operationType 枚举值说明// event.data.message - Message 对象,可将 event.data.message.content 渲染到到页面});tim.on(TIM.EVENT.PROFILE_UPDATED, function(event) {// 收到自己或好友的资料变更通知// event.name - TIM.EVENT.PROFILE_UPDATED// event.data - 存储 Profile 对象的数组 - [Profile]});tim.on(TIM.EVENT.BLACKLIST_UPDATED, function(event) {// 收到黑名单列表更新通知// event.name - TIM.EVENT.BLACKLIST_UPDATED// event.data - 存储 userID 的数组 - [userID]});tim.on(TIM.EVENT.ERROR, function(event) {// 收到 SDK 发生错误通知,可以获取错误码和错误信息// event.name - TIM.EVENT.ERROR// event.data.code - 错误码// event.data.message - 错误信息});tim.on(TIM.EVENT.SDK_NOT_READY, function(event) {// wx.setStorageSync('isImLogin', false)console.log('SDK_NOT_READY')that.globalData.isImLogin = falsewx.setStorageSync('isImLogin', false)// 收到 SDK 进入 not ready 状态通知,此时 SDK 无法正常工作// event.name - TIM.EVENT.SDK_NOT_READY});tim.on(TIM.EVENT.KICKED_OUT, function(event) {console.log('KICKED_OUT')wx.setStorageSync('isImLogin', false)that.globalData.isImLogin = false// 收到被踢下线通知// event.name - TIM.EVENT.KICKED_OUT// event.data.type - 被踢下线的原因,例如://    - TIM.TYPES.KICKED_OUT_MULT_ACCOUNT 多实例登录被踢//    - TIM.TYPES.KICKED_OUT_MULT_DEVICE 多终端登录被踢//    - TIM.TYPES.KICKED_OUT_USERSIG_EXPIRED 签名过期被踢})that.globalData.tim = tim},globalData: {tim: '',isImLogin: false,msgList: [],myMessages: new Map(),tabBottom: 0, // 全面屏底部黑条高度accountTid: '', //当前用户的tidisDetail: true  }})

登录

点击底部消息到消息列表 (onShow里面加判断)


// 因为所有的api调用都需要SDK处于read状态才可以 此处如果登录我存在了global里面 因为不知道如何判断SDK是否处于read状态 只能每次进入都登录一次(不刷新的话不需要重新登录) 呃(⊙o⊙)…
// wx.getStorageSync('isImLogin') 之前尝试存在本地缓存 发现一刷新 SDK就不处于read状态了 onShow: function () {if (app.globalData.isImLogin) {// 已经登录了SDK处于read状态this.setData({hasUserInfo: true})// 由于登录是写在会话列表的 因此如果已经登录 (SDK处于ready状态)就直接获取会话列表(会话列表函数在下面会话列表里整体贴)this.initRecentContactList()} else {if (wx.getStorageSync('tokenAdmin')) {util.sLoading()this.setData({hasUserInfo: true})// 获取登录密码userSign和tid(这里通过后端接口获取)this.getPassword()} else {// 没有登录 就会出现一个授权页 让用户登录(小程序的登录)针对没有登录过的用户,登录过的用户做了静默登录 会自动登录this.setData({hasUserInfo: false})}}}// 获取登录所用的userSign 和 tid(密码)getPassword() {http.getUserSign({header: {'Authorization': 'bearer ' + wx.getStorageSync('tokenAdmin').access_token},data: {openId: wx.getStorageSync('tokenAdmin').openId,nickName: app.globalData.userInfo ? app.globalData.userInfo.nickName : '',faceUrl: app.globalData.userInfo ? app.globalData.userInfo.avatarUrl : ''},success: res => {this.setData({userSign: res.data.sign,userId: res.data.tid})app.globalData.accountTid = res.data.tidthis.loginIm()},fail: err => {util.sLoadingHide()wx.showToast({title: 'get password error' + err,icon: 'none',duration: 3000})console.log(err)}})},//腾讯云im的登录loginIm() {var that = thisvar tim = app.globalData.timlet promise = tim.login({userID: that.data.userId, userSig: that.data.userSign});promise.then(function(imResponse) {console.log(imResponse)console.log('登录成功')wx.setStorageSync('isImLogin', true)app.globalData.isImLogin = truesetTimeout(() => {// 拉取会话列表that.initRecentContactList()}, 1000);}).catch(function(imError) {util.sLoadingHide()wx.showToast({title: 'login error' + imError,icon: 'none',duration: 3000})console.warn('login error:', imError); // 登录失败的相关信息})},

会话列表

1,会话列表wxml

<!--pages/message/index.wxml-->
<wxs src="../../utils/filter.wxs" module="filter"/>
<view style='padding-top: calc({{height}}px + 18rpx)'>// 自定义头部<nav-bar title="消息" showIcon="0"></nav-bar><block wx:for="{{msg}}" wx:key="index"><view class="item" bindtap="contactsClick" data-conversationid="{{item.conversationID}}" data-name="{{item.userProfile.nick}}" data-avatar="{{item.userProfile.avatar}}"><image src="{{item.userProfile.avatar ? item.userProfile.avatar : '/images/avatar.png'}}" class="avatar"></image><view class="right"><view class="name"><text>{{item.userProfile.nick}}</text><text class="tag" wx:if="{{filter.consultant(item.userProfile.userID)}}">置业顾问</text></view><view class="text" wx:if="{{item.lastMessage.type != 'TIMCustomElem'}}">{{item.lastMessage.payload.text}}</view><view class="text" wx:if="{{item.lastMessage.type == 'TIMCustomElem'}}">[房源]{{item.lastMessage.payload.data.title || item.lastMessage.payload.description}}<text style="padding-left: 10rpx">{{item.lastMessage.payload.data.price || item.lastMessage.payload.extension}}元/m²·月</text></view></view><view class="time">{{filter.getDateDiff(item.lastMessage.lastTime, now)}}</view><view class="unreadCount" wx:if="{{item.unreadCount > 0}}">{{item.unreadCount * 1 > 99 ? '99+' : item.unreadCount}}</view></view></block><!-- 使用消息需要授权登录 根据需要 自己封装--><login wx:if="{{!hasUserInfo}}" bind:closePage="closePage"><view class="middle_box"><view class="line" style="height: calc({{height}}px + 16rpx)"></view><image class="yzz_logo" src="/images/yzz_logo.png"></image><view class="des">为了给您提供更好的服务,壹直租申请获取您的昵称、头像信息</view><view class="login_btn">授权登录</view></view></login>
</view>
<view wx:if="{{ empty_show }}" class="empty"><image src="/images/msg_empty.png" class="msg_empty"></image><view class="empty_text">暂无聊天记录</view>
</view>

2,会话列表页面样式wxss

/* pages/message/index.wxss */
page{background-color: #fff;
}
.item{border-bottom: 1px solid #EDEDED;height: 149rpx;display: flex;position: relative;align-items: center;
}
.item:nth-of-type(1) {border-top: 1px solid #EDEDED;
}
.avatar{width: 89rpx;height: 89rpx;border-radius: 50%;margin-left: 40rpx;margin-right: 22rpx;box-sizing: content-box;
}
.right .name {font-size:32rpx;color:rgba(35,35,35,1);margin-bottom: 8rpx;display: flex;align-items: center;
}
.right .name .tag{width:114rpx;height:30rpx;background:rgba(246,247,248,1);border-radius:15rpx;color: #9EA2AC;font-size: 22rpx;display: flex;justify-content: center;align-items: center;margin-top: 4rpx;margin-left: 10rpx;
}
.right .text {color: #A2A3A4;font-size: 24rpx;width: 466rpx;overflow: hidden;white-space: nowrap;text-overflow: ellipsis;
}
.item .time{position: absolute;right: 34rpx;top: 44rpx;color: #9EA2AC;font-size: 20rpx;
}
.unreadCount {position: absolute;right: 34rpx;top: 79rpx;color: #fff;background-color: #F00C22;font-size: 20rpx;border-radius: 50rpx;display: flex;justify-content: center;align-items: center;padding: 0 10rpx;height: 32rpx;min-width: 32rpx;
}
.middle_box{position: fixed;left: 0;top: 0;z-index: 999;height: 100%;width: 100%;display:flex;flex-direction:column;align-items:center;/* justify-content: center; */background-color: #fff;
}
.middle_box .line{width:750rpx;box-shadow:0px 1px 0px 0px rgba(239,239,239,1);
}
.yzz_logo{width: 104rpx;height: 165rpx;margin-top: 290rpx;margin-bottom: 50rpx;
}
.middle_box .des{width:373rpx;height:55rpx;font-size:24rpx;color:rgba(160,160,160,1);margin-bottom: 140rpx;line-height: 38rpx;text-align: left;
}
.login_btn{width:548rpx;height:88rpx;background:rgba(255,147,40,1);border-radius:6rpx;font-size: 28rpx;color: #FFFFFF;display: flex;justify-content: center;align-items: center;
}
.empty{display: flex;flex-direction: column;align-items: center;border-top: 1px solid #efefef;
}
.msg_empty{width: 350rpx;height: 246rpx;margin-top: 198rpx;
}
.empty_text{font-size: 24rpx;color: #AAAAAA;margin-top: 52rpx;
}

3,会话列表页面js

data: {userId: '',hasUserInfo: false,userSign: '',nickName: '',msg: [],empty_show: false,now: '',height: app.globalData.height },// 点击消息列表跳转到聊天详情页(需要把列表页的头像传过去,因为详情获取的数据里面没有聊天头像)contactsClick(e) {var conversationID= e.currentTarget.dataset.conversationid // 置业顾问的conversationID(当前会话的人)var avatar= e.currentTarget.dataset.avatarvar name= e.currentTarget.dataset.namewx.navigateTo({url: '/subpackages/message-detail/index?conversationID=' + conversationID + '&avatar=' + avatar  + '&name=' + name,})},// 获取会话列表 (必须要在SDK处于ready状态调用(否则会报错))initRecentContactList() {var that = this// 拉取会话列表var tim = app.globalData.timlet promise = tim.getConversationList();if(!promise) {util.sLoadingHide()wx.showToast({title: 'SDK not ready',icon: 'none',duration: 3000})return}promise.then(function(imResponse) {util.sLoadingHide()console.log('会话列表')console.log(imResponse)// 如果最后一条消息是自定义消息的话,处理一下dataconst conversationList = imResponse.data.conversationList; // 会话列表,用该列表覆盖原有的会话列表conversationList.forEach(e => {if(e.lastMessage.type == 'TIMCustomElem') {var data = e.lastMessage.payload.datavar new_data = ''if(typeof(data) == 'string' && data) {new_data = JSON.parse(data)}e.lastMessage.payload.data = new_data}})that.setData({msg: conversationList,empty_show: conversationList && conversationList.length>0 ? false : true})var number = 0conversationList.forEach(e => {number = number + e.unreadCount})if(number>0) {wx.setTabBarBadge({index: 2,text: number.toString()})} else {wx.hideTabBarRedDot({index: 2})}}).catch(function(imError) {util.sLoadingHide()wx.showToast({title: 'getConversationList error:' + imError,icon: 'none',duration: 3000})console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息})},

补充: 从房源进入的跳转(需要多传一个type和housid)(详情页的置业顾问 在这儿封装成了的组件,使用如下(主要看msgindex))

详情页写法:

<block wx:for="{{baseDto.consultantsDtos}}" wx:key="index" wx:if="{{index<showCum}}"><adviser item="{{item}}" houseid="{{baseDto.id}}" msgindex="{{msg_index}}" type="{{rentType}}"></adviser>
</block>
onLoad: function (options) {msg_index: options.index || 0
},

组件内部跳转

meassge(e) {console.log(e.currentTarget.dataset)var houseid = e.currentTarget.dataset.houseidvar type = e.currentTarget.dataset.type // 0 building 1 shopvar avatar = e.currentTarget.dataset.avatarvar conversationID = 'C2C' + e.currentTarget.dataset.tidvar name = e.currentTarget.dataset.name// C2Cc2020042017735if(this.properties.msgindex) {// 点击直接返回聊天界面(从聊天界面进入的)(处理多次从发送的房源点进去再聊天 小程序页面打开数超过10个点不动问题)wx.navigateBack({delta: 1})} else {wx.navigateTo({url: '/subpackages/message-detail/index?type=' + type + '&houseid=' + houseid + '&conversationID=' + conversationID + '&avatar=' + avatar  + '&name=' + name,})}},

聊天页面

详情页:可以一对一文字聊天 下拉加载更多历史记录 进入聊天详情的入口有两个 1、从房源详情进入 2、直接从会话列表进入 区别: 房源详情进入需要 发送一条当前房源的数据信息(自定义信息)需要有一条自动回复 (类似置业顾问的欢迎语),从列表进入则不需要。

1、页面wxml

<view class='chat' id="chat" style="min-height:{{height}}px; padding-bottom:116rpx; background-color:#EFF0F3"><!-- <view class="more"><text class="more_text">{{more_text}}</text></view> 下拉加载更多 --><view class="more"><text class="more_text">聊天的时候,置业顾问无法知道您的手机号!</text></view><block wx:for="{{myMessages}}" wx:key="index" ><!-- 自定义消息 --><view class="chat_box" wx:if="{{item.type == 'TIMCustomElem'}}"><view class="chat-item" wx:if="{{item.flow == 'in'}}" data-type="{{item.payload.data.type}}" data-id="{{item.payload.data.id}}" bindtap="house_detail"><image class='avatar' style="margin-right: 19rpx;" mode= "scaleToFill" src="{{friendAvatarUrl ? friendAvatarUrl : '/images/avatar.png'}}"></image><view class="custom_box"><image src="{{item.payload.data.house_pic}}" class="pic"></image><view class="des_box"><view class="title">{{item.payload.data.title}}</view><view class="des"><view>{{item.payload.data.area}}m²</view><view style="padding:0 8rpx">|</view><view class="park_name">{{item.payload.data.city}}·{{item.payload.data.park}}</view></view><view class="price">¥{{item.payload.data.price}}元/m²·月</view></view></view></view><view wx:else class="chat-item flex-wrap" data-type="{{item.payload.data.type}}" data-id="{{item.payload.data.id}}" bindtap="house_detail"><view class='avatar' style="margin-left: 19rpx" wx:if="{{item.flow == 'out'}}"><open-data type="userAvatarUrl"></open-data></view><view class="custom_box"><image src="{{item.payload.data.house_pic}}" class="pic"></image><view class="des_box"><view class="title">{{item.payload.data.title}}</view><view class="des"><view>{{item.payload.data.area}}m²</view><view style="padding:0 8rpx">|</view><view class="park_name">{{item.payload.data.city}}·{{item.payload.data.park}}</view></view><view class="price">¥{{item.payload.data.price}}元/m²·月</view></view></view></view></view><view class="chat_box" wx:if="{{item.type != 'TIMCustomElem'}}"><view class="chat-item {{item.flow == 'in' ? '' : 'flex-wrap'}}"><image wx:if="{{item.flow == 'in'}}" class='avatar' style="margin-right: 19rpx;" mode= "scaleToFill" src="{{friendAvatarUrl ? friendAvatarUrl : '/images/avatar.png'}}"></image><view class='avatar' style="margin-left: 19rpx" wx:else><open-data  type="userAvatarUrl"></open-data></view><view class='content'>{{item.payload.text}}</view></view></view></block>
</view>
<view class="chat-footer" style="padding-bottom: calc({{tabBottom}}px + 25rpx)">
<view class='input' bindtap="bindFocus"><textarea class="inputArea" focus="{{focus}}" fixed="true" cursor-spacing="25" disable-default-padding="true" bindinput="bindKeyInput" bindfocus="bindfocus" bindblur="bindblur" value="{{inputValue}}" placeholder=""/><text class="placeHolder" wx:if="{{inputShow}}">对ta发送消息</text>
</view><view class='send' bindtap='bindConfirm'>发送</view>
</view>

2,聊天详情页css

/* subpackages/message-detail/index.wxss */
.custom_box{width: 510rpx;border-radius: 4rpx;box-shadow:0px 4px 12px 0px rgba(4,0,0,0.03);background-color: #fff;display: flex;padding: 25rpx 20rpx;
}
.chat{overflow: scroll;
}
.pic{width: 162rpx;height: 141rpx;border-radius: 2rpx;margin-right: 18rpx;flex-shrink: 0;
}
.des_box{}
.title{font-size: 28rpx;color: #232323;overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp:2;-webkit-box-orient: vertical;
}
.des_box .des{display: flex;color: #999999;font-size: 20rpx;width: 280rpx;height: 30rpx;margin-top: 2rpx;
}
.park_name{flex: 1;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;text-overflow: ellipsis;
}
.des_box .price{font-size: 28rpx;color: #FF8711;margin-top: 6rpx;
}
.chat_box{padding: 0 33rpx;
}
.avatar{width:78rpx;height:78rpx;border-radius:50%;overflow: hidden;
}
.chat-item{display: flex;margin-bottom: 46rpx;
}
.chat-item.flex-wrap{flex-direction: row-reverse;
}
.chat-item .content{max-width: 512rpx;padding: 24rpx;border-radius: 4rpx;background-color: #fff;color: #232323;font-size: 28rpx;word-wrap: break-word;
}
.chat-footer{position: fixed;bottom: 0;left: 0;width: 100%;background: #ffffff;display: flex;justify-content: space-between;align-items: center;padding: 20rpx 25rpx;
}
.chat-footer.full_sucreen{padding-bottom: 100rpx;
}
.input{width:527rpx;height:76rpx;line-height: 76rpx;background:rgba(255,255,255,1);border: none;border:1px solid rgba(212, 215, 222, 1);border-radius:6rpx;font-size: 26rpx;padding:0 20rpx;display: flex;flex-direction: row;align-items: center;position: relative;
}
.inputArea{position: absolute;width: 487rpx;height: 30rpx;line-height: 30rpx;left: 20rpx;top:50%;margin-top: -15rpx;z-index: 1;
}
.placeHolder{position: absolute;font-size: 26rpx;
color: #cccccc;height: 50rpx;line-height: 50rpx;left: 20rpx;top:50%;margin-top: -25rpx;z-index: 0;
}
.send{color: #fff;background-color: #FF9328;width: 124rpx;height: 76rpx;border-radius: 12rpx;display: flex;justify-content: center;align-items: center;font-size: 26rpx;
}
.footer-h{position: fixed;top: 100px;
}
.more{display: flex;justify-content: center;align-items: center;
}
.more_text{padding: 6rpx 14rpx;background:rgba(216,216,216,1);border-radius:4rpx;color: #FFFFFF;font-size: 20rpx;margin: 30rpx auto;
}

3、聊天详情页js

import TIM from 'tim-wx-sdk'
import http from '../../utils/api.js'
const app = getApp()
Page({/*** 页面的初始数据*/data: {noData: '/images/defaultPark.png',houseDefault: '/images/delete.png',inputValue:'',//发送的文字消息内容myMessages: [],//消息selToID:0,scrollTop: 0,houseId:'',//房源idtype:'',//房源类型height:'',complete:0,//默认为有历史记录可以拉取is_lock:true,//发送消息锁,nav_title: '',tim: '',userSign: '',userId: '', // 自己的idconversationID: '', // 置业顾问的idmsgList: app.globalData.msgList,friendAvatarUrl: '',tabBottom: app.globalData.tabBottom,top_height: app.globalData.height,isCompleted: false,nextReqMessageID: '',more_text: '下拉查看更多历史信息',isSuperSend: false,isDetail: false,inputHeight: 0,inputShow:true,focus:false,adjust: true},/*** 生命周期函数--监听页面加载*/onLoad: function (options) {var that = thiswx.showLoading({title: '加载中...',icon: 'none'})that.setData({conversationID: options.conversationID,friendAvatarUrl: options.avatar,height: wx.getSystemInfoSync().windowHeight,houseId: options.houseid * 1 || '',type: options.type* 1, // 0 building 1 shopnav_title: options.name,// 设置头部title(自定义的)isDetail: true})wx.setNavigationBarTitle({title: options.name})// 滚动到底部that.pageScrollToBottom()wx.event.on('testFunc',(e,newMsgForm)=>{console.log('testFunc')if((newMsgForm === options.conversationID) && app.globalData.isDetail) {var newmsg = app.globalData.myMessages[that.data.conversationID]if (newmsg) {newmsg.forEach(e => {if(e.type == 'TIMCustomElem') {if(typeof(e.payload.data) == 'string' && e.payload.data) {var new_data = JSON.parse(e.payload.data)e.payload.data = new_data}}if(!e.isRead) {that.setData({myMessages: that.data.myMessages.concat(newmsg)})}})}console.log(that.data.myMessages)that.setMessageRead()that.pageScrollToBottom()}})// watch.setWatcher(that); // 设置监听器,建议在onLoad下调用if(app.globalData.isImLogin) {console.log('登录了')// 获取消息列表that.getMsgList()} else {console.log('未登录')that.getPassword()}},watch:{myMessages:function(newVal,oldVal){console.log(newVal,oldVal)}},inputFocus(e) {console.log(e)var inputHeight = 0if (e.detail.height) {inputHeight = e.detail.height}this.setData({inputHeight: inputHeight})this.pageScrollToBottom()},inputBlur(e) {this.setData({inputHeight: 0,})},getPassword() {var that = thishttp.getUserSign({header: {'Authorization': 'bearer ' + wx.getStorageSync('tokenAdmin').access_token},data: {openId: wx.getStorageSync('tokenAdmin').openId,nickName: app.globalData.userInfo ? app.globalData.userInfo.nickName : '',faceUrl: app.globalData.userInfo ? app.globalData.userInfo.avatarUrl : ''},success: res => {that.setData({userSign: res.data.sign,userId: res.data.tid})app.globalData.accountTid = res.data.tidvar tim = app.globalData.timlet promise = tim.login({userID: res.data.tid, userSig: res.data.sign})promise.then(res => {console.log('登录成功')wx.setStorageSync('isImLogin', true)app.globalData.isImLogin = truesetTimeout(() => {that.getMsgList()}, 1000);})},fail: err => {console.log(err)}})},getMsgList() {console.log('获取会话列表')var that = thisvar tim = app.globalData.timif (that.data.houseId) {// 从房源详情进入聊天界面(请求房源详情 发送一条自定义信息)// 0 building 1 shopif (that.data.type * 1 === 0) {that.createXzlmsg()} else if(that.data.type * 1 === 1){that.createShopmsg()}}// 拉取会话列表var params = {conversationID: that.data.conversationID, count: 15,nextReqMessageID: that.data.nextReqMessageID}let promise = tim.getMessageList(params);promise.then(function(imResponse) {console.log('会话列表')const messageList = imResponse.data.messageList; // 消息列表。// 处理自定义的消息messageList.forEach(e => {if(e.type == 'TIMCustomElem') {if(typeof(e.payload.data) == 'string' && e.payload.data) {var new_data = JSON.parse(e.payload.data)e.payload.data = new_data}}})const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。// 将某会话下所有未读消息已读上报that.setMessageRead()that.setData({myMessages: messageList,isCompleted: isCompleted,nextReqMessageID: nextReqMessageID,more_text: isCompleted ? '没有更多了': '下拉查看更多历史信息'})wx.hideLoading()that.pageScrollToBottom()}).catch(function(imError) {console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息});},// 默认欢迎语getSingleMsg() {var that = thisvar text = '您好,我是天安置业顾问' + that.data.nav_title + ',很高兴为您服务,请问有什么可以帮到您?'http.sendSingleMsg({header: {'Content-Type': 'application/json','Authorization': 'bearer ' + wx.getStorageSync('tokenAdmin').access_token},data: {fromAccount: that.data.conversationID.slice(3),toAccount: app.globalData.accountTid,text: text,isSuperSend: that.data.isSuperSend,},success: res => {console.log('发送欢迎语')that.pageScrollToBottom()},fail: err=> {console.log(err)}})},// 下来加载更多聊天历史记录getMoreMsgList() {wx.hideLoading()// console.log('获取会话列表')var tim = app.globalData.timvar that = this// 拉取会话列表var params = {conversationID: that.data.conversationID, count: 15,nextReqMessageID: that.data.nextReqMessageID}let promise = tim.getMessageList(params);promise.then(function(imResponse) {// console.log('下拉获取会话列表')// 处理自定义的消息imResponse.data.messageList.forEach(e => {if(e.type == 'TIMCustomElem') {if(e.payload.data) {var new_data = JSON.parse(e.payload.data)e.payload.data = new_data}}})const messageList = imResponse.data.messageList.concat(that.data.myMessages); // 消息列表。const nextReqMessageID = imResponse.data.nextReqMessageID; // 用于续拉,分页续拉时需传入该字段。const isCompleted = imResponse.data.isCompleted; // 表示是否已经拉完所有消息。that.setData({myMessages: messageList,isCompleted: isCompleted,nextReqMessageID: nextReqMessageID,more_text: isCompleted ? '没有更多了': '下拉查看更多历史信息'})}).catch(function(imError) {console.warn('getConversationList error:', imError); // 获取会话列表失败的相关信息});},// 设置已读上报setMessageRead() {var tim = app.globalData.timvar that = thislet promise = tim.setMessageRead({conversationID: that.data.conversationID})promise.then(function(imResponse) {// 已读上报成功var noready = 0that.data.myMessages.forEach(e => {if(!e.isRead) {noready++}})var number = wx.getStorageSync('number_msg')var newNumber = number - noreadywx.setStorageSync('number_msg', newNumber)}).catch(function(imError) {// 已读上报失败console.warn('setMessageRead error:', imError);})},//创建自定义房源消息体createXzlmsg(){// console.log('创建自定义房源消息体')var that = this;var id = that.data.houseIdhttp.xzlDetail(id, {data: {timestamp: Date.parse(new Date())},success: res => {if(res.code == 200) {var house_pic = res.data.coverUrl ? res.data.coverUrl : '/images/detail_default.jpg' // 房源图片var area = res.data.areaConstruction // 面积var price = res.data.unitPrice // 单价var park = res.data.parkName // 园区名称var city = res.data.parkArea // 城市var title = res.data.title // 标题var type = 0 // 类型 // 0:写字楼,1:商铺,2:广告位const params =  {house_pic: house_pic,area: area,price: price,park: park,city: city,title: title,type: type,id: id}const option = {to: that.data.conversationID.slice(3), // 消息的接收方conversationType: TIM.TYPES.CONV_C2C, // 会话类型取值TIM.TYPES.CONV_C2C或TIM.TYPES.CONV_GROUPpayload: {data: JSON.stringify(params),// 自定义消息的数据字段description: params.title, // 自定义消息的说明字段extension: params.price // 自定义消息的扩展字段} // 消息内容的容器}const tim = app.globalData.tim// 2. 创建消息实例,接口返回的实例可以上屏let message = tim.createCustomMessage(option)// 2. 发送消息let promise = tim.sendMessage(message)promise.then(function(res){// 发送成功// console.log('自定义消息发送成功')var new_data = JSON.parse(res.data.message.payload.data) res.data.message.payload.data = new_datavar messageList = that.data.myMessagesmessageList.push(res.data.message)that.setData({myMessages: messageList})// 发送自定义欢迎语that.getSingleMsg()})}},fail: err => {console.log(err)}})},//创建自定义房源消息体(商铺)createShopmsg(){var that = this;var id = that.data.houseIdhttp.shopDetail(id, {data: {timestamp: Date.parse(new Date())},success: res => {if(res.code == 200) {var house_pic = res.data.coverUrl ? res.data.coverUrl : '/images/detail_default.jpg' // 房源图片var area = res.data.areaConstruction // 面积var price = res.data.unitPrice || '0' // 单价var park = res.data.parkName // 园区名称var city = res.data.parkArea // 城市var title = res.data.title // 标题var type = 1 // 类型const params =  {house_pic: house_pic,area: area,price: price,park: park,city: city,title: title,type: type,id: id}const option = {to: that.data.conversationID.slice(3), // 消息的接收方conversationType: TIM.TYPES.CONV_C2C, // 会话类型取值TIM.TYPES.CONV_C2C或TIM.TYPES.CONV_GROUPpayload: {data: JSON.stringify(params),// 自定义消息的数据字段description: params.title, // 自定义消息的说明字段extension: params.price // 自定义消息的扩展字段} // 消息内容的容器}const tim = app.globalData.tim// 2. 创建消息实例,接口返回的实例可以上屏let message = tim.createCustomMessage(option)// 2. 发送消息let promise = tim.sendMessage(message)promise.then(function(res){// 发送成功var new_data = JSON.parse(res.data.message.payload.data) res.data.message.payload.data = new_datavar messageList = that.data.myMessagesmessageList.push(res.data.message)that.setData({myMessages: messageList})// 发送自定义欢迎语that.getSingleMsg()})}},fail: err => {console.log(err)}})},//获取普通文本消息bindKeyInput(e){var that = this;that.setData({inputValue:e.detail.value,})},bindfocus(){var that = this;that.setData({inputShow:false,focus:true,adjust: true})},bindblur(){var that = this;if(that.data.inputValue){that.setData({inputShow:false,focus:false})}else{that.setData({inputShow:true,focus:false})}// 键盘消失wx.hideKeyboard()// this.setData({//   adjust: false// })},// 发送普通文本消息bindConfirm(e) {var that = this;if(that.data.is_lock){that.setData({is_lock:false})if (that.data.inputValue.length == 0) {wx.showToast({title: '消息不能为空!',icon:'none'})that.setData({is_lock: true})return;}var content = {text: that.data.inputValue};var tim = app.globalData.timvar options = {to: that.data.conversationID.slice(3), // 消息的接收方conversationType: TIM.TYPES.CONV_C2C, // 会话类型取值TIM.TYPES.CONV_C2C或TIM.TYPES.CONV_GROUPpayload: content // 消息内容的容器}// // 发送文本消息,Web 端与小程序端相同// 1. 创建消息实例,接口返回的实例可以上屏let message = tim.createTextMessage(options)// 2. 发送消息let promise = tim.sendMessage(message)promise.then(function(imResponse) {// 发送成功var messageList = that.data.myMessagesmessageList.push(imResponse.data.message)that.setData({is_lock:true,myMessages: messageList})that.pageScrollToBottom()that.clearInput()}).catch(function(imError) {// 发送失败console.warn('sendMessage error:', imError);})}},// 清除输入框clearInput(e){this.setData({inputValue:''})},// 跳转house_detail(e) {var type = e.currentTarget.dataset.typevar id = e.currentTarget.dataset.id// // 0:写字楼,1:商铺if (type*1 === 0) {wx.navigateTo({url: `/pageHouse/xzl-detail/index?id=${id}&&index=1`})} else if(type*1 === 1) {wx.navigateTo({url: `/pageHouse/shop-detail/index?id=${id}&&index=1`})}},/*** 生命周期函数--监听页面显示*/onShow: function () {app.globalData.isDetail = true},/*** 生命周期函数--监听页面隐藏*/onHide: function () {// 键盘消失wx.hideKeyboard()// this.setData({//   adjust: false// })},/*** 生命周期函数--监听页面卸载*/onUnload: function () {// 关闭聊天界面的时候需要把当前聊天界面的监听器关闭 否则会一直监听着 在其他页面出现调用多次的问题wx.event.off("testFunc")// 键盘消失wx.hideKeyboard()// this.setData({//   adjust: false// })},/*** 页面相关事件处理函数--监听用户下拉动作*/onPullDownRefresh: function () {var that = thisif(!that.data.isCompleted) {wx.showLoading({title: '加载历史记录中...',icon: 'none'})that.getMoreMsgList()} else {wx.showToast({title: '没有更多历史记录了',icon:'none'})}setTimeout(() => {wx.stopPullDownRefresh(true)}, 300);},pageScrollToBottom() {wx.createSelectorQuery().select('#chat').boundingClientRect(function (rect) {// 使页面滚动到底部wx.pageScrollTo({selector: '#chat',scrollTop: rect ? rect.height : 0,duration: 0})}).exec()}
})

遇到的问题

开发过程遇到的其中一个问题就是数据同步 应该是有很多方法的 这儿采用的是引入一个监听器 使用方法 1,在app.js里面

import Event from './utils/event.js'
//挂载到wx对象上
wx.event=new Event();

2,创建event.js文件放在util里面
/utils/event.js

class Event {/*** on 方法把订阅者所想要订阅的事件及相应的回调函数记录在 Event 对象的 _cbs 属性中*/on(event, fn) {if (typeof fn != "function") {console.error('fn must be a function')return}this._cbs = this._cbs || {};(this._cbs[event] = this._cbs[event] || []).push(fn)}/*** emit 方法接受一个事件名称参数,在 Event 对象的 _cbs 属性中取出对应的数组,并逐个执行里面的回调函数*/emit(event) {this._cbs = this._cbs || {}var callbacks = this._cbs[event], argsif (callbacks) {callbacks = callbacks.slice(0)args = [].slice.call(arguments, 1)for (var i = 0, len = callbacks.length; i < len; i++) {callbacks[i].apply(null, args)}}}/*** off 方法接受事件名称和当初注册的回调函数作参数,在 Event 对象的 _cbs 属性中删除对应的回调函数。*/off(event, fn) {this._cbs = this._cbs || {}// allif (!arguments.length) {this._cbs = {}return}var callbacks = this._cbs[event]if (!callbacks) return// remove all handlersif (arguments.length === 1) {delete this._cbs[event]return}// remove specific handlervar cbfor (var i = 0, len = callbacks.length; i < len; i++) {cb = callbacks[i]if (cb === fn || cb.fn === fn) {callbacks.splice(i, 1)break}}return}}export default Event

filter.wxs

  // 时间转换
getDateDiff: function(dateTimeStamp,now){var result;var minute = 1000 * 60;var hour = minute * 60;var day = hour * 24;var halfamonth = day * 15;var month = day * 30;var diffValue = now - dateTimeStamp * 1000;if(diffValue < 0){return;}var monthC =diffValue/month;var weekC =diffValue/(7*day);var dayC =diffValue/day;var hourC =diffValue/hour;var minC =diffValue/minute;if(monthC>=1){result="" + parseInt(monthC) + "月前";} else if(weekC>=1){result="" + parseInt(weekC) + "周前";} else if(dayC>=1){result=""+ parseInt(dayC) +"天前";} else if(hourC>=1){result=""+ parseInt(hourC) +"小时前";} else if(minC>=1){result=""+ parseInt(minC) +"分钟前";} else result="刚刚";return result;},// 是否是置业顾问consultant: function (str) {if(str) {if (str.substring(0,1) == 'c') {return true}}},

然后就是登录问题 我一直觉得我的登录是有问题的 哈哈 如果有人指点就 更好了 嘿嘿 欢迎留言评论 后续会继续采坑优化 也会加上更多聊天功能 。不说啦 不说啦 继续我的采坑道路┭┮﹏┭┮。

微信小程序原生接入腾讯云im(单聊,列表,聊天界面,自定义消息,自动回复)相关推荐

  1. 微信小程序手把手接入腾讯地图

    正经学徒,佛系记录,不搞事情 一.理解腾讯地图于小程序的作用 要想在小程序上使用腾讯地图,就要先去理解腾讯地图是在做什么 map 地图.该组件是原生组件,使用时请注意相关限制. 个性化地图能力可在小程 ...

  2. 微信小程序直播,腾讯云直播+微信小程序实现实时直播

    一:小程序代码端 小程序直播使用小程序组件 live-pusher 组件和live-player组件 首先开通直播权限 小程序开发工具内进行推流拉流都不会成功,所以需要使用两个手机进行推拉流测试: 1 ...

  3. 微信小程序上传图片到腾讯云服务器,微信小程序 (发帖功能), 上传本地图片到腾讯云怎么实现?...

    1 我刚开始用 lin-ui组件的 imagePicker组件,但是只能实现图片本地上传,本地预览.删除等功能, 无法跟腾讯云cos对象存储交互. cos对象代码 cos.putObject({ Bu ...

  4. 小程序,是腾讯云的杀手锏?

    "[报告下载]后台回复关键词"数据智能"可免费下载数据猿最新发布的完整高清版<2021中国数据智能产业发展报告> 大数据产业创新服务媒体 --聚焦数据 · 改 ...

  5. 微信小程序 -- 原生JS集成腾讯IM实时聊天/实时音视频(踩坑及心得)

    原生JS集成腾讯IM实时聊天/实时音视频对话功能 一.腾讯IM集成 前期准备 实例创建及初始化 IM登录 收发消息 二.腾讯音视频实时互动 跑通demo 三.同时集成即时通讯IM 和 音视频直播的 坑 ...

  6. 校园社团微信小程序,基于腾讯小程序云开发,后端完整代码包括社团通知,社团简介,社团福利,社团章程,社团招新,社团活动报名预约等

    功能介绍 校园社团小程序,前后端完整代码包括社团通知,社团简介,社团福利,社团章程,社团招新,社团活动报名预约等功能,采用腾讯提供的小程序云开发解决方案,无须服务器和域名 预约管理:开始/截止时间/人 ...

  7. 【微信小程序-原生开发】实用教程08 - 开通微信云开发,操作云数据库新增数据(含修改数据权限),初始化云服务(含获取微信云环境 id),获取云数据,滚动公告栏

    开始前,请先完成圆梦宝典中宫格导航的开发,详见 [微信小程序-原生开发]实用教程 07 - Grid 宫格导航,详情页,侧边导航(含自定义页面顶部导航文字) https://blog.csdn.net ...

  8. 【微信小程序-原生开发】实用教程09 - 可滚动选项,动态列表-步骤条(含事件传参),动态详情(含微信云查询单条数据 doc)

    开始前,请先完成圆梦宝典中滚动公告栏的开发,详见 [微信小程序-原生开发]实用教程 08 - 开通微信云开发,操作云数据库新增数据(含修改数据权限),初始化云服务(含获取微信云环境 id),获取云数据 ...

  9. 微信小程序原生开发功能合集一:微信小程序开发介绍

    一.专栏介绍   本专栏主要内容为微信小程序常用功能开发过程的介绍说明,包括开发微信小程序常用组件的封装.常用功能的开发等,提供源代码.开发过程讲解视频.完整的课程等.   组件封装: 下拉选择组件. ...

最新文章

  1. WINCE6.0系统调用
  2. NTU 课程笔记: CV6422 regression
  3. IOS 学习笔记 2015-03-22 OC-API-日期
  4. Py之playsound:playsound的简介、安装、使用方法之详细攻略
  5. Android 显示全文折叠控件
  6. 解题报告——蓝桥杯 试题 基础练习 字符串对比——16行代码AC
  7. ZooKeeper,策展人以及微服务负载平衡的工作方式
  8. pppcloud云主机内LINUX用户安全管理2
  9. 升级鸿蒙系统无法选择应用,申请鸿蒙系统有一个应用选择怎么选择呢
  10. 偶然发现的Unity3d,两点之间的距离计算。
  11. MongoDB驱动程序快速入门
  12. karto探秘之open_karto 第三章 --- 扫描匹配
  13. python数字图像处理(4):图像数据类型及颜色空间转换
  14. java final修饰的数组_Java基于final修饰数据过程解析
  15. uniapp引入阿里图标库
  16. 你在公司项目里面看过哪些操蛋的代码?
  17. 把照片的字转换为数字版
  18. MATLAB麦克劳林展开式cosx,用matlab绘制e^x的泰勒展开式的图像
  19. 三循环流水灯电路的原理
  20. 前端面试题之浏览器原理篇

热门文章

  1. 春夏秋冬java用代码怎么写_春夏秋冬季节判断 (C语言代码)
  2. FROM_UNIXTIME 格式化MYSQL时间戳函数
  3. 单节锂电池充电方案三种常用电路
  4. Core 3.0使用Swagger<完全图解>
  5. 新一轮的应用商店拒绝潮表明iOS 14.5、新iPad可能即将面世
  6. 5G NR R16 SPS ---- 半持续调度
  7. 交换机和大机技术术语(大全)
  8. 【亚马逊运营】借助Coupon流量入口,让你的爆款锦上添花!
  9. 关于自学activiti开源流程引擎的一点点感悟和代码分享demo教程
  10. android 笔记管理app,安卓有什么记笔记的软件?安卓平台好用的笔记app