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

  • 在线客服系统源码开发实战总结:需求分析及前端代码基本技术方案已关闭评论
  • 7 次浏览
  • A+
所属分类:Web前端
摘要

在这个系列文章里,我尝试将自己开发唯一客服系统(gofly.v1kf.com)所涉及的经验和技术点进行梳理总结。

在这个系列文章里,我尝试将自己开发唯一客服系统(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">                     <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="/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-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 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=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="" 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="/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: {             //初始化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 = 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("rn");                 messageContent=messageContent.replace("n","");                 messageContent=messageContent.replace("rn","");                 messageContent=filterXSS(messageContent);                 if(messageContent==""||messageContent=="rn"){                     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();             },             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=
; _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 = 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 = 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=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; }); }, //展示微信