環信 Web IM SDK 爲PC/移動 Web 應用,提供完善的即時通訊功能開發能力,屏蔽其內部複雜細節,對外提供較爲簡潔的 API 接口,方便第三方應用快速集成即時通訊功能。javascript
環信的SDK集成和配置基本按照官網給的文檔就能夠實現。本文僅完成了根據用戶名互相發收文字信息的功能,更多功能可在此基礎上參考官方文檔、API。css
首先須要購買環信服務,申請appkey,使用帳號和密碼申請環信帳號。html
// 如下部分是在前端部分的集成和配置前端
step1:在官網下載WEIM SDK+體驗demo(下載本身所須要的版本便可)java
step2:建立本身的項目,將下載的文件解壓,並將sdk目錄下的三個文件複製到本身的項目中:node
step3:建立html文件,用於展現聊天界面,在該html中使用使用引入外部js文件的方式,將step2中的三個文件引入進來。jquery
step4:建立webIMConfig.js文件,用於對webim的配置:web
// 配置 WebIM.config = { xmppURL: 'http://im-api-v2.easemob.com/ws', // xmpp Server地址 apiURL: 'http://a1.easemob.com', // rest Server地址 appkey: '', // App key https: false, // 是否使用https isHttpDNS: true, // 3.0 SDK支持,防止DNS劫持從服務端獲取XMPPUrl、restUrl isMultiLoginSessions: false, // 是否開啓多頁面同步收消息,注意,須要先聯繫商務開通此功能 isAutoLogin: true, // 自動出席,(如設置爲false,則表示離線,沒法收消息,須要在登陸成功後手動調用conn.setPresence()才能夠收消息) isDebug: false, // 打開調試,會自動打印log,在控制檯的console中查看log autoReconnectNumMax: 2, // 斷線重連最大次數 autoReconnectInterval: 2, // 斷線重連時間間隔 heartBeatWait: 4500, // 使用webrtc(視頻聊天)時發送心跳包的時間間隔,單位ms delivery: true, // 是否發送已讀回執 Host: "", } // 建立鏈接 var conn = {}; conn = WebIM.conn = new WebIM.default.connection({ appKey: WebIM.config.appkey, isHttpDNS: WebIM.config.isHttpDNS, isMultiLoginSessions: WebIM.config.isMultiLoginSessions, host: WebIM.config.Host, https: WebIM.config.https, url: WebIM.config.xmppURL, apiUrl: WebIM.config.apiURL, isAutoLogin: WebIM.config.isAutoLogin, heartBeatWait: WebIM.config.heartBeatWait, autoReconnectNumMax: WebIM.config.autoReconnectNumMax, autoReconnectInterval: WebIM.config.autoReconnectInterval, isStropheLog: WebIM.config.isStropheLog, delivery: WebIM.config.delivery })
以上內容都是直接從官網文檔複製的,注意修改的地方有三個(代碼中用紅色標出的部分):api
1.appkey:要替換成本身申請的appkey;服務器
2.Host:配置成本身的服務器的主機號+端口號;
3.new webIM.default.connection,官網文檔中給出的是new webIM.connection,可是報connection未定義的錯誤,我將webIM打印出來發現這個connection在default下邊,因此改爲了這樣。直接複製代碼的話可能會有這相似的問題,能夠注意一下。
一樣在step2中引入的webIMSDK3.0.6.js也有一樣的錯誤,我也在webIM後邊加上了.default。
step5:添加回調監聽,官網文檔裏複製的:
conn.listen({ onOpened: function(message) { //鏈接成功回調 // 若是isAutoLogin設置爲false,那麼必須手動設置上線,不然沒法收消息 // 手動上線指的是調用conn.setPresence(); 若是conn初始化時已將isAutoLogin設置爲true // 則無需調用conn.setPresence(); console.log("鏈接成功") }, onClosed: function(message) {}, //鏈接關閉回調 onTextMessage: function(message) { console.log(message); receiverMessage(message) }, //收到文本消息 onEmojiMessage: function(message) {}, //收到表情消息 onPictureMessage: function(message) {}, //收到圖片消息 onCmdMessage: function(message) {}, //收到命令消息 onAudioMessage: function(message) {}, //收到音頻消息 onLocationMessage: function(message) {}, //收到位置消息 onFileMessage: function(message) {}, //收到文件消息 onVideoMessage: function(message) { var node = document.getElementById('privateVideo'); var option = { url: message.url, headers: { 'Accept': 'audio/mp4' }, onFileDownloadComplete: function(response) { var objectURL = WebIM.utils.parseDownloadResponse.call(conn, response); node.src = objectURL; }, onFileDownloadError: function() { console.log('File down load error.') } }; WebIM.utils.download.call(conn, option); }, //收到視頻消息 onPresence: function(message) { handlePresence(message); }, //處理「廣播」或「發佈-訂閱」消息,如聯繫人訂閱請求、處理羣組、聊天室被踢解散等消息 onRoster: function(message) {}, //處理好友申請 onInviteMessage: function(message) {}, //處理羣組邀請 onOnline: function() {}, //本機網絡鏈接成功 onOffline: function() { console.log("offline") }, //本機網絡掉線 onError: function(message) { console.log('onError: ', message); }, //失敗回調 onBlacklistUpdate: function(list) { //黑名單變更 // 查詢黑名單,將好友拉黑,將好友從黑名單移除都會回調這個函數,list則是黑名單現有的全部好友信息 console.log(list); }, onRecallMessage: function(message) {}, //收到撤回消息回調 onReceivedMessage: function(message) {}, //收到消息送達服務器回執 onDeliveredMessage: function(message) {}, //收到消息送達客戶端回執 onReadMessage: function(message) {}, //收到消息已讀回執 onCreateGroup: function(message) {}, //建立羣組成功回執(需調用createGroupNew) onMutedMessage: function(message) {} //若是用戶在A羣組被禁言,在A羣發消息會走這個回調而且消息不會傳遞給羣其它成員 });
以上已經將環信配置完畢了,接下來使用環信的API製做客服功能。
這裏咱們申請了兩個環信帳號,一個做爲客服,一個做爲客戶。
先來看一下效果:
客服端:
客戶端:
前端樣式很是簡單,我直接將代碼放在後邊。注意在進行對話以前要先登陸。因爲這個項目會接入到商城中,所以不用顯式登錄,會寫一個接口傳入用戶名和密碼,並自動完成登錄功能。
如下講述幾個製做中的難點:
難點1:接收和發送的信息展現在頁面裏。
爲了讓消息交替錯落的展現,每一條消息都是一個<div>塊級元素,這樣就能自動完成換行。當接收或者發送消息的時候,在頁面中插入一個相應的<div>。
難點2:當消息過多的時候,可以自動向上滾動,以顯示最新消息。
使用的scrollTop動畫來完成。給scrollTop設置一個很大的值,這樣頁面就能老是能滑動顯示出最新的消息。
難點3:客服端的側邊顯示來自不一樣用戶的消息,而且顯示消息未讀小紅點。
當收到信息時,咱們收到的消息中包含一個from字段,表示消息來自哪一個用戶,根據這個字段將消息插入到對應的對話界面中。噹噹前顯示的對話界面不是該用戶時,就要出現小紅點提示有未讀信息。
添加一個全局變量talkTo,其中保存當前對話方的用戶名,表示當前在與誰對話。經過點擊消息列表中的用戶來改變這個變量的值。咱們用這個值來完成消息的發送等功能。同時也能夠用這個值來判斷出當前展現的對話界面,從而判斷是否顯示消息未讀的小紅點。
難點4:顯示與不一樣用戶的交流界面
使用<div>包裹用戶名+用戶對話界面,並給這個div添加帶有用戶信息的獨特id。默認這個<div>是display:none。當經過側邊列表項選擇對話方時,將對應的<div>設置爲display:block
在官方文檔中提供了消息的發送和接收的示例函數,能夠直接複製過來再稍加改動,添加一點顯示方面的代碼就能夠了。
客戶端和客服端代碼基本一致,只是客戶端沒有旁邊的列表,也不用顯示不一樣的對話界面,因此html和css上稍有差別,js中對消息的處理和顯示也稍簡單一點。
他們都公用環信的配置、初始化和監聽函數,儘可能減小二者在監聽中的差別,不一樣之處在各自的js中處理。
項目代碼以下:
客服端:
HTML
<!DOCTYPE html> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if gt IE 8]><!--> <html> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="index.css"> </head> <body> <!-- 消息列表 --> <ul class="sidebar" id="roster" onclick="showDialogist()"> <div>消息列表</div> </ul> <div class="message"> <!-- 信息顯示 --> <div class="display" id="display"> <!-- 默認對話界面 --> <div> <div class="dialogist">歡迎來到客戶服務平臺</div> <div class="content"> </div> </div> </div> <!-- 信息編輯 --> <div class="edit"> <textarea id="sendContent"></textarea> <button onclick="sendMessage()">發送信息</button> </div> </div> <script type='text/javascript' src='lib/jquery-3.2.1.js'></script> <script type='text/javascript' src='lib/webimSDK3.0.6.js'></script> <script type='text/javascript' src='lib/EMedia_x1v1.js'></script> <!-- <script type='text/javascript' src='lib/EMedia_sdk-dev.js'></script> --> <script type='text/javascript' src='WebIMConfig.js'></script> <script type='text/javascript' src='index.js'></script> </body> </html>
CSS
body { margin: 0; } /* 側邊欄 */ .sidebar { position: fixed; top: 0; left: 0; width: 250px; height: 100%; background: #444; margin: 0; padding: 0; } .sidebar div { width: 100%; text-align: center; color: #FFF; font-size: 18px; font-weight: bold; margin-top: 16px; margin-bottom: 19px; } .sidebar li { padding: 18px; display: flex; align-items: center; position: relative; cursor: pointer; } .sidebar img { width: 45px; height: 45px; margin-right: 16px; } .sidebar .name { margin-right: 5px; color: #c8c9cc; font-size: 18px; width: 130px; overflow: hidden; text-overflow: ellipsis; } .sidebar .redIcon { width: 12px; height: 12px; background-color: red; border-radius: 50%; position: absolute; top: 3px; left: 56px; } /* 側邊欄標籤鼠標移入樣式 */ .sidebar li:hover { background-color: #666; color: #FFF; } /* 側邊欄標籤選中樣式 */ .sidebar-selected, .sidebar .sidebar-selected:hover { background: #FFF; } .sidebar-selected .name { color: #333; } /* 信息部分 */ .message { margin-left: 250px; width: calc(100% - 250px); } /* 信息顯示部分 */ .message .display { width: 100%; } .message .display .dialogist { border-bottom: 1px solid #333; padding: 16px; font-size: 20px; } .message .display .content { width: 100%; height: 385px; padding-bottom: 16px; overflow-y: scroll; } .message .display .head { width: 30px; height: 30px; border-radius: 50%; margin: 5px 16px; } .message .display .time { color: #bbb; font-size: 12px; margin: 5px 8px; display: inline-block; } .message .display .textBox { max-width: 250px; min-height: 20px; border-radius: 5px; padding: 8px 16px; box-shadow: 0 0 2px 1px #ddd; word-break: break-all; } .message .display .left { display: flex; align-items: flex-start; justify-content: flex-start; padding-left: 10px; margin-top: 16px; } .message .display .right { display: flex; align-items: flex-start; justify-content: flex-end; padding-right: 10px; margin-top: 16px; } .message .display .left .time { text-align: left; } .message .display .right .time { text-align: right; } .message .display .left .textBox { background-color: yellowgreen; } .message .display .right .textBox { background-color: #fff; } /* 信息編輯部分 */ .message .edit { border-top: 1px solid #333; height: 230px; width: 100%; } .message .edit textarea { width: calc(100% - 20px); height: calc(100% - 70px); resize: none; padding: 10px; font-size: 16px; border: 0; outline: none; } .message .edit button { float: right; background-color: #333; border: 0; color: #fff; padding: 8px 16px; cursor: pointer; margin-right: 30px; } .message .edit button:hover { background-color: #666; }
JS
var username = "";//客服帳號 var password = "";//客服密碼 var talkTo = "";//當前對話方 var curTalkList = new Set();//當前對話用戶列表 window.onload = function() { this.login(); } // 登錄 function login() { //登錄API var options = { apiUrl: WebIM.config.apiURL, user: username, pwd: password, appKey: WebIM.config.appkey }; conn.open(options); } // 退出 function logOut() { conn.close(); } // 單聊發送文本信息 //官方文檔示例函數,稍加修改,與發送消息顯示函數鏈接到一塊兒 function sendPrivateText(myTime, content) { var id = conn.getUniqueId(); // 生成本地消息id var msg = new WebIM.default.message('txt', id); // 建立文本消息 msg.set({ msg: content, // 消息內容 to: talkTo, // 接收消息對象(用戶id) roomType: false, ext: { "time": myTime }, //擴展消息 success: function(id, serverMsgId) { console.log('send private text Success'); }, // 對成功的相關定義,sdk會將消息id登記到日誌進行備份處理 fail: function(e) { console.log("Send private text error"); } // 對失敗的相關定義,sdk會將消息id登記到日誌進行備份處理 }); msg.body.chatType = 'singleChat'; conn.send(msg.body); }; // 顯示已發送信息 function sendMessage() { var myTime = new Date().toLocaleString(); var content = document.getElementById("sendContent").value; document.getElementById("sendContent").value = ""; $("#" + talkTo + "_i").children(".content").append('<div class="right">' + '<div class="time">' + myTime + '</div>' + '<div class="textBox">' + content + '</div>' + '<img class="head" src="./img/logo.png"></div>') $("#" + talkTo + "_i").children(".content").animate({ scrollTop: 9999 }, 200) sendPrivateText(myTime, content); } // 顯示接收到的信息 // 客服端:在列表中顯示消息 function receiverMessage(message) { var name = message.from, time = message.ext.time, content = message.data; if (curTalkList.has(name)) { $("#" + name + "_i").children(".content").append('<div class="left">' + '<img class="head" src="./img/logo.png">' + '<div class="textBox">' + content + '</div>' + '<div class="time">' + time + '</div></div>') if (talkTo == name) { $("#" + name).children(".redIcon").attr("style", "display:block;"); } } else { // 添加消息列表中的信息 curTalkList.add(name); $("#roster").append('<li id="' + name + '"><div class="redIcon"></div><img src="./img/logo.png"><div class="name">' + name + '</div></li>'); // 添加消息顯示框中的信息 $("#display").append('<div style="display:none;" id="' + name + '_i"><div class="dialogist">' + name + '</div>' + '<div class="content">' + '<div class="left">' + '<img class="head" src="./img/logo.png">' + '<div class="textBox">' + content + '</div>' + '<div class="time">' + time + '</div></div>' + '</div></div>') } $("#" + name + "_i").children(".content").animate({ scrollTop: 9999 }, 200) } // 點擊列表中的用戶,顯示對應的對話界面 function showDialogist() { $("#roster").children("li").attr("class", ""); $("#display").children("div").attr("style", "display:none;"); var cur = event.target.closest("li").id; talkTo = cur; $("#" + cur).attr("class", "sidebar-selected"); $("#" + cur).children(".redIcon").attr("style", "display:none;"); $("#" + cur + "_i").attr("style", "display:block;"); }
客戶端:
HTML
<!DOCTYPE html> <!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--> <!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--> <!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--> <!--[if gt IE 8]><!--> <html> <!--<![endif]--> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="client.css"> </head> <body> <div class="message"> <!-- 信息顯示 --> <div class="display"> <div class="dialogist">商城-客服</div> <div class="content" id="display"> </div> </div> <!-- 信息編輯 --> <div class="edit"> <textarea id="sendContent"></textarea> <button onclick="sendMessage()">發送信息</button> </div> </div> <script type='text/javascript' src='lib/jquery-3.2.1.js'></script> <script type='text/javascript' src='lib/webimSDK3.0.6.js'></script> <script type='text/javascript' src='lib/EMedia_x1v1.js'></script> <!-- <script type='text/javascript' src='lib/EMedia_sdk-dev.js'></script> --> <script type='text/javascript' src='WebIMConfig.js'></script> <script type='text/javascript' src='client.js'></script> </body> </html>
CSS
body { margin: 0; } /* 信息部分 */ .message { width: 100%; } /* 信息顯示部分 */ .message .display { width: 100%; } .message .display .dialogist { height: 27px; border-bottom: 1px solid #333; padding: 16px; font-size: 20px; } .message .display .content { width: 100%; height: 385px; padding-bottom: 16px; overflow-y: scroll; } .message .display .head { width: 30px; height: 30px; border-radius: 50%; margin: 5px 16px; } .message .display .time { color: #bbb; font-size: 12px; margin: 5px 8px; display: inline-block; } .message .display .textBox { max-width: 250px; min-height: 20px; border-radius: 5px; padding: 8px 16px; box-shadow: 0 0 2px 1px #ddd; word-break: break-all; } .message .display .left { display: flex; align-items: flex-start; justify-content: flex-start; padding-left: 10px; margin-top: 16px; } .message .display .right { display: flex; align-items: flex-start; justify-content: flex-end; padding-right: 10px; margin-top: 16px; } .message .display .left .time { text-align: left; } .message .display .right .time { text-align: right; } .message .display .left .textBox { background-color: yellowgreen; } .message .display .right .textBox { background-color: #fff; } /* 信息編輯部分 */ .message .edit { border-top: 1px solid #333; height: 205px; width: 100%; } .message .edit textarea { width: calc(100% - 20px); height: calc(100% - 70px); resize: none; padding: 10px; font-size: 16px; border: 0; outline: none; } .message .edit button { float: right; background-color: #333; border: 0; color: #fff; padding: 8px 16px; cursor: pointer; margin-right: 30px; } .message .edit button:hover { background-color: #666; }
JS
// 登錄 var username = "";//客戶用戶名 var password = "";//客戶密碼 var talkTo = "";//客服用戶名 window.onload = function() { this.login(); } function login() { var options = { apiUrl: WebIM.config.apiURL, user: username, pwd: password, appKey: WebIM.config.appkey }; conn.open(options); } // 退出 function logOut() { conn.close(); } // 單聊發送文本信息 function sendPrivateText(myTime, content) { var id = conn.getUniqueId(); // 生成本地消息id var msg = new WebIM.default.message('txt', id); // 建立文本消息 msg.set({ msg: content, // 消息內容 to: talkTo, // 接收消息對象(用戶id) roomType: false, ext: { "time": myTime }, //擴展消息 success: function(id, serverMsgId) { console.log('send private text Success'); }, // 對成功的相關定義,sdk會將消息id登記到日誌進行備份處理 fail: function(e) { console.log("Send private text error"); } // 對失敗的相關定義,sdk會將消息id登記到日誌進行備份處理 }); msg.body.chatType = 'singleChat'; conn.send(msg.body); }; // 顯示已發送信息 function sendMessage() { var myTime = new Date().toLocaleString(); var content = document.getElementById("sendContent").value; document.getElementById("sendContent").value = ""; $("#display").append('<div class="right">' + '<div class="time">' + myTime + '</div>' + '<div class="textBox">' + content + '</div>' + '<img class="head" src="./img/logo.png"></div>') $("#display").animate({ scrollTop: 9999 }, 200) sendPrivateText(myTime, content); } // 顯示接收到的信息 function receiverMessage(message) { var time = message.ext.time, content = message.data; $("#display").append('<div class="left">' + '<img class="head" src="./img/logo.png">' + '<div class="textBox">' + content + '</div>' + '<div class="time">' + time + '</div></div>') $("#display").animate({ scrollTop: 9999 }, 200) }