在这个系列文章里,我尝试将自己开发唯一客服系统(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="/static/cdn/element-ui/2.15.1/theme-chalk/index.min.css"><script src="/static/cdn/vue/2.6.11/vue.min.js"></script><script src="/static/cdn/element-ui/2.15.1/index.js"></script><script src="/static/cdn/jquery/3.6.0/jquery.min.js"></script><script src="/static/js/functions.js?v=0.6.9"></script><link rel="stylesheet" href="/static/css/common.css?v=yuyfgfgfg" /><link rel="stylesheet" href="/static/css/icono.min.css" /><link rel="icon" href="/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="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='/static/images/laba.svg'/><span><{visitorNotice}></span><img v-on:click="visitorNotice=''" src="/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="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="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="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="javascript:void(0);"><{flyLang.socketclose}></a></div><div class="hotQuestion" v-if="hotQuestion.length!=0"><aclass="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="/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=v.path></li></ul><div 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" class="visitorEditorVoice visitorFaceBtn"></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" class="visitorEditorSmile visitorFaceBtn"></div>*/}}
{{/*                    <div v-if="VisitorUploadImgBtn!='true'&&VisitorPlusBtn=='true'" class="icono-image visitorEditorImg" 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" class="visitorEditorChoose"></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="{{.CopyrightUrl}}" target="_blank">{{.CopyrightTxt}}</a></div></div></div><div class="chatArticle"><div style="padding: 8px;"><img style="width: 100%" :src="entInfo.intro_pic" v-if="entInfo.intro_pic" :title="entInfo.username"/></div><h3 class="hotQuestionTitle"><img src="/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-imagestyle="display: none;"ref="preview"class="hideImgDiv":src="imgPreviewSrc[0]":preview-src-list="imgPreviewSrc"z-index="9999"></el-image><!--评价--><el-dialogcenter: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-inputtype="textarea":rows="4"v-model="commentContent"></el-input>
{{/*                <el-tooltip content="good" placement="top">*/}}
{{/*                    <span class="icono-smile" v-on:click="sendComment('good');comment = false"></span>*/}}
{{/*                </el-tooltip>*/}}
{{/*                <el-tooltip content="normal" placement="top">*/}}
{{/*                <span class="icono-meh" v-on:click="sendComment('normal');comment = false"></span>*/}}
{{/*                </el-tooltip>*/}}
{{/*                <el-tooltip content="bad" placement="top">*/}}
{{/*                <span class="icono-frown" 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=0src="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="" 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-dialogcenter: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="/static/js/xss.js"></script>
<script src="/static/js/reconnecting-websocket.min.js"></script>
<script src="/static/js/recoder.js"></script>
<script src="/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="/static/js/chat-lang.js?v=dsdsdgf45hkjk"></script>
<script src="/static/js/chat-config.js?v=0.5.1"></script>
<script src="/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: {//初始化websocketinitConn: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 = JSON.parse(e.data);if (redata.type == "kfOnline") {let msg = redata.dataif(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.dataif(!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.dataif(!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.resultif(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();},OnError: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=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+"位访客正在咨询,请稍等一会再尝试&nbsp;<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={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=$(this).attr("href");if(href=="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 = 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 = 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=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 = nullif (items && items.length) {// 检索剪切板itemsfor (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 = 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;//初始化peerjsthis.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=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 = 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 = 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 class="wechatTip">';child += '<img style="width:100px; margin:6px;" src="'+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>

在线客服系统源码开发实战总结:需求分析及前端代码基本技术方案相关推荐

  1. 在线客服系统源码开发实战总结:动态加载js文件实现粘贴一段js的sdk代码,直接引入插件效果...

    常见的在线客服系统中,或者是统计代码中,粘贴一段js代码,就能引入某个插件的效果.这个是怎么实现的呢? 原理非常的简单: 对于不同的加载文件类型创建不同的节点,然后添加各自的属性,最后扔到head 标 ...

  2. 在线客服系统源码开发实战总结:渐变色效果的实际运用效果

    css里面有个背景色渐变色的效果,我们能拿来做什么呢 现在就演示下,我在开发此页面时所实际实现的样子 演示页面-唯一在线客服系统 实现代码很简单,效果还是很不错: background: linear ...

  3. 在线客服系统源码开发实战总结:H5 Notifications浏览器桌面通知

    在浏览器访问网站,想在浏览器最新化的情况下,也能收到右下角的消息通知 这个时候就会用到H5 Notifications 具体效果可以参照演示页面 演示页面-唯一在线客服系统 实现代码js functi ...

  4. 开源在线客服系统源码(PHP开发的网页在线客服聊天系统源码)

    开源在线客服系统源码是一个可以高度个性化定制客户支持管理系统,最初为IT支持公司开发,以管理和跟踪他们的支持案例.零售商店和业务客户.使用最新的编程语言和技术,是完全web启用.我们已经将它打包为一个 ...

  5. 最新在线客服系统源码软件代码+自动回复+管理后台

    正文: 完整标题: PHP在线客服系统源码是一款PHP开发的在线客服系统源码,网站在线客服系统,网页在线客服软件代码,免费在线客服系统源码,支持多商家多客服,客服系统源码支持二开,客服同时支持手机移动 ...

  6. 在线客服系统源码(PHP完全开源版)

    在线客服系统软件使开发和运营团队能够高速协作,因此要求源码系统能够快速响应业务变化,并快速提供出色的客户和员工服务体验. 在线客服源码演示及获取:zxkfym.top 客服沟通问题加起来会成为重大的财 ...

  7. java在线客服系统源码 springboot客服聊天源码 网页客服源码 netty通信技术,java源码

    ava在线客服系统源码 springboot客服聊天源码 网页客服源码 netty通信技术,java源码 Java在线客服系统源码 企业网站客服聊天源码 网页客服源码 开发环境:Java + Spri ...

  8. Spring boot在线客服系统源码 在线坐席对话源码

    JAVA在线客服系统源码 网站在线客服源码 网页版在线聊天源码 开发环境:Java + Spring boot + mysql + 通信技术:netty框架 后台管理 首页-工作绩效(会话.邀请.拒绝 ...

  9. 在线客服系统源码软件代码+自动回复+可生成接入+手机版管理后台

    介绍: PHP在线客服系统源码是一款PHP开发的在线客服系统源码,网站在线客服系统,网页在线客服软件代码,免费在线客服系统源码,支持多商家多客服,客服系统源码支持二开,客服同时支持手机移动端和PC网页 ...

最新文章

  1. ubuntu下网页显示乱码的解决方法
  2. 产品经理的四个重要阶段
  3. Ubuntu 18.04 安装 MySQL 5.7【解决普通用户登录、密码修改、远程访问等问题】
  4. Android之在linux环境不通过TAG快速过滤日志
  5. [傅里叶变换及其应用学习笔记] 二十四. 级联,脉冲响应
  6. Windows下pip 离线包安装
  7. spring数据持久化
  8. 【微型计算机原理与接口技术】寻址方式
  9. java 异步查询转同步多种实现方式:循环等待,CountDownLatch,Spring EventListener,超时处理和空循环性能优化...
  10. C#音视频处理开源项目收录
  11. QQ空间 自动点赞脚本
  12. YouTube-8M数据集starter code部分翻译
  13. 浙江大学2019年数学分析考研试题
  14. java 中国标准时间_JAVA 转Wed Oct 05 2016 00:00:00 GMT+0800 (中国标准时间)
  15. 国内景色最震撼的9座雪山
  16. cv面试百问day2
  17. shell双引号、单引号、反撇号的使用
  18. ubuntu下(wifi)硬件开关控制的设置
  19. 新媒体管理师详解,新媒体矩阵搭建不完全指南
  20. 【CSS】PhotoShop 切图 ② ( PhotoShop 切片选择工具 | 清除切片 | 新建基于图层的切片 | 透明背景图片切图 | 根据参考线选择切片 )

热门文章

  1. 共度鸿蒙什么意思,逐月情缘游戏-逐月情缘手游官网最新版预约 v 1.0-apk3安卓网...
  2. 总结篇:系统及应用监控的综合思路
  3. 关于360的一些理解(不是黑数字公司的)
  4. linux运维常用服务器软件整理和介绍
  5. 支持python的云虚拟主机价格_万网云虚拟主机密码怎么样,python 购买虚拟主机测评...
  6. 如何在Psim中使用C语言实现LLC闭环仿真
  7. windows的i386和amd64含义
  8. Cassandra介绍
  9. 登录失败。该登录名来自不受信任的域,不能与 Windows 身份验证一起使用。
  10. 重启计算机后ip丢失,电脑重启后网关丢失怎么办