发送图片语音消息传送→
1.项目需求
2.参考文档
3.效果图
4.初始化 集成SDK
5.登录
6.会话列表
7.聊天页面
8.遇到的问题
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,只能参考文档自己采坑。
|
|
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 = true
wx.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判断是否未读 否则加1
if(!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.data
var number = 0
conversationList.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 = false
wx.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: '', //当前用户的tid
isDetail: true
}
})
// 因为所有的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.tid
this.loginIm()
},
fail: err => {
util.sLoadingHide()
wx.showToast({
title: 'get password error' + err,
icon: 'none',
duration: 3000
})
console.log(err)
}
})
},
//腾讯云im的登录
loginIm() {
var that = this
var tim = app.globalData.tim
let 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 = true
setTimeout(() => {
// 拉取会话列表
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.avatar
var name= e.currentTarget.dataset.name
wx.navigateTo({
url: '/subpackages/message-detail/index?conversationID=' + conversationID + '&avatar=' + avatar + '&name=' + name,
})
},
// 获取会话列表 (必须要在SDK处于ready状态调用(否则会报错))
initRecentContactList() {
var that = this
// 拉取会话列表
var tim = app.globalData.tim
let 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)
// 如果最后一条消息是自定义消息的话,处理一下data
const conversationList = imResponse.data.conversationList; // 会话列表,用该列表覆盖原有的会话列表
conversationList.forEach(e => {
if(e.lastMessage.type == 'TIMCustomElem') {
var data = e.lastMessage.payload.data
var 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 = 0
conversationList.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>
<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.houseid
var type = e.currentTarget.dataset.type // 0 building 1 shop
var avatar = e.currentTarget.dataset.avatar
var conversationID = 'C2C' + e.currentTarget.dataset.tid
var name = e.currentTarget.dataset.name
// C2Cc2020042017735
if(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、页面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:'',//房源id
type:'',//房源类型
height:'',
complete:0,//默认为有历史记录可以拉取
is_lock:true,//发送消息锁,
nav_title: '',
tim: '',
userSign: '',
userId: '', // 自己的id
conversationID: '', // 置业顾问的id
msgList: 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 = this
wx.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 shop
nav_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 = 0
if (e.detail.height) {
inputHeight = e.detail.height
}
this.setData({
inputHeight: inputHeight
})
this.pageScrollToBottom()
},
inputBlur(e) {
this.setData({
inputHeight: 0,
})
},
getPassword() {
var that = this
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 => {
that.setData({
userSign: res.data.sign,
userId: res.data.tid
})
app.globalData.accountTid = res.data.tid
var tim = app.globalData.tim
let promise = tim.login({userID: res.data.tid, userSig: res.data.sign})
promise.then(res => {
console.log('登录成功')
wx.setStorageSync('isImLogin', true)
app.globalData.isImLogin = true
setTimeout(() => {
that.getMsgList()
}, 1000);
})
},
fail: err => {
console.log(err)
}
})
},
getMsgList() {
console.log('获取会话列表')
var that = this
var tim = app.globalData.tim
if (that.data.houseId) {
// 从房源详情进入聊天界面(请求房源详情 发送一条自定义信息)// 0 building 1 shop
if (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 = this
var 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.tim
var 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.tim
var that = this
let promise = tim.setMessageRead({conversationID: that.data.conversationID})
promise.then(function(imResponse) {
// 已读上报成功
var noready = 0
that.data.myMessages.forEach(e => {
if(!e.isRead) {
noready++
}
})
var number = wx.getStorageSync('number_msg')
var newNumber = number - noready
wx.setStorageSync('number_msg', newNumber)
}).catch(function(imError) {
// 已读上报失败
console.warn('setMessageRead error:', imError);
})
},
//创建自定义房源消息体
createXzlmsg(){
// console.log('创建自定义房源消息体')
var that = this;
var id = that.data.houseId
http.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_GROUP
payload: {
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_data
var messageList = that.data.myMessages
messageList.push(res.data.message)
that.setData({
myMessages: messageList
})
// 发送自定义欢迎语
that.getSingleMsg()
})
}
},
fail: err => {
console.log(err)
}
})
},
//创建自定义房源消息体(商铺)
createShopmsg(){
var that = this;
var id = that.data.houseId
http.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_GROUP
payload: {
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_data
var messageList = that.data.myMessages
messageList.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.tim
var options = {
to: that.data.conversationID.slice(3), // 消息的接收方
conversationType: TIM.TYPES.CONV_C2C, // 会话类型取值TIM.TYPES.CONV_C2C或TIM.TYPES.CONV_GROUP
payload: content // 消息内容的容器
}
// // 发送文本消息,Web 端与小程序端相同
// 1. 创建消息实例,接口返回的实例可以上屏
let message = tim.createTextMessage(options)
// 2. 发送消息
let promise = tim.sendMessage(message)
promise.then(function(imResponse) {
// 发送成功
var messageList = that.data.myMessages
messageList.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.type
var 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 = this
if(!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()
}
})
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], args
if (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 || {}
// all
if (!arguments.length) {
this._cbs = {}
return
}
var callbacks = this._cbs[event]
if (!callbacks) return
// remove all handlers
if (arguments.length === 1) {
delete this._cbs[event]
return
}
// remove specific handler
var cb
for (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
然后就是登录问题 我一直觉得我的登录是有问题的 哈哈 如果有人指点就 更好了 嘿嘿 欢迎留言评论 后续会继续采坑优化 也会加上更多聊天功能 。不说啦 不说啦 继续我的采坑道路┭┮﹏┭┮。