在這個系列文章里,我嘗試將自己開發唯一客服系統(gofly.v1kf.com)所涉及的經驗和技術點進行梳理總結,
文章寫作水平有限,有時候會表達不清楚,難免有所疏漏,歡迎批評指正
該系列將分成以下幾個部分
一. 需求分析
二. 初步技術方案選型,驗證
三. 資料庫結構設計
四. WEB訪客前端設計與開發
五. WEB客服端設計與開發
六. 客戶端設計與開發
在這個系列的文章中,您將了解并學習到以下技術知識:
MySQL、VUE、WebSocket、Golang+Gin、UniApp 等
如果這些技術對您有用,還請您 推薦 一下本文章,謝謝!
什么是在線客服系統:
常見的用法是,點擊立即咨詢按鈕,直接跳轉到聊天視窗,或者是只需將系統生成的一段JavaScript代碼嵌入網站頁面,即可在網站上顯示代表客服的浮動小圖示,邀請框,點擊按鈕后在當前頁面彈窗展示,
而客服端可以在WEB客服后臺,查看網站正在溝通的實時在線訪客、瀏覽軌跡等,能直接和網站訪客進行在線即時交流,目的是提升客戶滿意度,及時解決客戶的問題,進一步提升網站的銷售額,
由此分析,在線客服系統大至分為三大塊:1)訪客端,2)客服端,3)客服移動端,但是僅僅分為這三大塊是不夠的,后面我們還將對每一塊進行進一步的分析,
訪客彈窗入口界面

訪客端彈窗界面

前端界面是使用的elementui,是基于vue.js的UI框架,作為后端開發程式員,非常不習慣用node.js編譯開發前端,所以我還是選擇了使用cdn引入的形式去使用這個框架
彈窗效果是使用的layer.js進行的彈窗,點擊圖示,呼叫layer.js去iframe的形式加載了訪客鏈接,這個訪客鏈接就是下面直接打開時的效果
訪客端直接打開的界面

此界面為回應式設計,綜合運用了css3的媒體查詢功能,在大螢屏和小螢屏都能適配展示,所以該訪客界面是可以直接接入微信和APP中,
這個界面可以說的還是比較多的,后面我再去詳細總結
客服端界面

客服端也是使用的elementUI框架,整體結構是iframe框出來的,然后點擊不同的選單加載URL展示出來
總體來說,專案是偏向后端風格的,偏傳統的架構
下面是訪客端界面的代碼,就可以看出這個作業量有多大~~
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<!--洗掉蘋果默認的工具列和選單欄,默認為no顯示工具列和選單欄,-->
<meta name="apple-mobile-web-app-capable" content="yes"/>
<!--QQ強制全屏-->
<meta name="x5-fullscreen" content="true">
<!--UC強制全屏-->
<meta name="fullscreen" content="yes">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" name="viewport" />
<title>{{.Title}}</title>
<link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/p/"/static/cdn/element-ui/2.15.1/theme-chalk/index.min.css">
<script src=https://www.cnblogs.com/taoshihan/p/"/static/cdn/vue/2.6.11/vue.min.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/cdn/element-ui/2.15.1/index.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/cdn/jquery/3.6.0/jquery.min.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/functions.js?v=0.6.9"></script>
<link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/p/"/static/css/common.css?v=yuyfgfgfg" />
<link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/p/"/static/css/icono.min.css" />
<link rel="icon" href=https://www.cnblogs.com/taoshihan/p/"/static/images/favicon.ico">
<style>
.el-message-box{
width: auto;
max-width: 100%;
max-height: 100%;
overflow: auto;
}
</style>
</head>
<body class="visitorBody">
<div id="app" class="chatCenter">
<template>
<!--客服代碼-->
<div class="chatEntTitle" v-show="!isIframe">
<el-badge :type="onlineType" is-dot class="item">
<el-avatar class="chatEntTitleLogo" :size="35" :src=https://www.cnblogs.com/taoshihan/p/"noticeAvatar"></el-avatar>
</el-badge>
<div>
<div><{chatTitle}></div>
<div class="entIntro" v-show="entIntroduce!=''"><{entIntroduce}></div>
</div>
</div>
<div class="chatEntBox">
<!--公告欄-->
<div v-show="visitorNotice!=''"
class="visitorNotice"
>
<img src=https://www.cnblogs.com/taoshihan/p/'/static/images/laba.svg'/>
<span><{visitorNotice}></span>
<img v-on:click="visitorNotice=''" src=https://www.cnblogs.com/taoshihan/p/"/static/images/cha.png" class="visitorNoticeClose"/>
</div>
<!--//公告欄-->
<div ref="chatVisitorPage" id="chatVisitorPage" class="chatContext chatVisitorPage" v-on:click="showIconBtns=false;showFaceIcon=false">
<div class="chatBox">
<div class="chatNotice" v-on:click="loadMoreMessages" v-show="showLoadMore">
<a class="chatNoticeContent"><{flyLang.moremessage}></a>
</div>
<el-row :gutter="2" v-for="v in msgList" v-bind:class="{'chatBoxMe': v.is_kefu==true}">
<div class="messageBox questionBox" v-if="v.type=='question'">
<div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
<div class="left" v-if="v.is_kefu!=true" style="display: flex;">
<el-avatar style="margin-right:10px;flex-shrink: 0;" :size="36" :src=https://www.cnblogs.com/taoshihan/p/"v.avator"></el-avatar>
<div class="chatMsgContent">
<div class="chatUser" v-if="showKefuName!='off'"><{v.name}></div>
<div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
</div>
</div>
</div>
<!--猜你想問-->
<div class="cardBox" v-else-if="v.type=='card'">
<div class='visitorReplyTitle'><{flyLang.guess}><a><i class='el-icon-refresh-left'></i> <{flyLang.huanyihuan}></a></div>
<div class="cardBoxContent" v-html="v.content"></div>
</div>
<!--//猜你想問-->
<!--訊息模板-->
<div class="messageBox" v-else>
<div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
<div class="left" v-if="v.is_kefu!=true" style="display: flex;">
<el-avatar style="margin-right:10px;flex-shrink: 0;" :size="36" :src=https://www.cnblogs.com/taoshihan/p/"v.avator"></el-avatar>
<div class="chatMsgContent">
<div class="chatUser" v-if="showKefuName!='off'"><{v.name}></div>
<div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
</div>
</div>
<div class="kefuMe right" v-if="v.is_kefu==true" style="display: flex;justify-content: flex-end;">
<div>
<div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
<div class="chatReadStatus" v-show="VisitorReadStatus!='true'"><{v.read_status}></div>
</div>
<el-avatar v-if="VisitorShowAvator=='true'" style="margin-left:10px;flex-shrink: 0;" :size="36" :src=https://www.cnblogs.com/taoshihan/p/"v.avator"></el-avatar>
</div>
<div class="clear"></div>
</div>
<!--//訊息模板-->
</el-row>
</div>
</div>
<div class="chatBoxSend">
<div class="chatBoxSendMask" v-if="reconnectDialog">
<a @click="initConn" href=https://www.cnblogs.com/taoshihan/p/"javascript:void(0);"><{flyLang.socketclose}></a>
</div>
<div class="hotQuestion" v-if="hotQuestion.length!=0">
<a
class="slideInRightItem"
v-for="item in hotQuestion"
v-on:click="messageContent=item;chatToUser()">
<{item}>
</a>
</div>
<!--進度條-->
<div class="progressLine">
<el-progress :stroke-width="6" :percentage="percentage" v-show="percentage!=0" :text-inside="true"></el-progress>
</div>
<!--//進度條-->
<div class="iconBtns visitorIconBox">
<el-tooltip :content="flyLang.emotions" placement="top">
<div class="icono-smile visitorIconBtns visitorFaceBtn" v-on:click="showFaceIcon==true?showFaceIcon=false:showFaceIcon=true"></div>
</el-tooltip>
<el-tooltip :content="flyLang.photo" placement="top">
<div v-show="VisitorUploadImgBtn!='true'" :title="flyLang.photo" class="el-icon-picture" id="uploadImg" v-on:click="uploadImg('/uploadimg')" style="font-size: 22px;"></div>
</el-tooltip>
<el-tooltip :content="flyLang.file" placement="top">
<div v-show="VisitorUploadFileBtn!='true'" :title="flyLang.file" class="el-icon-upload" id="uploadFile" v-on:click="uploadFile('/2/uploadFile')" style="font-size: 22px;"></div>
</el-tooltip>
<el-tooltip :content="flyLang.recoder" placement="top">
<div v-show="VisitorVoiceBtn!='true'" :title="flyLang.recoder" class="el-icon-microphone" v-on:click="audioDialog=true" style="font-size: 22px;"></div>
</el-tooltip>
<el-tooltip :content="flyLang.map" placement="top">
<div v-show="VisitorMapBtn!='true'" style="font-size: 22px;" class="el-icon-location" v-on:click="qqMap==true?qqMap=false:qqMap=true;"></div>
</el-tooltip>
<el-tooltip :content="flyLang.audio" placement="top">
<div class="el-icon-phone-outline" @click="callPhone()" style="font-size: 20px;">
</div>
</el-tooltip>
<el-tooltip :content="flyLang.video" placement="top">
<div class="el-icon-video-camera" @click="callPeer()" style="font-size: 22px;">
</div>
</el-tooltip>
<el-tooltip :content="flyLang.language" placement="top">
<div @click="flagsDialog='true'">
<img src=https://www.cnblogs.com/taoshihan/p/"/static/images/lang.png" style="width: 20px;"/>
</div>
</el-tooltip>
</div>
<div class="faceBox visitorFaceBox" v-if="showFaceIcon">
<ul class="faceBoxList">
<li v-on:click="faceIconClick(i)" class="faceIcon" v-for="(v,i) in face" :title="v.name"><img :src=https://www.cnblogs.com/taoshihan/p/v.path>
class="clear"></div>
</div>
<!--搜索建議-->
<div class="searchList" v-show="searchList.length!=0">
<div v-on:click="messageContent=item.title;chatToUser();searchList=[]" class="searchItem" v-for="item in searchList" v-html="item.htmlTitle"></div>
</div>
<!--//搜索建議-->
<div class="visitorEditor">
{{/* <div v-if="VisitorVoiceBtn!='true'" v-on:click="audioDialog==true?audioDialog=false:audioDialog=true" ></div>*/}}
<el-input :placeholder="flyLang.textarea" show-word-limit :maxlength="VisitorMaxLength" :rows="2" type="textarea" resize="none" class="visitorEditorArea" @focus="scrollBottom;showIconBtns=false" @blur="scrollBottom;showIconBtns=false" v-model="messageContent" @keyup.native="inputNextText" v-on:keyup.enter.native="chatToUser">
</el-input>
{{/* <div v-if="VisitorFaceBtn!='true'" :title="flyLang.emotions" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" ></div>*/}}
{{/* <div v-if="VisitorUploadImgBtn!='true'&&VisitorPlusBtn=='true'" id="uploadImg" v-on:click="uploadImg('/uploadimg')"></div>*/}}
{{/* <div v-if="VisitorPlusBtn!='true'" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" v-show="messageContent==''" :title="flyLang.emotions" ></div>*/}}
</div>
<el-button type="primary" size="mini" class="visitorEditorBtn" :disabled="sendDisabled||messageContent==''" v-on:click="chatToUser();showIconBtns=false"><{flyLang.sent}></el-button>
<div class="footContact clear">
<a href=https://www.cnblogs.com/taoshihan/p/"{{.CopyrightUrl}}" target="_blank">{{.CopyrightTxt}}</a>
</div>
</div>
</div>
<div class="chatArticle">
<div style="padding: 8px;"><img style="width: 100%" :src=https://www.cnblogs.com/taoshihan/p/"entInfo.intro_pic" v-if="entInfo.intro_pic" :title="entInfo.username"/></div>
<h3 class="hotQuestionTitle">
<img src=https://www.cnblogs.com/taoshihan/p/"/static/images/fire.svg" class="fire"/><{flyLang.hotQuestionTitle}>
</h3>
<ul>
<li v-on:click="messageContent=item;chatToUser()" class="chatArticleItem" v-for="item in topQuestionList"><a><{item}></a></li>
</ul>
</div>
<div class="clear"></div>
<!--//客服代碼-->
<audio id="chatMessageAudio">
<source id="chatMessageAudioSource" />
</audio>
<audio id="chatMessageSendAudio">
<source id="chatMessageSendAudioSource" />
</audio>
<!--圖片預覽-->
<el-image
style="display: none;"
ref="preview"
class="hideImgDiv"
:src="imgPreviewSrc[0]"
:preview-src-list="imgPreviewSrc"
z-index="9999"
></el-image>
<!--評價-->
<el-dialog
center
:title="flyLang.visitorCommentTitle"
:close-on-click-modal="false"
width="90%"
:visible.sync="comment"
>
<div class="commentBox">
<div style="line-height: 25px;"><{flyLang.commentDesc}></div>
<el-rate v-model="commentScore" style="margin-bottom: 30px;"></el-rate>
<el-input
type="textarea"
:rows="4"
v-model="commentContent">
</el-input>
{{/* <el-tooltip content="good" placement="top">*/}}
{{/* <span v-on:click="sendComment('good');comment = false"></span>*/}}
{{/* </el-tooltip>*/}}
{{/* <el-tooltip content="normal" placement="top">*/}}
{{/* <span v-on:click="sendComment('normal');comment = false"></span>*/}}
{{/* </el-tooltip>*/}}
{{/* <el-tooltip content="bad" placement="top">*/}}
{{/* <span v-on:click="sendComment('bad');comment = false"></span>*/}}
{{/* </el-tooltip>*/}}
</div>
<span slot="footer" class="dialog-footer">
<el-button type="primary" v-on:click="sendComment();comment = false"><{flyLang.sent}></el-button>
</span>
</el-dialog>
<!--//評價-->
<!--地圖-->
<iframe v-if="qqMap" style="position: fixed;top: 0;left: 0;z-index: 999999999" id="mapPage" width="100%" height="100%" frameborder=0
src="https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=
OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&referer=kefu">
</iframe>
<!--//地圖-->
<el-dialog
:title="flyLang.leave"
:visible.sync="allOffline"
width="100%"
top="0">
<el-input style="margin-bottom: 10px;" :placeholder="flyLang.email" v-model="visitorContact.email"></el-input>
<el-input style="margin-bottom: 10px;" :placeholder="flyLang.wechat" v-model="visitorContact.weixin"></el-input>
<el-input style="margin-bottom: 10px;" :placeholder="flyLang.realname" v-model="visitorContact.name"></el-input>
<el-input :placeholder="flyLang.content" type="textarea" v-model="visitorContact.msg"></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="sendEmailMsg"><{flyLang.sent}></el-button>
<el-button @click="allOffline = false"><{flyLang.cancel}></el-button>
</span>
</el-dialog>
<!--錄音-->
<el-dialog
:visible.sync="audioDialog"
width="100%"
>
<div class="dialogRecoder">
<el-progress :color="colors" type="dashboard" :format="recoderFormat" :stroke-width="10" :percentage="recoderSecond"></el-progress>
<br/>
<audio v-show="recorderEnd" controls ref="audio" muted="muted" src=https://www.cnblogs.com/taoshihan/p/"" id="audio"></audio>
<br/>
<el-button id="start" @click="startRecoder($event)" size="small" type="primary"><{flyLang.start}></el-button>
<el-button @click="stopRecoder($event)" size="small" type="warning"><{flyLang.stop}></el-button>
<el-button @click="cancelRecoder()" size="small" type="danger"><{flyLang.cancel}></el-button>
<el-button @click="sendRecoder()" size="small" type="success"><{flyLang.sent}></el-button>
</div>
</el-dialog>
<!--//錄音-->
<!--切換語言-->
<el-dialog
:visible.sync="flagsDialog"
width="90%"
top="30px">
<el-button @click="selectLang('cn')" class="flagBtn" type="primary" plain>中文簡體</el-button>
<el-button @click="selectLang('tw')" class="flagBtn" type="primary" plain>中文繁體</el-button>
<el-button @click="selectLang('en')" class="flagBtn" type="primary" plain>English</el-button>
</el-dialog>
<!--//切換語言-->
<!--錄音-->
<!--視頻-->
<el-dialog
center
:close-on-click-modal="false"
:visible="isCalling"
width="90%"
:show-close="false"
@opened="initCallingDialog"
>
<video id="chatRtc" style="width: 100%;" controls autoplay></video>
<video v-if="isVideo==true" id="chatLocalRtc" style="width: 100%;" controls muted></video>
<span slot="footer" class="dialog-footer">
<el-button @click="callClose()" type="danger" size="mini">掛斷</el-button>
</span>
</el-dialog>
<!--//錄音-->
</template>
</div>
</body>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/xss.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/reconnecting-websocket.min.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/recoder.js"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/audio.js"></script>
<script>
var KEFU_ID='{{.KEFU_ID}}';
var REFER=urlDecode('{{.Refer}}');
var REFER_URL=urlDecode('{{.ReferUrl}}');
var ENT_ID='{{.ENT_ID}}';
var IS_TRY='{{.IS_TRY}}';
var VISITOR_ID='{{.visitorId}}';
var VISITOR_NAME='{{.visitorName}}';
var ERR_MSG='{{.errMsg}}';
var AVATOR='{{.avator}}';
var LANG=checkLang();
var SHOW_KEFU_NAME='{{.ShowKefuName}}';
var EXTRA='{{.Extra}}';
</script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/chat-lang.js?v=dsdsdgf45hkjk"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/chat-config.js?v=0.5.1"></script>
<script src=https://www.cnblogs.com/taoshihan/p/"/static/js/peer.js"></script>
<script>
new Vue({
el: '#app',
delimiters:["<{","}>"],
data: {
window:window,
server:getWsBaseUrl()+"/ws_visitor",
socket:null,
msgList:[],
imgPreviewSrc:[
""],
msgListNum:[],
messageContent:"",
chatTitle:KEFU_LANG[LANG]['connecting'],
visitor:{},
face:emojiGifsMap(),
showKfonline:false,
socketClosed:false,
focusSendConn:false,
wsSocketClosed:true,
timer:null,
loadingTimer:null,
sendDisabled:false,
entLogo:"",
entName:"",
peer:null,
peerjsId:"",
kefuPeerId:"",
loading:null,
localStream:null,
flyLang:KEFU_LANG[LANG],
lang:LANG,
textareaFocused:false,
replys:[],
noticeName:"",
noticeAvatar:"",
allOffline:false,
visitorContact:{
email:"",
weixin:"",
name:"",
msg:"",
},
haveUnreadMessage:false,
audioDialog:false,
flagsDialog:false,
recorder:null,
recorderAudio:null,
recordeTimer:null,
recoderSecond:0,
currentActiveTime:Date.now(),
timeoutTimer:null,
timeoutLongTime:20*60*1000,//20分鐘沒反應
allTimeouter:[],
currentPage:1,
showLoadMore:false,
loadMoreDisable:false,
websocketOpenNum:0,//websocket打開次數
websocketMaxOpenNum:10,//websocket最大打開次數
talkBtnText:"按住 說話",
recorderEnd:false,
isMobile:false,
isIframe:false,
onlineType:"success",
reconnectDialog:false,
showIconBtns:false,
showFaceIcon:false,
showKefuName:SHOW_KEFU_NAME,
comment:false,
qqMap:false,
hotQuestion:[],
topQuestionList:[],
topQuestionCount:0,
topQuestionPage:1,
topQuestionPagesize:5,
entIntroduce:"",
robotSwitch:"",
robotNoAnswer:"",
visitorNotice:"",
autoWelcome:"",
searchList:[],
VisitorVoiceBtn:'{{.VisitorVoiceBtn}}',
VisitorMapBtn:'{{.VisitorMapBtn}}',
VisitorCommentBtn:'{{.VisitorCommentBtn}}',
VisitorFaceBtn:'{{.VisitorFaceBtn}}',
VisitorReadStatus:'{{.VisitorReadStatus}}',
VisitorPlusBtn:'{{.VisitorPlusBtn}}',
VisitorUploadImgBtn:'{{.VisitorUploadImgBtn}}',
VisitorUploadFileBtn:'{{.VisitorUploadFileBtn}}',
VisitorMaxLength:'{{.VisitorMaxLength}}'==''?100:parseInt('{{.VisitorMaxLength}}'),
VisitorShowAvator:'{{.VisitorShowAvator}}',
VisitorWechatQrcodeUrl:'{{.VisitorWechatQrcodeUrl}}',
percentage:0,
visitorMaxNumLimit:false,//客服達到接待上限
visitorMaxNumNotice:"",//客服達到接待上限文案
visitorCookie:"",
scanWechatQrcode:"",
entConfig:{},
colors: [
{color: '#f56c6c', percentage: 20},
{color: '#e6a23c', percentage: 40},
{color: '#5cb87a', percentage: 60},
{color: '#1989fa', percentage: 80},
{color: '#6f7ad3', percentage: 100}
],
commentScore:0,
commentContent:"",
isCalling:false,
call:null,
videoElement:null,
canvasElement:null,
isVideo:false,
entInfo:{},
},
methods: {
//初始化websocket
initConn:function() {
this.socket = new ReconnectingWebSocket(this.server+"?visitor_id="+this.visitor.visitor_id+"&to_id="+this.visitor.to_id);//創建Socket實體
this.socket.debug = true;
this.socket.onmessage = this.OnMessage;
this.socket.onopen = this.OnOpen;
this.socket.onerror = this.OnError;
this.socket.onclose = this.OnClose;
this.ping();
},
OnOpen:function() {
console.log("ws:onopen");
//限制最大打開次數
if(this.websocketOpenNum>=this.websocketMaxOpenNum){
this.chatTitle=KEFU_LANG[LANG]['refresh'];
this.socket.close();
return;
}
this.websocketOpenNum++;
this.chatTitle=this.noticeName;
this.checkTimeout();
this.socketClosed=false;
this.focusSendConn=false;
this.wsSocketClosed=false;
this.sendVisitorLogin();
this.getExtendInfo();
this.reconnectDialog=false;
this.showTitle(KEFU_LANG[LANG]['connectok']);
this.getNotice();
},
OnMessage:function(e) {
console.log("ws:onmessage");
this.socketClosed=false;
this.focusSendConn=false;
const redata =https://www.cnblogs.com/taoshihan/p/ JSON.parse(e.data);
if (redata.type == "kfOnline") {
let msg = redata.data
if(this.showKfonline && this.visitor.to_id==msg.id){
return;
}
this.visitor.to_id=msg.id;
this.chatTitle=msg.name+","+KEFU_LANG[LANG]['chating'];
$(".chatBox").append("<div class=\"chatTime\">"+this.chatTitle+"</div>");
this.scrollBottom();
this.showKfonline=true;
}
if (redata.type == "transfer") {
var kefuId = redata.data
if(!kefuId){
return;
}
this.visitor.to_id=kefuId;
}
if (redata.type == "comment") {
this.comment=true;
}
if (redata.type == "wechat_notice") {
this.showTitle(KEFU_LANG[LANG]['wechatNotice']);
}
if (redata.type == "notice") {
let msg = redata.data
if(!msg){
return;
}
this.chatTitle=msg
$(".chatBox").append("<div class=\"chatTime\">"+this.chatTitle+"</div>");
this.scrollBottom();
}
if (redata.type == "accept") {
var _this=this;
let msg = redata.data;
if(!msg||_this.localStream==null){
return;
}
// this.$confirm('請求與您通話?', '提示', {
// confirmButtonText: '確定',
// cancelButtonText: '取消',
// type: 'warning'
// }).then(() => {
_this.kefuPeerId=msg;
_this.isCalling=true;
// }).catch(() => {
// });
}
if (redata.type == "callPhone") {
this.callPhone();
}
if (redata.type == "callVideo") {
this.callPeer();
}
if (redata.type == "refuse") {
this.$message({
message: "已掛斷",
type: 'error'
});
this.callClear();
return;
}
if (redata.type == "delete") {
var msg = redata.data;
for(var i=0;i<this.msgList.length;i++){
if(this.msgList[i].msg_id==msg.msg_id){
this.msgList.splice(i,1);
break;
}
}
}
if (redata.type == "read") {
var msg = redata.data;
for(var i=0;i<this.msgList.length;i++){
this.msgList[i].read_status=KEFU_LANG[LANG]['read'];
}
}
if (redata.type == "message") {
let msg = redata.data
//this.visitor.to_id=msg.id;
var _this=this;
var msgArr=msg.content.split("[br]");
for(var i in msgArr){
let content = {}
content.avator = msg.avator;
content.name = msg.name;
content.content =replaceSpecialTag(msgArr[i]);
content.is_kefu = false;
content.time = shortTime(msg.time);
content.is_reply=true;
content.msg_id = msg.msg_id;
this.msgList.push(content);
setTimeout(function () {
_this.scrollBottom();
},200);
}
// let content = {}
// content.avator = msg.avator;
// content.name = msg.name;
// content.content =replaceSpecialTag(msg.content);
// content.is_kefu = false;
// content.time = msg.time;
// content.msg_id = msg.msg_id;
// this.msgList.push(content);
notify(msg.name, {
body: msg.content,
icon: msg.avator
},function(notification) {
window.focus();
notification.close();
});
//this.scrollBottom();
flashTitle();//標題閃爍
//clearInterval(this.timer);
this.cleanAllTimeout();
this.alertSound('/static/images/alert4.mp3');//提示音
this.haveUnreadMessage=true;
}
if (redata.type == "close") {
this.showTitle(KEFU_LANG[LANG]['closemes']);
this.scrollBottom();
this.socket.close();
//this.socketClosed=true;
this.focusSendConn=true;
this.reconnectDialog=true;
}
if (redata.type == "force_close") {
this.showTitle(KEFU_LANG[LANG]['forceclosemes']);
this.scrollBottom();
this.socket.close();
this.socketClosed=true;
this.reconnectDialog=true;
}
if (redata.type == "auto_close") {
this.showTitle(KEFU_LANG[LANG]['autoclosemes']);
this.scrollBottom();
this.socket.close();
this.socketClosed=true;
this.reconnectDialog=true;
}
if (redata.type == "change_id") {
var openId = redata.data;
setFakeCookie("visitor_"+ENT_ID,openId,this.visitorCookie);
location.reload();
}
window.parent.postMessage(redata,"*");
},
//發送給客戶
chatToUser:function() {
this.searchList=[];
if(this.sendDisabled){
return;
}
var messageContent=this.messageContent.trim("\r\n");
messageContent=messageContent.replace("\n","");
messageContent=messageContent.replace("\r\n","");
messageContent=filterXSS(messageContent);
if(messageContent==""||messageContent=="\r\n"){
this.messageContent="";
return;
}
this.messageContent="";
this.currentActiveTime=Date.now();
if(this.socketClosed){
this.initConn();
// this.$message({
// message: '連接關閉!請重新打開頁面',
// type: 'warning'
// });
//return;
}
this.sendDisabled=true;
let _this=this;
let content = {}
content.avator=_this.visitor.avator;
content.content = replaceSpecialTag(messageContent);
content.name = _this.visitor.name;
content.is_kefu = true;
content.time = _this.getNowDate();
content.show_time=false;
let mes = {};
mes.type = "visitor";
mes.content = messageContent;
mes.from_id = this.visitor.visitor_id;
mes.to_id = this.visitor.to_id;
//機器人回答
if(this.robotSwitch=="true"){
this.sendDisabled=false;
this.messageContent="";
//轉接人工,沒用處于排隊狀態
if(this.turnToMan && this.turnToMan.includes(mes.content)){
if(this.visitorMaxNumLimit){
this.showTitle(this.visitorMaxNumNotice);
return;
}
this.initConn();
this.robotSwitch="";
}else{
content.read_status = KEFU_LANG[LANG]['read'];
_this.msgList.push(content);
_this.scrollBottom();
_this.sendAjax("/2/robotMessage","post",{ent_id:ENT_ID,content:messageContent},function(msg){
if(msg.content==""){
msg.content=_this.robotNoAnswer;
}
if(msg.content==""){
return;
}
let content = {}
content.avator=msg.avator;
content.content = replaceSpecialTag(msg.content);
content.name = msg.username;
content.is_kefu = false;
content.read_status = KEFU_LANG[LANG]['read'];
content.time = _this.getNowDate();
content.show_time=false;
_this.msgList.push(content);
_this.scrollBottom();
});
return;
}
}
content.read_status = KEFU_LANG[LANG]['unread'];
_this.msgList.push(content);
_this.scrollBottom();
//發送人工訊息
$.post("/2/message?lang="+getQuery("lang"),mes,function(res){
_this.sendDisabled=false;
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
if(res.code==401){
setTimeout(function(){
window.location.reload();
},2000);
}
return;
}
var result=res.result
if(result.isBlack){
_this.msgList.pop();
content.content=result.content;
_this.msgList.push(content);
}
_this.messageContent = "";
_this.cleanAllTimeout();
_this.sendSound();
_this.sendDisabled=false;
});
},
//正在輸入
inputNextText:function(){
var _this=this;
this.sendInputingStrNow(this.messageContent);
//是否進行搜索
if(this.robotSwitch!="true"){
return;
}
this.sendAjax("/2/searchQuestion","get",{ent_id:ENT_ID,content:this.messageContent},function(res){
result=res.result;
if(!result){
return;
}
for(key in result){
var str=result[key].title;
str= str.replace(_this.messageContent,"<span>"+_this.messageContent+"</span>");
result[key].htmlTitle=str;
}
_this.searchList=result;
});
},
sendInputingStrNow:function(str){
if(this.socketClosed||!this.socket||this.wsSocketClosed){
return;
}
var message = {}
message.type = "inputing";
message.data = {
from : this.visitor.visitor_id,
to : this.visitor.to_id,
content:str
};
this.socket.send(JSON.stringify(message));
},
sendVisitorLogin:function(){
var _this=this;
setTimeout(function(){
if(_this.socketClosed||!_this.socket||_this.wsSocketClosed){
return;
}
var message = {}
message.type = "visitor_login";
message.data = {
from : _this.visitor.visitor_id,
to : _this.visitor.to_id,
};
_this.socket.send(JSON.stringify(message));
}, 3000);
},
OnClose:function(event) {
console.log("ws:onclose",event);
this.focusSendConn=true;
this.wsSocketClosed=true;
this.closeTimeoutTimer();
},
one rror:function(event) {
console.log("ws:onerror",event);
this.closeTimeoutTimer();
},
//獲取當前用戶資訊
getUserInfo:function(){
var _this=this;
var visitor_id=getFakeCookie("visitor_"+ENT_ID);
var to_id=KEFU_ID;
var extra=EXTRA;
var url=getQuery("url");
var paramVisitorId=VISITOR_ID;
if(paramVisitorId!=""){
visitor_id=paramVisitorId;
}
var visitorName=VISITOR_NAME;
var avator=AVATOR;
if(extra==""){
var ext={};
var refer=document.referrer?document.referrer:"-";
ext.refer=refer;
ext.host=document.location.href;
extra=utf8ToB64(JSON.stringify(ext));
}else{
try{
var jsonStr=b64ToUtf8(extra)
var extJson=JSON.parse(jsonStr)
if(extJson.refer){
if(REFER=="") REFER=extJson.refer;
if(REFER_URL=="") REFER_URL=extJson.refer;
}
}catch (e) {}
}
if(REFER_URL==""){
REFER_URL=document.referrer;
}
if(REFER==""){
REFER=document.title;
}
//發送訊息
$.ajax({
type: "post",
url: "/visitor_login",
data:{visitor_id:visitor_id,
visitor_name:visitorName,
avator:avator,
refer:REFER,
to_id:to_id,
extra:extra,
ent_id:ENT_ID,
url:document.location.href,
refer_url:REFER_URL,
title:document.title
},
error:function(res){
var data=https://www.cnblogs.com/taoshihan/p/JSON.parse(res.responseText);
_this.$message({
message: data.msg,
type: 'error'
});
},
success: function(res) {
if(res.code==40012){
_this.$message({
message: res.msg,
type: 'error'
});
_this.chatTitle=res.msg;
_this.sendDisabled=true;
return;
}
if(res.code==40016){
_this.$message({
message: KEFU_LANG[LANG]['freqLimit'],
type: 'error'
});
_this.chatTitle=KEFU_LANG[LANG]['freqLimit'];
_this.sendDisabled=true;
return;
}
_this.entInfo=res.kefu;
_this.noticeName=res.kefu.username;
_this.noticeAvatar=res.kefu.avatar;
_this.robotNoAnswer=res.robotNoAnswer;
_this.getTopQuestion();
//判斷同時接待訪客數
if(res.code==40018){
_this.visitorMaxNumLimit=true;
_this.visitor=res.result;
_this.robotSwitch="true";
_this.chatTitle=_this.noticeName;
_this.turnToMan=res.turnToMan.split(",");
var visitorMaxNumNotice=res.visitorMaxNumNotice;
if(visitorMaxNumNotice==""){
visitorMaxNumNotice="當前有"+res.visitorMaxNum+"位訪客正在咨詢,請稍等一會再嘗試 <a href='javascript:window.location.reload();'>重繪</a>";
}
_this.visitorMaxNumNotice=visitorMaxNumNotice;
_this.showTitle(visitorMaxNumNotice);
_this.sendDisabled=true;
return;
}
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
_this.chatTitle=res.msg;
_this.sendDisabled=true;
return;
}
if(res.alloffline){
_this.onlineType="danger";
}else{
_this.onlineType="success";
}
if(KEFU_CONFIG.SHOW_OFFLINE_PAGE){
_this.allOffline=res.alloffline;
}
_this.sendDisabled=false;
_this.visitor=res.result;
_this.noticeName=res.kefu.username;
_this.noticeAvatar=res.kefu.avatar;
_this.entIntroduce=res.entIntroduce;
_this.robotSwitch=res.robotSwitch;
_this.turnToMan=res.turnToMan.split(",");
_this.chatTitle=_this.noticeName;
_this.visitorNotice=res.visitorNotice;
_this.autoWelcome=res.autoWelcome;
_this.visitorCookie=res.visitorCookie;
_this.scanWechatQrcode=res.scanWechatQrcode;
document.title=res.kefu.username;
if(!getFakeCookie("visitor_"+ENT_ID)){
setFakeCookie("visitor_"+ENT_ID,_this.visitor.visitor_id,res.visitorCookie);
}
_this.loadMoreMessages();
_this.showWechatTip();
if(_this.robotSwitch!="true"){
_this.initConn();
}
}
});
},
//獲取資訊串列
getMesssagesByVisitorId:function(isAll){
let _this=this;
$.ajax({
type:"get",
url:"/2/messages?visitor_id="+this.visitor.visitor_id,
success: function(data) {
if(data.code==200 && data.result!=null&&data.result.length!=0){
let msgList=data.result;
_this.msgList=[];
for(var i=0;i<msgList.length;i++){
let visitorMes=msgList[i];
let content = {}
if(visitorMes["mes_type"]=="kefu"){
content.is_kefu = false;
}else{
content.is_kefu = true;
}
content.avator = visitorMes["avator"];
content.name = visitorMes["name"];
content.content = replaceContent(visitorMes["content"]);
content.time = visitorMes["time"];
_this.msgList.push(content);
_this.scrollBottom();
}
}
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
_this.chatTitle=KEFU_LANG[LANG]['refresh'];
}
}
});
},
//獲取資訊串列
sendEmailMsg:function(){
let _this=this;
_this.visitorContact.ent_id=ENT_ID;
$.ajax({
type:"post",
url:"/ent/email_message",
data:_this.visitorContact,
success: function(data) {
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}else{
_this.allOffline=false;
}
}
});
},
//滾動到底部
scrollBottom:function(){
var _this=this;
//$('.chatVisitorPage').animate({scrollTop:'99999999999999'},"slow");
this.$nextTick(function(){
var container = _this.$el.querySelector(".chatVisitorPage");
container.scrollTop = 999999;
//alert(1);
//$('.chatVisitorPage').animate({scrollTop:'99999999999999'},'99999999');
// $('.chatVisitorPage').scrollTop(9999999999999999999);
});
},
//軟鍵盤問題
textareaFocus:function(){
// if(/Android|webOS|iPhone|iPad|BlackBerry/i.test(navigator.userAgent)) {
// //$(".chatContext").css("margin-bottom","0");
// //$(".chatBoxSend").css("position","static");
// this.textareaFocused=true;
// }
this.scrollBottom();
},
textareaBlur:function(){
// if(this.textareaFocused&&/Android|webOS|iPhone|iPad|BlackBerry/i.test(navigator.userAgent)) {
// var chatBoxSendObj=$(".chatBoxSend");
// var chatContextObj=$(".chatContext");
// if(this.textareaFocused&&chatBoxSendObj.css("position")!="fixed"){
// //chatContextObj.css("margin-bottom","105px");
// //chatBoxSendObj.css("position","fixed");
// this.textareaFocused=false;
// }
//
// }
this.scrollBottom();
},
sendReply:function(title){
var _this=this;
let msg = {}
msg.avator=_this.visitor.avator;
msg.content = replaceContent(title);
msg.name = _this.visitor.name;
msg.is_kefu = true;
msg.time = _this.getNowDate();
msg.show_time=false;
_this.msgList.push(msg);
_this.scrollBottom();
var mes = {};
mes.content = title;
mes.from_id = this.visitor.visitor_id;
mes.ent_id = ENT_ID;
_this.sendAjax("/2/message_ask","post",mes,function(msg){
var msgArr=msg.content.split("[b]");
for(var i in msgArr){
let content = {}
content.avator = msg.avator;
content.name = msg.name;
content.content =replaceSpecialTag(msgArr[i]);
content.is_kefu = false;
content.time = msg.time;
content.is_reply=true;
_this.msgList.push(content);
_this.scrollBottom();
}
_this.cleanAllTimeout();
_this.alertSound('/static/images/notification.mp3');//提示音
});
//this.chatToUser();
},
//獲取日期
getNowDate : function() {// 獲取日期
var d = new Date(new Date());
return d.getFullYear() + '-' + this.digit(d.getMonth() + 1) + '-' + this.digit(d.getDate())
+ ' ' + this.digit(d.getHours()) + ':' + this.digit(d.getMinutes()) + ':' + this.digit(d.getSeconds());
},
//補齊數位
digit : function (num) {
return num < 10 ? '0' + (num | 0) : num;
},
setCache : function (key,obj){
if(navigator.cookieEnabled&&typeof window.localStorage !== 'undefined'){
localStorage.setItem(key, JSON.stringify(obj));
}
},getCache : function (key){
if(navigator.cookieEnabled&&typeof window.localStorage !== 'undefined') {
return JSON.parse(localStorage.getItem(key));
}
},
setNoticeWelcome(list){
var _this=this;
var msgs = list;
var delaySecond=0;
for(let i in msgs){
var msg=msgs[i];
if(msg.delay_second){
delaySecond+=msg.delay_second;
}else{
delaySecond+=4;
}
var timer = setTimeout(function (msg) {
msg.time=shortTime(getNowDate());
msg.content = replaceSpecialTag(msg.content);
msg.name=_this.entConfig.robotName;
_this.msgList.push(msg);
_this.scrollBottom();
_this.alertSound('/static/images/notification.mp3');
var redata=https://www.cnblogs.com/taoshihan/p/{
type:"message",
data:msg
}
window.parent.postMessage(redata,"*");
},1000*delaySecond,msg);
_this.allTimeouter.push(timer);
}
},
//獲取自動歡迎陳述句
getNotice : function (){
var _this=this;
var oldNotice=getFakeCookie("noticed_"+ENT_ID);
if(oldNotice){
if(_this.autoWelcome=="on"){
return;
}
$.get("/2/notices?visitor_id="+this.visitor.visitor_id+"&ent_id="+ENT_ID+"&kefu_name="+this.visitor.to_id,function(res) {
_this.entConfig=res.result.ent_config;
if (res.result.welcome != null) {
setFakeCookie("noticed_"+ENT_ID,res.result.welcome,7*3600*24);
_this.setNoticeWelcome(res.result.welcome);
}
});
return;
}
$.get("/2/notices?is_record=1&visitor_id="+this.visitor.visitor_id+"&ent_id="+ENT_ID+"&kefu_name="+this.visitor.to_id,function(res) {
_this.entConfig=res.result.ent_config;
if (res.result.welcome != null) {
setFakeCookie("noticed_"+ENT_ID,res.result.welcome,7*3600*24);
_this.setNoticeWelcome(res.result.welcome);
}
});
},
initCss:function(){
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?82938760e00806c6c57adee91f39aa5e";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
var _this=this;
$(function () {
//手機端的樣式問題
// if(_this.isMobile){
// $(".chatVisitorPage").css("height","calc(100% - 155px)");
// }
//展示表情
// var faces=placeFace();
// $.each(faceTitles, function (index, item) {
// _this.face.push({"name":item,"path":faces[item]});
// });
// $(".visitorFaceBtn").click(function(e){
// var status=$('.faceBox').css("display");
// if(status=="block"){
// $('.faceBox').hide();
// }else{
// $('.faceBox').show();
// }
// return false;
// });
$("body").on("click",".replyContentBtn a",function() {
var txt=$(this).text();
var href=https://www.cnblogs.com/taoshihan/p/$(this).attr("href");
if(href=https://www.cnblogs.com/taoshihan/p/="self"||!href){
_this.messageContent=txt;
_this.chatToUser();
return false;
}
});
//var windheight = $(window).height();
$(window).resize(function(){
//var docheight = $(window).height(); /*喚起鍵盤時當前視窗高度*/
//_this.scrollBottom();
_this.visitorPageHeight();
// if(docheight < windheight){ /*當喚起鍵盤高度小于未喚起鍵盤高度時執行*/
// $(".chatBoxSend").css("position","static");
// }else{
// $(".chatBoxSend").css("position","fixed");
// }
//_this.visitorPageHeight();
//document.getElementById('chatVisitorPage').style.height = (document.documentElement.clientHeight - 71) + 'px';
});
if(isMobile()){
_this.visitorPageHeight();
//document.getElementById('chatVisitorPage').style.height = (document.documentElement.clientHeight - 71) + 'px';
}
//自動問答
$("body").on("click",".visitorReplyContent",function() {
var txt=$(this).find("span").text();
_this.messageContent=txt;
_this.chatToUser();
});
//自動問答換一換
$("body").on("click",".visitorReplyTitle a",function() {
++_this.topQuestionPage;
if(_this.topQuestionPage>_this.topQuestionCount){
_this.topQuestionPage=1;
}
var result=pagination(_this.topQuestionPage,_this.topQuestionPagesize,_this.topQuestionList);
_this.makeReplyItem(result,true);
});
});
},
//心跳
ping:function(){
let _this=this;
let mes = {}
mes.type = "ping";
mes.data = "visitor:"+_this.visitor.visitor_id;
setInterval(function () {
if(_this.socket!=null&&!_this.wsSocketClosed){
_this.socket.send(JSON.stringify(mes));
}
},10000);
},
//初始化
init:function(){
var _this=this;
_this.isMobile=isMobile();
this.initCss();
//已讀訊息
var ms= 1000*2;
var lastClick = Date.now() - ms;
$("body").mouseover(function(){
if(!_this.haveUnreadMessage){
return;
}
if (Date.now() - lastClick >= ms) {
lastClick = Date.now();
//如果有未讀訊息,呼叫已讀介面
_this.sendAjax("/2/messages_read","post",{"visitor_id":_this.visitor.visitor_id,"kefu":_this.visitor.to_id},function(data){
_this.haveUnreadMessage=false;
});
}
});
$('body').click(function(){
clearFlashTitle();
window.parent.postMessage({type:"focus"},"*");
//$('.faceBox').hide();
//剪貼板
try{
var selecter = window.getSelection().toString();
if (selecter != null && selecter.trim() != ""){
var str=selecter.trim();
_this.sendInputingStrNow(str);
}
} catch (err){
var selecter = document.selection.createRange();
var s = selecter.text;
if (s != null && s.trim() != ""){
var str=s.trim();
_this.sendInputingStrNow(str);
}
}
});
$("body").on("click",".chatImagePic",function() {
var url=$(this).attr("data-src");
_this.imgPreviewSrc=[url];
_this.$refs.preview.clickHandler();
//new PinchZoom.default($(this)[0], {});
// _this.$alert("<img src='"+url+"'/>", "", {
// dangerouslyUseHTMLString: true
// });
return false;
});
window.onfocus = function () {
//_this.focusHandle();
}
//判斷當前是否在iframe中
if(self!=top){
_this.isIframe=true;
}
},
//表情點擊事件
faceIconClick:function(index){
this.showFaceIcon=false;
this.messageContent+="face"+this.face[index].name;
},
//上傳圖片
uploadImg:function (url){
let _this=this;
$('#uploadImg').after('<input type="file" accept="image/gif,image/jpeg,image/jpg,image/png" id="uploadImgFile" name="file" style="display:none" >');
$("#uploadImgFile").click();
$("#uploadImgFile").change(function (e) {
var formData = https://www.cnblogs.com/taoshihan/p/new FormData();
var file = $("#uploadImgFile")[0].files[0];
formData.append("imgfile",file); //傳給后臺的file的key值是可以自己定義的
filter(file) && $.ajax({
url: url || '',
type: "post",
data: formData,
contentType: false,
processData: false,
dataType: 'JSON',
mimeType: "multipart/form-data",
//添加自定義屬性,監聽背景關系的進度
xhr: function() {
//創建原生的ajax請求物件
var xhr = $.ajaxSettings.xhr();
//監聽進度的一個事件
xhr.upload.onprogress = function(e) {
console.log(e.total); //檔案大小
console.log(e.loaded); //上傳多少
var w = parseInt((e.loaded / e.total) * 100)
console.log(w);
_this.percentage=w;
if(w>=100){
_this.percentage=0;
}
}
return xhr
},
success: function (res) {
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
}else{
_this.$message({
message: KEFU_LANG[LANG]['uploadSuccess'],
type: 'success'
});
_this.messageContent+='img[' + res.result.path + ']';
_this.chatToUser();
setTimeout(function () {
_this.scrollBottom();
},2000);
}
},
error: function (data) {
console.log(data);
_this.$message({
message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
type: 'error'
});
}
});
});
},
//上傳檔案
uploadFile:function (url){
let _this=this;
$('#uploadFile').after('<input type="file" id="uploadRealFile" name="file2" style="display:none" >');
$("#uploadRealFile").click();
$("#uploadRealFile").change(function (e) {
var formData = https://www.cnblogs.com/taoshihan/p/new FormData();
var file = $("#uploadRealFile")[0].files[0];
formData.append("realfile",file); //傳給后臺的file的key值是可以自己定義的
console.log(formData);
$.ajax({
url: url || '',
type: "post",
data: formData,
contentType: false,
processData: false,
dataType: 'JSON',
mimeType: "multipart/form-data",
//添加自定義屬性,監聽背景關系的進度
xhr: function() {
//創建原生的ajax請求物件
var xhr = $.ajaxSettings.xhr();
//監聽進度的一個事件
xhr.upload.onprogress = function(e) {
console.log(e.total); //檔案大小
console.log(e.loaded); //上傳多少
var w = parseInt((e.loaded / e.total) * 100)
console.log(w);
_this.percentage=w;
if(w>=100){
_this.percentage=0;
}
}
return xhr
},
success: function (res) {
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
}else{
_this.$message({
message: KEFU_LANG[LANG]['uploadSuccess'],
type: 'success'
});
//_this.messageContent+='file[' + res.result.path + ']';
var data=https://www.cnblogs.com/taoshihan/p/JSON.stringify({
name:res.result.name,
ext:res.result.ext,
size:res.result.size,
path:res.result.path,
})
_this.messageContent+='mutiFile[' + data+ ']';
_this.chatToUser();
}
},
error: function (data) {
console.log(data);
_this.$message({
message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
type: 'error'
});
}
});
});
},
//粘貼上傳圖片
onPasteUpload:function(event){
let items = event.clipboardData && event.clipboardData.items;
let file = null
if (items && items.length) {
// 檢索剪切板items
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile()
}
}
}
if (!file) {
return;
}
let _this=this;
var formData = https://www.cnblogs.com/taoshihan/p/new FormData();
formData.append('imgfile', file);
$.ajax({
url: '/uploadimg',
type: "post",
data: formData,
contentType: false,
processData: false,
dataType: 'JSON',
mimeType: "multipart/form-data",
//添加自定義屬性,監聽背景關系的進度
xhr: function() {
//創建原生的ajax請求物件
var xhr = $.ajaxSettings.xhr();
//監聽進度的一個事件
xhr.upload.onprogress = function(e) {
console.log(e.total); //檔案大小
console.log(e.loaded); //上傳多少
var w = parseInt((e.loaded / e.total) * 100)
console.log(w);
_this.percentage=w;
if(w>=100){
_this.percentage=0;
}
}
return xhr
},
success: function (res) {
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
}else{
_this.$message({
message: KEFU_LANG[LANG]['uploadSuccess'],
type: 'success'
});
_this.messageContent+='img[' + res.result.path + ']';
_this.chatToUser();
setTimeout(function () {
_this.scrollBottom();
},2000);
}
},
error: function (data) {
console.log(data);
_this.$message({
message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
type: 'error'
});
}
});
},
//自動
getTopQuestion:function(){
var _this=this;
$.get("/other/getTopQuestion?ent_id="+ENT_ID,function(res) {
if(res.code!=200||!res.result){
return;
}
var hotQuestion=res.result.hotQuestion;
if(hotQuestion!=""){
_this.hotQuestion=hotQuestion.split(",");
}
var questionList=res.result.questionList;
if(questionList.length==0){
return;
}
_this.topQuestionList=questionList;
_this.topQuestionCount=sumPage(_this.topQuestionPagesize,questionList);
var result=pagination(1,_this.topQuestionPagesize,questionList);
_this.makeReplyItem(result);
});
},
makeReplyItem:function(result,isPage){
var _this=this;
var msg={};
msg.type="card";
msg.avator = _this.noticeAvatar;
msg.name = _this.noticeName;
msg.show_time = true;
msg.time = _this.getNowDate();
msg.content="";
var i=1;
for(key in result){
msg.content+="<div class='visitorReplyContent'>"+i+". <span>"+result[key]+"</span></div>";
i++;
}
if(!isPage){
_this.msgList.push(msg);
_this.scrollBottom();
}else{
$(".cardBoxContent").html(msg.content);
}
},
//自動
getAutoReply:function(){
var _this=this;
$.get("/autoreply?ent_id="+ENT_ID,function(res) {
if(res.code!=200 || res.result.length==0){
return;
}
var result=res.result;
_this.replys.push(result);
});
},
//提示音
alertSound:function(soundUrl){
var b = document.getElementById("chatMessageAudio");
if (b.canPlayType('audio/ogg; codecs="vorbis"')) {
b.type= 'audio/mpeg';
b.src=soundUrl ;
var p = b.play();
p && p.then(function () {
}).catch(function (e) {
});
}
},
sendSound:function(){
var b = document.getElementById("chatMessageSendAudio");
if (b.canPlayType('audio/ogg; codecs="vorbis"')) {
b.type= 'audio/mpeg';
b.src= '/static/images/sent.ogg';
var p = b.play();
p && p.then(function(){}).catch(function(e){});
}
},
initPeerjs:function(){
var peer = new Peer();
this.peer=peer;
var _this=this;
peer.on('open', function(id) {
console.log('My peer ID is: ' + id);
_this.peerjsId=id;
});
peer.on('close', function() {
console.log('My peer close');
if(_this.loading!=null){
_this.loading.close();
}
});
peer.on('disconnected', function() {
console.log('My peer disconnected');
if(_this.loading!=null){
_this.loading.close();
}
});
peer.on('error', function() {
console.log('My peer error');
if(_this.loading!=null){
_this.loading.close();
}
});
},
//打電話
callPhone:function(){
var _this=this;
var media=(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
if(!media){
_this.$message({
type: 'error',
message: "not support"
});
return ;
}
var getUserMedia = media.bind(navigator);
this.$confirm(this.flyLang.videoAudio, this.flyLang.tips, {
confirmButtonText: this.flyLang.audio,
cancelButtonText: this.flyLang.cancel,
type: 'warning'
}).then(() => {
_this.messageContent="voice call...";
_this.chatToUser();
_this.loading = _this.$loading({
lock: true,
text: _this.flyLang.connecting,
spinner: 'el-icon-phone-outline',
background: 'rgba(0, 0, 0, 0.7)'
});
_this.initPeerjs();//初始化peerjs
_this.loadingTimerTimeoutClose();
_this.isVideo=false;
getUserMedia({video:false, audio: {
noiseSuppression: true,
echoCancellation: true,
}}, function(stream) {
_this.localStream=stream;
_this.sendAjax("/2/callKefu","post",{"action":"callpeer",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
});
}, function(err) {
_this.$message({
type: 'error',
message: err
});
if(_this.loading) _this.loading.close();
});
}).catch(() => {
});
},
callPeer:function(){
var _this=this;
var media=(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
if(!media){
_this.$message({
type: 'error',
message: "not support"
});
return ;
}
var getUserMedia = media.bind(navigator);
this.$confirm(this.flyLang.videoAudio, this.flyLang.tips, {
confirmButtonText: this.flyLang.video,
cancelButtonText: this.flyLang.cancel,
type: 'warning'
}).then(() => {
_this.messageContent="video call...";
_this.chatToUser();
this.loading = this.$loading({
lock: true,
text: _this.flyLang.connecting,
spinner: 'el-icon-video-camera',
background: 'rgba(0, 0, 0, 0.5)'
});
this.loadingTimerTimeoutClose();
this.isVideo=true;
//初始化peerjs
this.initPeerjs();
getUserMedia({video:true, audio: {
noiseSuppression: true,
echoCancellation: true,
}}, function(stream) {
_this.localStream=stream;
_this.sendAjax("/2/callKefu","post",{"action":"callpeer",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
});
}, function(err) {
_this.$message({
type: 'error',
message: err
});
if(_this.loading) _this.loading.close();
});
}).catch(() => {
});
},
talkPeer:function(){
var _this=this;
var canvas = this.canvasElement;
if(this.loading!=null){
this.loading.close();
}
clearTimeout(this.loadingTimer);
this.$message({
message: '正在通話...',
type: 'success'
});
if(_this.kefuPeerId==""||_this.localStream==null){
return;
}
//本人攝像頭
if(_this.isVideo){
var localVideo=document.querySelector("#chatLocalRtc");
localVideo.srcObject = _this.localStream;
localVideo.autoplay=true;
}
//localVideo.autoplay = true;
_this.call = _this.peer.call(_this.kefuPeerId, _this.localStream);
_this.call.on('stream', function(remoteStream) {
var remoteVideo = document.querySelector("#chatRtc");
remoteVideo.srcObject = remoteStream;
remoteVideo.autoplay = true;
});
_this.call.on('close', function() {
console.log("call close");
_this.loading.close();
_this.callClear();
});
_this.call.on('error', function(err) {
console.log(err);
_this.callClear();
_this.loading.close();
});
// _this.$alert('正在通話,請保持頁面..', '提示', {
// confirmButtonText: '掛斷',
// callback: function(){
// _this.sendAjax("/2/callKefu","post",{"action":"callCancel",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
// });
// if(call!=null){
// call.close();
// }
// }
// });
// 呼叫Vudio
// var vudio = new Vudio(_this.localStream, canvas, {
// accuracy: 256,
// width: 800,
// height: 100,
// waveform: {
// fadeSide: false,
// maxHeight: 100,
// verticalAlign: 'middle',
// horizontalAlign: 'center',
// color: '#2980b9'
// }
// })
//
// vudio.dance()
},
callClose(){
var _this=this;
if(this.call==null) return;
_this.sendAjax("/2/callKefu","post",{"action":"callCancel",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
});
_this.callClear();
},
getExtendInfo:function(){
var _this=this;
var extra=getQuery("extra");
if(extra==""){
return;
}
try{
var extraString=b64ToUtf8(extra);
if(_this.getCache("extra")==extraString){
return;
}
var extra=JSON.parse(extraString);
if (typeof extra=="string"){
extra=JSON.parse(extra);
}
for(var key in extra){
if(extra[key]==""){
extra[key]="無";
}
if(key=="visitorProduct"){
_this.messageContent="product["+JSON.stringify(extra[key])+"]";
_this.chatToUser();
_this.setCache("extra",extraString);
};
}
}catch (e) {
}
},
sendAjax:function(url,method,params,callback){
let _this=this;
$.ajax({
type: method,
url: url,
data:params,
headers:{
"lang":getQuery("lang"),
},
error:function(res){
var data=https://www.cnblogs.com/taoshihan/p/JSON.parse(res.responseText);
console.log(data);
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}
},
success: function(data) {
if(data.code!=200){
_this.$message({
message: data.msg,
type: 'error'
});
}else if(data.result!=null){
callback(data.result);
}else{
callback(data);
}
}
});
},
showTitle:function(title){
//this.chatTitle=title;
$(".chatBox").append("<div class=\"chatNotice\"><div class='chatNoticeContent'>"+title+"</div></div>");
this.scrollBottom();
},
//開始錄音
startRecoder:function(e){
if(this.recorder){
this.recorder.destroy();
this.recorder=null;
}
var _this=this;
Recorder.getPermission().then(function() {
_this.recorder = new Recorder();
_this.recorderAudio = document.querySelector('#audio');
_this.recorder.start();
_this.recorder.onprogress = function (params) {
_this.recoderSecond = parseInt(params.duration);
}
this.talkBtnText = "松開 結束";
}, function(error){
_this.$message({
message: error,
type: 'error'
});
return;
});
e.preventDefault();
},
stopRecoder:function(e){
if(!this.recorder){
return;
}
var blob=this.recorder.getWAVBlob();
this.recorderAudio.src =https://www.cnblogs.com/taoshihan/p/ URL.createObjectURL(blob);
this.recorderAudio.controls = true;
this.talkBtnText="按住 說話";
this.recorderEnd=true;
e.preventDefault();
},
sendRecoder:function(){
if(!this.recorder){
return;
}
var blob=this.recorder.getWAVBlob();
var formdata = https://www.cnblogs.com/taoshihan/p/new FormData(); // form 表單 {key:value}
formdata.append("realfile", blob); // form input type="file"
var _this=this;
this.loading = this.$loading({
lock: true,
text: '正在發送',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
$.ajax({
url: "/2/uploadAudio",
type: 'post',
processData: false,
contentType: false,
data: formdata,
dataType: 'JSON',
mimeType: "multipart/form-data",
success: function (res) {
_this.loading.close();
if(res.code!=200){
_this.$message({
message: res.msg,
type: 'error'
});
}else{
_this.cancelRecoder();
_this.messageContent+='audio[' + res.result.path + ']';
_this.chatToUser();
}
}
})
},
cancelRecoder:function(){
this.audioDialog=false;
if(!this.recorder){
return;
}
this.recorder.destroy();
this.recorder=null;
this.recoderSecond=0;
},
recoderFormat:function(percentage){
return percentage+"s";
},
openNewWindow:function(){
var features = "height=800px, width=960px, top=0, left=0, toolbar=no, menubar=no,scrollbars=no,resizable=no, location=no, status=no"; //設定新視窗的特性
var me = window.open(location.href, "newW", features);
},
//超時關閉
checkTimeout:function(){
var _this=this;
this.timeoutTimer=setInterval(function(){
if (Date.now() - _this.currentActiveTime >= _this.timeoutLongTime) {
if(_this.VisitorCommentBtn!="true"){
_this.comment=true;
}
console.log("長時間無操作");
if(_this.socket!=null){
_this.reconnectDialog=true;
_this.showTitle(KEFU_LANG[LANG]['autoclosemes']);
_this.socket.close();
_this.socket=null;
}
}
},55000);
},
closeTimeoutTimer:function(){
clearInterval(this.timeoutTimer);
},
cleanAllTimeout:function(){
for(var i in this.allTimeouter){
clearTimeout(this.allTimeouter[i]);
}
},
loadMoreMessages:function(){
var _this=this;
var pagesize=5;
// if(this.currentPage>1){
// this.replys=[];
// }
if(_this.loadMoreDisable){
return;
}
var moreMessage=KEFU_LANG[LANG]['moremessage'];
this.flyLang.moremessage=this.flyLang.loading;
this.loadMoreDisable=true;
var hasUnread=false;
this.sendAjax("/2/messages_page","get",{pagesize:pagesize,ent_id:ENT_ID,page:this.currentPage,visitor_id:_this.visitor.visitor_id},function(result){
var len=result.list.length;
if(result.list.length!=0){
if(len<pagesize){
_this.showLoadMore=false;
}else{
_this.showLoadMore=true;
}
let msgList=result.list;
for(var i=0;i<msgList.length;i++) {
let visitorMes = msgList[i];
let content = {}
if (visitorMes["mes_type"] == "kefu") {
content.is_kefu = false;
content.content = replaceSpecialTag(visitorMes["content"]);
} else {
content.is_kefu = true;
content.content = replaceContent(visitorMes["content"]);
}
if (visitorMes["read_status"] == "read") {
content.read_status = KEFU_LANG[LANG].read;
} else {
content.read_status = KEFU_LANG[LANG].unread;
if(i==0){
hasUnread=true;
_this.haveUnreadMessage=true;
}
}
content.avator = visitorMes["avator"];
content.name = visitorMes["name"];
content.msg_id = visitorMes["msg_id"];
content.time = shortTime(visitorMes["time"]);
_this.msgList.unshift(content);
//_this.scrollBottom();
}
}else{
_this.showLoadMore=false;
}
if(_this.currentPage==1){
_this.scrollBottom();
//_this.getAutoReply();
}
_this.currentPage++;
_this.flyLang.moremessage=moreMessage;
_this.loadMoreDisable=false;
});
},
//展示微信公眾號帶參二維碼
showWechatTip:function(){
var _this=this;
if(this.VisitorWechatQrcodeUrl==""||this.scanWechatQrcode!="true"){
return;
}
if(this.visitor.visitor_id.substr(0,2)=='wx'){
this.showTitle("微信訪客用戶已登錄");
return;
}
var msg={};
msg.avator = _this.noticeAvatar;
msg.name = _this.noticeName;
msg.show_time = true;
msg.time = _this.getNowDate();
var child = '<div >';
child += '<img style="width:100px; margin:6px;" src="https://www.cnblogs.com/taoshihan/p/'+this.VisitorWechatQrcodeUrl+'?visitor_id=' + this.visitor.visitor_id + '&ent_id='+ENT_ID+'">';
child += '掃描或長按左側二維碼關注公眾號,<br>可防止更換瀏覽器丟失訊息、收不到回復,<br>并可接識訓復通知</div>';
msg.content=child;
_this.msgList.push(msg);
},
sendComment:function(tagName){
var _this=this;
if(!_this.commentScore){
this.$message({
message: _this.flyLang.invalidParam,
type: 'error'
});
return;
}
this.sendAjax("/2/comment","post",{
comment_score:_this.commentScore,
comment_content:_this.commentContent,
kefu_name:this.visitor.to_id,
ent_id:ENT_ID,
visitor_id:_this.visitor.visitor_id},function(result){});
},
//格式化時間
formatTime:function(time) {
// var timeDate=new Date(time);
// var timeStamp = Math.round(timeDate.getTime()/1000);
// var nowTime=Math.round(new Date(new Date().toLocaleDateString()).getTime()/1000);
// var timeDiff=timeStamp-nowTime;
// if(timeDiff>=0){
// return dateFormat("H:M:S",timeDate);
// //return beautifyTime(timeStamp,LANG);
// }else{
// return dateFormat("Y-m-d H:M:S",timeDate);
// }
return time;
},
getVersion:function(){
if(IS_TRY=="false"){
return;
}
this.$alert('當前為試用版本,請點擊底部鏈接獲取授權', '警告!', {
confirmButtonText: '確定',
});
},
selectLang:function(lang){
var url=changeURLPar(document.URL,"lang",lang);
document.location.href=url;
},
//focus事件處理
focusHandle(){
clearFlashTitle();
window.location.reload();
},
visitorPageHeight(){
//$("#chatVisitorPage").css("height","calc(100% - 121px)");
if(isWeiXin()){
$("body").css("height","100vh");
}else{
$("body").css("height",document.documentElement.clientHeight+"px");
}
//document.getElementById('chatVisitorPage').style.height = (document.documentElement.clientHeight - 71) + 'px';
},
checkDomainAuth(){
return;
var _this=this;
$.get("/other/domainAuth",{},function(data){
if(data.code=="201"){
_this.$alert( KEFU_LANG[LANG]['authLimit'],'', {
callback: function(){
window.location.reload();
}
});
}
});
},
initCallingDialog(){
this.canvasElement=$('#audioCanvas')[0];
this.videoElement=$('#chatRtc')[0];
this.talkPeer();
},
//loading關閉
loadingTimerTimeoutClose(){
var _this=this;
if(this.loadingTimer){
clearTimeout(this.loadingTimer);
this.loadingTimer=null;
}
this.loadingTimer=setTimeout(function(){
_this.loading.close();
_this.callClear();
}, 30000);
},
callClear() {
var _this=this;
_this.isCalling=false;
if(_this.loading){
_this.loading.close();
}
if(_this.call!=null){
_this.call.close();
}
if(_this.localStream){
var tracks=_this.localStream.getTracks();
for(var i=0;i<tracks.length;i++){
tracks[i].stop();
}
_this.localStream=null;
}
}
},
mounted:function() {
var _this=this;
document.addEventListener('paste', this.onPasteUpload);
document.addEventListener('scroll',this.textareaBlur);
window.addEventListener('message',function(e){
var msg=e.data;
if(msg.module&&msg.module=="locationPicker"){
_this.qqMap=false;
console.log('location', msg);
var address={
"title":msg.poiaddress,
"price":msg.poiname,
"img":"/static/images/qqmap.png",
"url":"https://apis.map.qq.com/tools/poimarker?type=0&marker=coord:"+msg.latlng.lat+","+msg.latlng.lng+"&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&referer=myapp"
};
_this.messageContent="product["+JSON.stringify(address)+"]";
_this.chatToUser();
}
if(msg.type=="inputing_message"){
_this.sendInputingStrNow(msg.content);
}
if(msg.type=="send_message"){
_this.messageContent=msg.content;
_this.chatToUser();
}
});
//監聽頁面關閉
window.onbeforeunload = function(e) {
_this.callClose();
};
},
created: function () {
this.init();
this.getUserInfo();
this.checkDomainAuth();
//加載歷史記錄
//this.msgList=this.getHistory();
//滾動底部
//this.scrollBottom();
//獲取歡迎
//this.initPeerjs();
//this.getVersion();
}
})
</script>
</html>
十年開發經驗程式員,離職全心創業中,歷時三年開發出的產品《唯一客服系統》
一款基于Golang+Vue開發的在線客服系統,軟體著作權編號:2021SR1462600,一套可私有化部署的網站在線客服系統,編譯后的二進制檔案可直接使用無需搭開發環境,下載zip解壓即可,僅依賴MySQL資料庫,是一個開箱即用的全渠道在線客服系統,致力于幫助廣大開發者/公司快速部署整合私有化客服功能, 開源地址:唯一客服(開源學習版) 官網地址:唯一客服官網
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/531851.html
標籤:JavaScript
下一篇:回圈練習
