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

微信小程序原生接入腾讯云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}}</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}}</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}}},

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/62169.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微信小程序接入腾讯云IM即时通讯(发送消息开发步骤)

微信小程序接入腾讯云IM即时通讯&#xff08;聊天窗口&#xff09; 1.效果图&#xff1a; 2.功能点 &#xff1a; 1.布局要分左右两边布局&#xff0c;如果是自己为发送消息方&#xff0c;都在右边&#xff0c;对方发送的消息在左边。 2.腾讯云返回的是时间戳&#xff0c;需…

uniapp小程序接入腾讯IM聊天

腾讯IM中创建项目 拿到AppID 与 密钥 下载demo 将debug 文件夹拖到自己项目中 在第一个js文件中填入自己项目的APPID 与密钥 下载&#xff08;tim-wx-sdk 上传文件 的依赖 看项目所需 import TIM from tim-wx-sdk; import TIMUploadPlugin from tim-upload-plugin; let optio…

用最通俗易懂的语言告诉你什么是信息熵

图和公式都挂了&#xff0c;想看原文的小伙伴去公众号后台回复”信息熵”即可 假设有个考试作弊团伙&#xff0c;需要连续不断地向外传递4选1单选题的答案。直接传递ABCD的ascii码的话&#xff0c;每个答案需要8个bit的二进制编码&#xff0c;从传输的角度&#xff0c;这显然有…

外景黑纱婚纱照高级感十足

婚纱照不止有自然小清新(&#xff06;还可以暗黑高级 看腻了千篇一律的白色婚纱礼服 炎追求个性、与众不同的仙女们不妨尝试一下黑色婚纱&#x1f5a4; 黑纱不一定只有酷和暗黑 也有法式赫本风的复古优雅 满满的氛围感和高级感 &#x1f3ac; 女生的拖尾黑裙神秘又优雅 …

福州一般拍一套婚纱照要多少钱

随着婚纱摄影行业发展以来&#xff0c;近几年选择婚纱照旅拍的准新人越来越多了。相信每一位新人对于这些都是第一次没有经验的&#xff0c;所以也不太清楚行情&#xff0c;不知道选择怎么样的套餐&#xff0c;那小编就来科普一下现在旅拍的行情&#xff0c;让你们在选择婚纱照…

晒一下我和老婆的婚纱照!

两人一起的 老婆单人的 老婆就是大名鼎鼎的晴天有雨啊&#xff01; 我单人的

冬天拍婚纱照注意事项 拍出养眼婚纱照

寒冷的冬天来了&#xff0c;对于北方人就要迎接雪花了&#xff0c;在寒冷的冬季进行婚纱摄影&#xff0c;会美丽但是也会“冻人”哦!下面 南京婚纱摄影工作室给大家介绍冬天拍婚纱照注意事项&#xff0c;准新人们可以参考一下哦! 冬天拍婚纱照注意事项 拍出养眼婚纱照 婚纱照 冬…

国内唯美花海 新娘绝美婚纱照

每位新郎都希望有一位仙女般的妻子&#xff0c;新娘也都希望自己是花丛中的花仙子。一组浪漫唯美的花海婚纱照无疑是诠释新人们花仙子梦的最好渠道。下面 青岛摄影工作室 就来给大家盘点一下国内那些唯美的花海。 1、云南罗平油菜花 云南省的罗平。从每年的二月份开始就进入了油…

W ndoWS十p啥意思,婚纱照相册10P是什么意思?入册多少张最合适?

原标题&#xff1a;婚纱照相册10P是什么意思&#xff1f;入册多少张最合适&#xff1f; 在预定婚纱照时&#xff0c;都会谈及相册P数&#xff0c;尺寸大小等等一些相册信息&#xff0c;一般相册的页数有7、10、12、15、20p还有30p的,而我们常见就为10P&#xff0c;那么&#xf…

婚纱照效果 取景很重要

新人们都希望可以有绝美的婚纱照&#xff0c;却又为自身没有绝对的优势而感到沮丧。其实漂亮的婚纱照通过选择适合景点选取一样可以拍出来。 婚纱照应该在选景的时候注意哪些问题&#xff0c;很多人渴望拥有类似杨幂结婚时一样的明星婚纱照&#xff0c;首先在选景的时候&#…

简约复古婚纱照拍摄攻略

当下拍婚纱照也要紧跟时尚潮流&#xff0c;当下拍婚纱照流行什么呢&#xff1f;简约、复古是当下婚纱照拍摄关键词。将时尚复古元素相结合也是当下最受年轻人亲睐的婚纱照拍摄形式&#xff0c;那么如何拍摄简约复古婚纱照呢&#xff1f;下面就跟无锡婚纱摄影一起来看看简约复古…

Photoshop透明婚纱照抠图处理

透明婚纱照抠图处理教程&#xff1a;先需要用钢笔等工具把人物部分抠出来包括婚纱部分。然后把抠出的人物图层复制一层&#xff0c;在通道选区婚纱部分较为清晰的通道并调出选区&#xff0c;回到图层面板后把选区反选按删除高光部分以外的图像&#xff0c;得到的图像就是我们抠…

九宫格摆法_九宫格婚纱照摆法图片与技巧

婚纱照挂法已不再是单一的排版&#xff0c;九宫格婚纱照开始逐渐流行在新人之中。下面来看看九宫格婚纱照摆法图片&#xff0c;以及九宫格婚纱照摆法的技巧。 一、九宫格婚纱照摆法 九宫格最常见的挂法就是方方正正的33的形式&#xff0c;当然除了九宫格的挂法之外&#xff0c;…

福州黑白风格的婚纱照拍摄介绍

新人们有没有想过在老一辈的婚纱照都是黑白的色彩但是一样能够拍摄出非常好的效果&#xff0c;新人们有没有想要试一试黑白风格的婚纱照呢&#xff0c;今天就让福州婚纱摄影价格最实惠的婚纱摄影工作室来和您说说吧&#xff01; 光源的选择   拍摄黑白风格的婚纱照&#xff…

java+springboot影楼婚纱照预约系统ssm

为了扩大影楼的生意&#xff0c;提高影楼的宣传形象&#xff0c;方便客户在线预约套系等&#xff0c;我们设计一款关于影楼的网站。设计婚纱照预约系统的目的是为实现客户与影楼预约&#xff0c;明细化工作人员分配、提高工作效率&#xff0c;节约时间及其他杂项成本。本系统基…

婚纱照选场地

婚纱照 内场1风格&#xff1a;复古风 内场2风格 :花场 内场3风格&#xff1a;酷酷的玩游戏机的 外场&#xff1a;马场&#xff08;马厩、吉普车、铁罐子、草堆&#xff09;

响应式织梦模板婚纱照摄影类网站

模板介绍&#xff1a; 织梦内核开发的模板&#xff0c;该模板属于企业通用类、婚纱摄影、艺术摄影类企业都可使用&#xff0c; 这款模板使用范围极广&#xff0c;不仅仅局限于一类型的企业&#xff0c;你只需要把图片和产品内容&#xff1b; 换成你的&#xff0c;颜色都可以修改…

PS调出春夏外景婚纱照

效果图 先来看看原图和夏季的效果图 先看看原图 教程终于来咯 原图暗部太深&#xff0c;需要将暗部提亮。可以把暗部选区选出来。为了精确选择暗部选区&#xff0c;我利用计算命令如上图所示。最后得到暗部的选区。 上图得到了暗部选区&#xff0c;添加曲线调整图层提亮如图&am…

突变!微软将结束对Office Android应用的支持

整理 | 祝涛 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 早在6月底&#xff0c;就有报道称&#xff0c;微软可能计划将Chromebook上的Office用户转移到网络上。直到近日&#xff0c;微软正式宣布&#xff0c;将于9月18日起停止支持Chrome OS系统上的安卓版Of…