微信小程序即時通訊開發記錄(結合通訊云IM)
發表時間:2021-3-15
發布人:葵宇科技
瀏覽次數:61
前言
最近接到一個需求是在原項目的基礎上去開發一個即時通訊的功能,主要是一對一的聊天,供客戶和客戶之間進行即時通訊,功能主要包括聊天列表、發送文字、發送表情、發送圖片、發送視頻、發送語音、發送自定義信息等功能。因為項目需求,所以選擇了騰訊云IM來開發。
開發流程
首先是要注冊騰訊云并創建應用,拿到APPID和秘鑰。
文檔位置: cloud.tencent.com/document/pr…
1.安裝依賴
// IM 小程序 SDK
npm install tim-wx-sdk --save
// 發送圖片、文件等消息需要騰訊云 即時通信 IM 上傳插件
npm install tim-upload-plugin --save
復制代碼
2.在項目腳本里引入模塊,并初始化。
安裝完依賴以后在項目目錄的utils目錄下創建tencentIM目錄,用于存放關于即時通訊IM相關的js文件。
封裝event.js
因為項目中需要一些時間監聽,所以需要在utils里面添加一個event類,然后在app.js上面綁定到wx全局對象上。
// app.js
import Event from './utils/tencentIM/event'
wx.event = new Event();
// event.js
class Event {
version = "1.0.0";
constructor() {
this._events = {};
}
on(eventName, listener) {
if (!eventName || !listener) return;
// 判斷回調的 listener 是否為函數
if (isValidListener(listener)) {
throw new TypeError("listener must be a function");
}
let events = this._events;
let listeners = (events[eventName] = events[eventName] || []);
let listenerIsWrapped = typeof listener === "object";
// 不重復添加事件,判斷是否有一樣的
if (indexOf(listeners, listener) === -1) {
listeners.push(
listenerIsWrapped
? listeners
: {
listener,
once: false,
}
);
}
return this;
}
once(eventName, listener) {
// 直接調用 on 方法,once 參數傳入 true,待執行之后進行 once 處理
this.on(eventName, {
listener,
once: true,
});
return this;
}
emit(eventName, args) {
// 直接通過內部對象獲取對應自定義事件的回調函數
let listeners = this._events[eventName];
if (!listeners) return;
// 需要考慮多個 listener 的情況
listeners.forEach((listener) => {
if (listener) {
listener.listener.apply(this, args || []);
if (listener.once) {
this.off(eventName, listener.listener);
}
}
});
return this;
}
off(eventName, listener) {
let listeners = this._events[eventName];
if (!listener) return;
listeners.some((item, index) => {
if (item && item.listener === listener) {
listeners.splice(index, 1);
return true;
}
return false;
});
}
allOff(eventName) {
if (eventName && this._events[eventName]) {
this._events[eventName] = [];
} else {
this._events = {};
}
}
}
// 判斷是否是合法的 listener
function isValidListener(listener) {
if (typeof listener === "function") {
return true;
} else if (listener && typeof listener === "object") {
return isValidListener(listener.listener);
} else {
return false;
}
}
// 判斷新增自定義事件是否存在
function indexOf(array, item) {
var result = -1;
item = typeof item === "object" ? item.listener : item;
for (var i = 0, len = array.length; i < len; i++) {
if (array[i].listener === item) {
result = i;
break;
}
}
return result;
}
復制代碼
服務端開發生成userSig接口或者下載騰訊云提供生成userSig的文件
相關文檔: cloud.tencent.com/document/pr…
騰訊云提供的客戶端生成userSig文件: github.com/tencentyun/…
初始化tim并設置監聽
import TIM from 'tim-wx-sdk';
import TIMUploadPlugin from 'tim-upload-plugin';
let options = {
SDKAppID: 0 // 接入時需要將0替換為您的即時通信 IM 應用的 SDKAppID
};
// 創建 SDK 實例,`TIM.create()`方法對于同一個 `SDKAppID` 只會返回同一份實例
let tim = TIM.create(options); // SDK 實例通常用 tim 表示
// 設置 SDK 日志輸出級別,詳細分級請參見 <a >setLogLevel 接口的說明</a>
tim.setLogLevel(0); // 普通級別,日志量較多,接入時建議使用
// tim.setLogLevel(1); // release 級別,SDK 輸出關鍵信息,生產環境時建議使用
// 注冊騰訊云即時通信 IM 上傳插件
tim.registerPlugin({'tim-upload-plugin': TIMUploadPlugin});
// 監聽事件,例如:
tim.on(TIM.EVENT.SDK_READY, function(event) {
// 收到離線消息和會話列表同步完畢通知,接入側可以調用 sendMessage 等需要鑒權的接口
// event.name - TIM.EVENT.SDK_READY
});
tim.on(TIM.EVENT.MESSAGE_RECEIVED, function(event) {
// 收到推送的單聊、群聊、群提示、群系統通知的新消息,可通過遍歷 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.MESSAGE_READ_BY_PEER, function(event) {
// SDK 收到對端已讀消息的通知,即已讀回執。使用前需要將 SDK 版本升級至 v2.7.0 或以上。僅支持單聊會話。
// event.name - TIM.EVENT.MESSAGE_READ_BY_PEER
// event.data - event.data - 存儲 Message 對象的數組 - [Message] - 每個 Message 對象的 isPeerRead 屬性值為 true
});
tim.on(TIM.EVENT.CONVERSATION_LIST_UPDATED, function(event) {
// 收到會話列表更新通知,可通過遍歷 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.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) {
// 收到 SDK 進入 not ready 狀態通知,此時 SDK 無法正常工作
// event.name - TIM.EVENT.SDK_NOT_READY
});
tim.on(TIM.EVENT.KICKED_OUT, function(event) {
// 收到被踢下線通知
// 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 簽名過期被踢 (v2.4.0起支持)。
});
tim.on(TIM.EVENT.NET_STATE_CHANGE, function(event) {
// 網絡狀態發生改變(v2.5.0 起支持)。
// event.name - TIM.EVENT.NET_STATE_CHANGE
// event.data.state 當前網絡狀態,枚舉值及說明如下:
// \- TIM.TYPES.NET_STATE_CONNECTED - 已接入網絡
// \- TIM.TYPES.NET_STATE_CONNECTING - 連接中。很可能遇到網絡抖動,SDK 在重試。接入側可根據此狀態提示“當前網絡不穩定”或“連接中”
// \- TIM.TYPES.NET_STATE_DISCONNECTED - 未接入網絡。接入側可根據此狀態提示“當前網絡不可用”。SDK 仍會繼續重試,若用戶網絡恢復,SDK 會自動同步消息
});
// 開始登錄
tim.login({userID: 'your userID', userSig: 'your userSig'});
復制代碼
聊天開發記錄點
(1) 表情開發
進入 www.oicqzone.com/tool/emoji/ 網站,把最后一列的表情下載下來 當然也可以通過下面這種方式把表情一下子copy下來,然后存到一個js里面 命令: Array.from(document.querySelectorAll('body > table > tbody > tr > td:nth-child(8)')).map(item=>item && item.innerText||'')
然后導入項目中就可以使用,點擊表情的時候將表情直接放到輸入框文本后面
selectEmoji(e){
const {text} = e.currentTarget.dataset
this.setData({
colSendMsg:this.data.colSendMsg + text,
})
}
復制代碼
(2)發送語音功能
發送語音主要是調用 wx.createInnerAudioContext()方法來實現的 主要細節在于錄音授權、長按錄音,錄音時間短于一定時間不予以發送并提示,可以通過上滑的方式取消發送。
錄音授權
// 點擊錄音按鈕
record() {
let that = this
// canRecord變量是用于保存是否授權錄音功能的狀態
if (that.canRecord) {
// 開始錄音
that.beforeStartRecord()
return
}
wx.authorize({
scope: 'scope.record',
success() {
console.log("錄音授權成功");
that.canRecord = true
// 用戶已經同意小程序使用錄音功能
},
fail() {
console.log("第一次錄音授權失敗");
wx.showModal({
title: '提示',
content: '您未授權錄音,功能將無法使用',
showCancel: true,
confirmText: "授權",
confirmColor: "#52a2d8",
success: function (res) {
if (res.confirm) {
//確認則打開設置頁面(重點)
wx.openSetting({
success: (res) => {
console.log(res.authSetting);
if (!res.authSetting['scope.record']) {
//未設置錄音授權
wx.showModal({
title: '提示',
content: '您未授權錄音,功能將無法使用',
showCancel: false,
success: function (res) {},
})
} else {
//第二次才成功授權
that.canRecord = true
console.log("設置錄音授權成功");
}
},
fail: function () {
console.log("授權設置錄音失敗");
}
})
} else if (res.cancel) {
console.log("cancel");
}
},
fail: function () {
console.log("openfail");
}
})
}
})
},
復制代碼
后面錄音相關的流程很多博文上都有,就不再贅述,只提供大家一個思考:
(3)頁面滾動到底部
聊天頁面很重要一點是要讓用戶看到最新的信息,所以要顯示頁面的最底部內容,我根據以下幾種情況顯示最底部內容:
- 第一次進入頁面
- 發送任意消息
- 監聽到最新的消息(這里其實推薦值設置提醒)
具體部分代碼如下:
// wxml
<scroll-view class="chat-area" refresher-enabled="{{true}}" refresher-triggered="{{triggered}}"
bindrefresherrefresh="onRefresh" scroll-into-view="{{ viewIndex }}" scroll-y="true" enable-flex="{{true}}"
bindtap="onHideSendMore" bindtap="hideBottom" style="padding-bottom:{{InputBottom}}px">
<!-- <view wx:if="{{showLoading}}" class="cu-load loading"></view> -->
<view class="cu-chat">
<view wx:for="{{ arrMsg }}" wx:key="ID">
// 具體內容
</view>
</view>
</scroll-view>
// js - pageScrollToBottom
// isBottom 是否滑動到底部
// lastIndex 滑動到指定位置
// arrMsg 消息列表
pageScrollToBottom(isBottom,lastIndex = 0) {
let index = null
if (isBottom) {
index = 'msg-' + (this.data.arrMsg.length -1);
} else if(lastIndex) {
index = 'msg-' + lastIndex;
}else{
index = 'msg-' + 0;
}
this.setData({
viewIndex: index
})
},
復制代碼
(4)底部彈起高度
我們在開發的時候常常需要底部彈起,還有鍵盤彈起的操作,這里也需要優化一下用戶的體驗。
- 彈起的時候讓聊天記錄也向上推起
- 保持其他部門彈起的高度和鍵盤彈起高度一樣(這樣就不會有過多的切換跳動效果)
我使用 style="padding-bottom:{{InputBottom}}px"
的方式,inputBottom默認我設置了200,然后在鍵盤彈起的時候記錄鍵盤彈起的高度,然后保存鍵盤彈起的高度,下次使用這個保存的高度
// wxml
<input catchblur="InputBlur"></input>
// js
InputFocus(e) {
let height = e.detail.height
if(height > 0) this.InputBottom = height
this.setData({
InputBottom: height
})
}
復制代碼
尾聲
本文只是對我開發這個項目的時候一些重要的地方的一個記錄,供大家參考和以后個人的回顧,如果有不足的地方,還請大家多多指點。