項(xiàng)目需求: 實(shí)時(shí)聊天驰后,發(fā)送文字肆资,圖片,以及文件 (該項(xiàng)目使用vant + vue 搭建)
首先灶芝,聊天也是我第一次寫郑原,之前只是維護(hù)唉韭,第一次采取了自己搭建的,源于后臺(tái)協(xié)商socket-io 服務(wù)器比較難搭建犯犁,本著時(shí)間緊任務(wù)重的原因纽哥,最后果斷采取了第三方聊天,在網(wǎng)上百度了好多資料栖秕,最后還是決定使用環(huán)信春塌。官方文檔屬實(shí)是不想看,后來就瘋狂百度簇捍,發(fā)現(xiàn)總是差一下只壳,最后還是自己摸索吧。
[環(huán)信官方文檔]: web集成官方文檔
第一步: 需要注冊(cè)環(huán)信即時(shí)通信云獲得appkey暑塑,注冊(cè)賬號(hào)之后登錄環(huán)信后臺(tái)創(chuàng)建應(yīng)用就可以得到appkey
第二步: 在vue項(xiàng)目中安裝插件
npm install easemob-websdk --save
在main.js里引入 import websdk from 'easemob-websdk'
第三步: 在util里新建一個(gè)文件夾WebIM吼句,文件夾下新建WebimConfig.js
const config = {
xmppURL: 'im-api.easemob.com', // xmpp Server地址,對(duì)于在console.easemob.com創(chuàng)建的appKey事格,固定為該值
// apiURL: 'http://a1.easemob.com', // rest Server地址惕艳,對(duì)于在console.easemob.com創(chuàng)建的appkey,固定為該值
apiURL: (location.protocol === 'https:' ? 'https:' : 'http:') + '//a1.easemob.com',
appkey: '678659375498374#demo', // App key
https : false, // 是否使用https
isHttpDNS: true, //防止DNS劫持從服務(wù)端獲取XMPPUrl驹愚、restUrl
isMultiLoginSessions: false, // 是否開啟多頁(yè)面同步收消息远搪,注意,需要先聯(lián)系商務(wù)開通此功能
isAutoLogin: true,
isWindowSDK: false,
isSandBox: false, // 自動(dòng)出席逢捺,(如設(shè)置為false谁鳍,則表示離線,無法收消息劫瞳,需要在登錄成功后手動(dòng)調(diào) 用conn.setPresence()才可以收消息)
isDebug: false, // 打開調(diào)試倘潜,會(huì)自動(dòng)打印log,在控制臺(tái)的console中查看log
autoReconnectNumMax: 2, // 斷線重連最大次數(shù)
autoReconnectInterval: 2, // 斷線重連時(shí)間間隔
isWebRTC: (/Firefox/.test(navigator.userAgent) || /WebKit/.test(navigator.userAgent)) && /^https\:$/.test(window.location.protocol),
heartBeatWait: 4500, // 使用webrtc(視頻聊天)時(shí)發(fā)送心跳包的時(shí)間間隔志于,單位ms
msgStatus: true,
delivery: true, // 是否發(fā)送已讀回執(zhí)
read: true,
saveLocal: false,
encrypt: {
type: 'none'
},
useOwnUploadFun: true //發(fā)送url圖片消息
}
export default config;
第四步: 將WebimConfig.js這個(gè)文件在main.js里引入
import webimconfig from './util/WebIM/WebimConfig'
//vue集成通信
import websdk from 'easemob-websdk'
import webimconfig from './util/WebIM/WebimConfig'
// 環(huán)信
let WebIM = window.WebIM = websdk;
WebIM.config = webimconfig;
// 環(huán)信實(shí)例
var conn = WebIM.conn = new WebIM.connection({
appKey: WebIM.config.appkey,
isHttpDNS: WebIM.config.isHttpDNS,
isMultiLoginSessions: WebIM.config.isMultiLoginSessions,
https: WebIM.config.https,
url: WebIM.config.xmppURL,
apiUrl: WebIM.config.apiURL,
isAutoLogin: true,
heartBeatWait: WebIM.config.heartBeatWait,
autoReconnectNumMax: WebIM.config.autoReconnectNumMax,
autoReconnectInterval: WebIM.config.autoReconnectInterval,
isStropheLog: WebIM.config.isStropheLog,
delivery: WebIM.config.delivery
})
var optionsIm = {
apiUrl: WebIM.config.apiURL,
user: '', //換成自己申請(qǐng)的賬號(hào)就行涮因,密碼也需要換
pwd: '',
appKey: WebIM.config.appkey,
success: function (res) {
console.log('鏈接服務(wù)器正常')
},
error: function (err) {
console.log(err)
}
}
Vue.prototype.$WebIM = WebIM;
Vue.prototype.$imconn = conn
Vue.prototype.$imoption = optionsIm;
第五步: 我這個(gè)項(xiàng)目沒有注冊(cè),我直接在登錄里拿到賬號(hào)密碼伺绽,存儲(chǔ)起來养泡,傳到后臺(tái),并入環(huán)信憔恳。
//測(cè)試賬號(hào)
this.$imoption.user = this.login_userName
this.$imoption.pwd = this.login_password
//存儲(chǔ)賬號(hào)密碼
this.saveGetUserInfo({name: this.login_userName, word: this.login_password })
this.$imconn.open(this.$imoption);
// 監(jiān)聽回調(diào)
this.$imconn.listen({
onOpened: function() {
console.log("用戶已上線");
},
onClosed: function() {
console.log("用戶下線");
},
});
第六步: 在聊天頁(yè)發(fā)送(接收)文本消息瓤荔,圖片净蚤,以及文件
<template>
<div id="ChatInterface">
<van-nav-bar :title='title'
:fixed=true
:border=false
@click-left="onClickLeft"
left-arrow
style="height:0.88rem" />
<div class="box">
<div class="content">
<van-divider v-if="charList.length == 0" :style="{ color: '#000', borderColor: '#ccc', padding: '0 16px',fontSize: '.28rem',fontWeight: '300' }">暫無聊天記錄</van-divider>
<div v-else >
<ul v-for="(item,index) in charList" :key="index">
<li :class="userInfo.member_id != item.member_id ? 'msg-other': 'msg-me'" >
<p v-if="item.if_show_time">{{item.insert_time_date}}</p>
<div class="message">
<div class="message_i">
<img class="message_image" :src="userInfo.member_id != item.member_id ? item.avatar: userInfo.member_avatar" alt="">
</div>
<!-- 文本 -->
<div class="content" v-if="item.content">{{item.content}}</div>
<!-- 圖片 -->
<img class="image_con" v-if="item.image" :src="item.image" alt="">
<!-- 文件 -->
<div class="document" v-if="item.document" @click="godownFile(item.document)">
<div class="doc_one">
<p class="file_title">{{item.fileName}}</p>
<p class="file_size"></p>
</div>
<img class="file_image" :src="downFile" alt="">
</div>
</div>
</li>
</ul>
</div>
</div>
<div class="Bottom">
<div class="replyInput">
<img :src="images.comment" />
<van-field
v-model="currentValue"
rows="1"
autosize
ref="testArea"
type="textarea"
placeholder="發(fā)消息..."
/>
</div>
<div class="sendclick" v-if="currentValue?false:true">
<!-- 發(fā)送文件 -->
<van-uploader class="save" :after-read="afterRead" accept=".txt, .pdf, .doc, .docx, .xls, .xlsx">
<img :src="images.file" />
</van-uploader>
<!-- 發(fā)送圖片 -->
<van-uploader class="save" :after-read="afterPicture" accept="image/*">
<img :src="images.picture" />
</van-uploader>
</div>
<div @click="submit" v-if="currentValue?true:false" class="send">發(fā)送</div>
</div>
</div>
<!--路由的出口-->
<transition name="router-slider"
mode="out-in">
<router-view></router-view>
</transition>
</div>
</template>
<script type="text/javascript">
import { Toast } from 'vant';
import { memberMessageList,memberLeaveMessage,memberChatFiles } from '../../../api/notificationChat'
import ChatMessage from './ChatMessage.vue'
import { mapState } from 'vuex'
import axios from "axios";
export default {
name: "chatInterface",
data(){
return{
currentValue:'',//輸入內(nèi)容
title: this.$route.query.member_name ? this.$route.query.member_name : '聊天名稱',
messageBy_id: this.$route.query.by_id ? this.$route.query.by_id : '', //聊天發(fā)送消息傳入聊天列表頁(yè)的by_id
charList:[],
fileImg:'', //文件
$imconn: {},
$imoption: {},
}
},
created(){
//聲明調(diào)用
this.$imconn = this.$imconn;
this.$imoption = this.$imoption;
this.loginWebIM()
//調(diào)用聊天記錄接口
that.messageList(this.detailId,this.chatId)
},
watch:{
//監(jiān)聽接收頁(yè)面?zhèn)鬟^來的動(dòng)態(tài)值
'$route.query': function(){
//判斷聊天窗口的title是否為空
this.title = this.$route.query.member_name;
this.messageBy_id = this.$route.query.by_id
if (this.source == 1) {
//調(diào)用聊天記錄接口
this.messageList('',this.chatId)
}else if(this.source == 2){
this.messageList(this.detailId,'') //獲取聊天記錄
}
},
},
//進(jìn)入頁(yè)面滑動(dòng)到底部
updated(){
this.scrollToBottom();
},
methods: {
//登錄環(huán)信
loginWebIM(){
//拿到存儲(chǔ)后的賬號(hào)密碼賦值
this.$imoption.user = this.name
this.$imoption.pwd = this.word
this.$imconn.open(this.$imoption);
// open方法钥组,傳入掛載在vue實(shí)例上的配置對(duì)象
let _this = this;
this.$imconn.listen({
onOpened: function (message) {
console.log('用戶已接收') // 控制臺(tái)打印出這句證明連接成功啦
},
onClosed: function (message) {
console.log('用戶未接收')
},
// 集成收到文本信息方法
onTextMessage: function ( message ) {
// console.log(message)
_this.charList.push({
avatar: message.ext.avatar,
content: message.data,
imgUrl: message.ext.url,
member_id: message.ext.member_id,
insert_time_date: message.ext.insert_time_date,
insert_time: message.ext.insert_time,
member_name: message.ext.member_name
});
},
//收到圖片消息
onPictureMessage: function ( message ) {
// console.log(message)
_this.charList.push({
avatar: message.ext.avatar,
member_id: message.ext.member_id,
insert_time_date: message.ext.insert_time_date,
insert_time: message.ext.insert_time,
member_name: message.ext.member_name,
image: message.ext.url
});
},
//收到文件消息
onFileMessage: function ( message ) {
// console.log(message)
_this.charList.push({
avatar: message.ext.avatar,
member_id: message.ext.member_id,
insert_time_date: message.ext.insert_time_date,
insert_time: message.ext.insert_time,
member_name: message.ext.member_name,
document: message.ext.fileUrl,
fileName:message.ext.fileName,
fileLength: message.ext.fileLength
});
},
})
},
// 返回
onClickLeft () {
this.$router.back();
},
//文件按鈕
afterRead(file) {
// 此時(shí)可以自行將文件上傳至服務(wù)器
let formData = new FormData()
formData.append('file',file.file)
formData.append('id',this.detailId)
formData.append('chat_id',this.chatId)
formData.append('image','')
// 添加請(qǐng)求頭
memberChatFiles (formData).then(res => {
if (res.data.status == 1) {
let filedata = res.data.data
var id = this.$imconn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('file', id); // 創(chuàng)建文件消息
var time = +new Date()
let _this = this; // 創(chuàng)建文本消息
msg.set({
file: file,
chatType: 'singleChat', //單聊
ext: { //ext擴(kuò)展 將參數(shù)可放置這里
time: time,
fileUrl: filedata.document,
fileName: filedata.fileName,
fileLength: file.file.size,
avatar: filedata.avatar,
member_id: filedata.member_id,
member_name: filedata.member_name,
insert_time_date: filedata.insert_time_date,
insert_time: filedata.insert_time
},
to: this.title, // 接收消息對(duì)象 (指的是跟你會(huì)話的人存入環(huán)信的賬號(hào))
onFileUploadError: function () { // 消息上傳失敗
console.log('onFileUploadError');
},
onFileUploadProgress: function (e) { // 上傳進(jìn)度的回調(diào)
console.log(e)
},
onFileUploadComplete: function () { // 消息上傳成功
console.log('文件上傳成功');
},
success: function () { // 消息發(fā)送成功
console.log('文件已發(fā)送')
_this.charList.push({ //將數(shù)據(jù)push數(shù)組內(nèi),展示在頁(yè)面(指自己發(fā)送的這方)
avatar: filedata.avatar,
member_id: filedata.member_id,
insert_time_date: filedata.insert_time_date,
insert_time: filedata.insert_time,
member_name: filedata.member_name,
document: filedata.document,
fileName: filedata.fileName
});
_this.keepbottom();
},
fail: function(e){
console.log("Fail"); //如禁言今瀑、拉黑后發(fā)送消息會(huì)失敗
},
flashUpload: WebIM.flashUpload,
});
msg.body.chatType = "singleChat";
this.$imconn.send(msg.body);
}else{
Toast(res.message)
}
})
},
//圖片
afterPicture(file){
let formData = new FormData()
formData.append('file','')
formData.append('id',this.detailId)
formData.append('chat_id',this.chatId)
formData.append('image',file.file)
// 添加請(qǐng)求頭
memberChatFiles (formData).then(res => {
if (res.data.status == 1) {
this.fileImg = res.data.data.image
let imagedata = res.data.data
var id = this.$imconn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('img', id); // 創(chuàng)建圖片消息
var time = +new Date()
let _this = this; // 創(chuàng)建文本消息
msg.set({
body:{
type: 'image',
url: this.fileImg,
filename: file.file.name,
filetype: file.file.type
},
ext: {
time: time,
url: this.fileImg,
avatar: imagedata.avatar,
member_id: imagedata.member_id,
insert_time_date: imagedata.insert_time_date,
insert_time: imagedata.insert_time,
member_name: imagedata.member_name
},
to: this.title, // 接收消息對(duì)象
success: function(){
console.log('圖片已發(fā)送')
_this.charList.push({
avatar: imagedata.avatar,
image: imagedata.image,
member_id: imagedata.member_id,
insert_time_date: imagedata.insert_time_date,
insert_time: imagedata.insert_time,
member_name: imagedata.member_name
});
_this.keepbottom();
},
flashUpload: WebIM.flashUpload
});
msg.body.chatType = "singleChat";
this.$imconn.send(msg.body);
}else{
Toast(res.data.message)
}
})
},
//獲取聊天記錄
messageList(id,chat_id){
//獲取聊天記錄
memberMessageList(id,chat_id).then(res => {
// console.log(res)
if(res.status == 1){
this.charList = res.data;
}
if(res.status == '-1'){
Toast(res.message)
this.$router.go(-1)
}
})
},
getMessage (item, index) {
this.getTime(item, index)
return this.charList[index]
},
//處理時(shí)間5分鐘內(nèi)不展示
getTime (item, index) {
let time = item.insert_time
//定義一個(gè)if_show_time默認(rèn)false 不顯示
this.charList[index]['if_show_time'] = false
if (index) {
if (Math.abs(this.charList[index - 1]['show_time'] - time) > 30) {
this.charList[index]['show_time'] = time
this.charList[index]['if_show_time'] = true
} else {
this.charList[index]['show_time'] = this.charList[index - 1]['show_time']
}
} else {
this.charList[index]['show_time'] = time
this.charList[index]['if_show_time'] = true
}
},
//進(jìn)入頁(yè)面滑動(dòng)到底部
scrollToBottom() {
this.$nextTick(() => {
var container = this.$el.querySelector('.content');
//content
container.scrollTop = container.scrollHeight
})
},
//發(fā)送
submit(){
if (this.currentValue=='') {
Toast('請(qǐng)?zhí)顚憙?nèi)容')
return
}
memberLeaveMessage(this.detailId,this.messageBy_id,this.currentValue).then(res => {
// console.log(res)
if (res.status == 1) {
let receivemsg = res.data
var id = this.$imconn.getUniqueId(); // 生成本地消息id
var msg = new WebIM.message('txt', id);
var time = +new Date()
let _this = this; // 創(chuàng)建文本消息
//生成本地消息id
msg.set({
msg: this.currentValue, // 消息內(nèi)
from: this.name,
to: this.title, // 接收消息對(duì)象(用戶id)
roomType: true,
// ext表示拓展對(duì)象程梦,這個(gè)會(huì)隨著你發(fā)送而發(fā)送過去点把,你接收時(shí)同樣可以拿到這個(gè)數(shù)據(jù)
ext: {
time: time,
avatar: receivemsg.avatar,
member_id: receivemsg.member_id,
insert_time_date: receivemsg.insert_time_date,
insert_time: receivemsg.insert_time,
member_name: receivemsg.member_name
},
success: function(id, serverMsgId) {
// 追加本地緩存處理
// console.log(msg);
//發(fā)送成功數(shù)據(jù)
console.log("消息發(fā)送成功");
//把發(fā)送者的頭像和文本數(shù)據(jù)push到數(shù)組中在頁(yè)面上展示
_this.charList.push({
avatar: receivemsg.avatar,
content: msg.value,
member_id: receivemsg.member_id,
insert_time_date: receivemsg.insert_time_date,
insert_time: receivemsg.insert_time,
member_name: receivemsg.member_name
});
_this.keepbottom();
},
fail: function(e) {
// 失敗原因:
// e.type === '603' 被禁言
// e.type === '605' 群組不存在
// e.type === '602' 不在群組或聊天室中
// e.type === '504' 撤回消息時(shí)超出撤回時(shí)間
// e.type === '505' 未開通消息撤回
// e.type === '506' 沒有在群組或聊天室白名單
// e.type === '501' 消息包含敏感詞
// e.type === '502' 被設(shè)置的自定義攔截捕獲
// e.type === '503' 未知錯(cuò)誤
console.log("消息發(fā)送失敗");
}
});
msg.body.chatType = "singleChat";
this.$imconn.send(msg.body);
this.currentValue = ''
}
})
},
godownFile(fileUrl){
window.open(fileUrl);
}
},
}
</script>