在这个系列文章里,我尝试将自己开发唯一客服系统(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展示出来
总体来说,项目是偏向后端风格的,偏传统的架构
下面是访客端界面的代码,就可以看出这个工作量有多大~~
代码语言:javascript复制<!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 "位访客正在咨询,请稍等一会再尝试 <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 = 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;
});
},
//展示微信公众号带参二维码
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>