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


    在这个系列文章里,我尝试将自己开发唯一客服系统(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展示出来

    总体来说,项目是偏向后端风格的,偏传统的架构

    下面是访客端界面的代码,就可以看出这个工作量有多大~~

    1. <!DOCTYPE html>
    2. <head>
    3. <meta charset="utf-8">
    4. <!--删除苹果默认的工具栏和菜单栏,默认为no显示工具栏和菜单栏。-->
    5. <meta name="apple-mobile-web-app-capable" content="yes"/>
    6. <!--QQ强制全屏-->
    7. <meta name="x5-fullscreen" content="true">
    8. <!--UC强制全屏-->
    9. <meta name="fullscreen" content="yes">
    10. <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" name="viewport" />
    11. <title>{{.Title}}</title>
    12. <link rel="stylesheet" href="/static/cdn/element-ui/2.15.1/theme-chalk/index.min.css">
    13. <script src="/static/cdn/vue/2.6.11/vue.min.js"></script>
    14. <script src="/static/cdn/element-ui/2.15.1/index.js"></script>
    15. <script src="/static/cdn/jquery/3.6.0/jquery.min.js"></script>
    16. <script src="/static/js/functions.js?v=0.6.9"></script>
    17. <link rel="stylesheet" href="/static/css/common.css?v=yuyfgfgfg" />
    18. <link rel="stylesheet" href="/static/css/icono.min.css" />
    19. <link rel="icon" href="/static/images/favicon.ico">
    20. <style>
    21. .el-message-box{
    22. width: auto;
    23. max-width: 100%;
    24. max-height: 100%;
    25. overflow: auto;
    26. }
    27. </style>
    28. </head>
    29. <body class="visitorBody">
    30. <div id="app" class="chatCenter">
    31. <template>
    32. <!--客服代码-->
    33. <div class="chatEntTitle" v-show="!isIframe">
    34. <el-badge :type="onlineType" is-dot class="item">
    35. <el-avatar class="chatEntTitleLogo" :size="35" :src="noticeAvatar"></el-avatar>
    36. </el-badge>
    37. <div>
    38. <div><{chatTitle}></div>
    39. <div class="entIntro" v-show="entIntroduce!=''"><{entIntroduce}></div>
    40. </div>
    41. </div>
    42. <div class="chatEntBox">
    43. <!--公告栏-->
    44. <div v-show="visitorNotice!=''"
    45. class="visitorNotice"
    46. >
    47. <img src='/static/images/laba.svg'/>
    48. <span><{visitorNotice}></span>
    49. <img v-on:click="visitorNotice=''" src="/static/images/cha.png" class="visitorNoticeClose"/>
    50. </div>
    51. <!--//公告栏-->
    52. <div ref="chatVisitorPage" id="chatVisitorPage" class="chatContext chatVisitorPage" v-on:click="showIconBtns=false;showFaceIcon=false">
    53. <div class="chatBox">
    54. <div class="chatNotice" v-on:click="loadMoreMessages" v-show="showLoadMore">
    55. <a class="chatNoticeContent"><{flyLang.moremessage}></a>
    56. </div>
    57. <el-row :gutter="2" v-for="v in msgList" v-bind:class="{'chatBoxMe': v.is_kefu==true}">
    58. <div class="messageBox questionBox" v-if="v.type=='question'">
    59. <div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
    60. <div class="left" v-if="v.is_kefu!=true" style="display: flex;">
    61. <el-avatar style="margin-right:10px;flex-shrink: 0;" :size="36" :src="v.avator"></el-avatar>
    62. <div class="chatMsgContent">
    63. <div class="chatUser" v-if="showKefuName!='off'"><{v.name}></div>
    64. <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
    65. </div>
    66. </div>
    67. </div>
    68. <!--猜你想问-->
    69. <div class="cardBox" v-else-if="v.type=='card'">
    70. <div class='visitorReplyTitle'><{flyLang.guess}><a><i class='el-icon-refresh-left'></i> <{flyLang.huanyihuan}></a></div>
    71. <div class="cardBoxContent" v-html="v.content"></div>
    72. </div>
    73. <!--//猜你想问-->
    74. <!--消息模板-->
    75. <div class="messageBox" v-else>
    76. <div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
    77. <div class="left" v-if="v.is_kefu!=true" style="display: flex;">
    78. <el-avatar style="margin-right:10px;flex-shrink: 0;" :size="36" :src="v.avator"></el-avatar>
    79. <div class="chatMsgContent">
    80. <div class="chatUser" v-if="showKefuName!='off'"><{v.name}></div>
    81. <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
    82. </div>
    83. </div>
    84. <div class="kefuMe right" v-if="v.is_kefu==true" style="display: flex;justify-content: flex-end;">
    85. <div>
    86. <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
    87. <div class="chatReadStatus" v-show="VisitorReadStatus!='true'"><{v.read_status}></div>
    88. </div>
    89. <el-avatar v-if="VisitorShowAvator=='true'" style="margin-left:10px;flex-shrink: 0;" :size="36" :src="v.avator"></el-avatar>
    90. </div>
    91. <div class="clear"></div>
    92. </div>
    93. <!--//消息模板-->
    94. </el-row>
    95. </div>
    96. </div>
    97. <div class="chatBoxSend">
    98. <div class="chatBoxSendMask" v-if="reconnectDialog">
    99. <a @click="initConn" href="javascript:void(0);"><{flyLang.socketclose}></a>
    100. </div>
    101. <div class="hotQuestion" v-if="hotQuestion.length!=0">
    102. <a
    103. class="slideInRightItem"
    104. v-for="item in hotQuestion"
    105. v-on:click="messageContent=item;chatToUser()">
    106. <{item}>
    107. </a>
    108. </div>
    109. <!--进度条-->
    110. <div class="progressLine">
    111. <el-progress :stroke-width="6" :percentage="percentage" v-show="percentage!=0" :text-inside="true"></el-progress>
    112. </div>
    113. <!--//进度条-->
    114. <div class="iconBtns visitorIconBox">
    115. <el-tooltip :content="flyLang.emotions" placement="top">
    116. <div class="icono-smile visitorIconBtns visitorFaceBtn" v-on:click="showFaceIcon==true?showFaceIcon=false:showFaceIcon=true"></div>
    117. </el-tooltip>
    118. <el-tooltip :content="flyLang.photo" placement="top">
    119. <div v-show="VisitorUploadImgBtn!='true'" :title="flyLang.photo" class="el-icon-picture" id="uploadImg" v-on:click="uploadImg('/uploadimg')" style="font-size: 22px;"></div>
    120. </el-tooltip>
    121. <el-tooltip :content="flyLang.file" placement="top">
    122. <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>
    123. </el-tooltip>
    124. <el-tooltip :content="flyLang.recoder" placement="top">
    125. <div v-show="VisitorVoiceBtn!='true'" :title="flyLang.recoder" class="el-icon-microphone" v-on:click="audioDialog=true" style="font-size: 22px;"></div>
    126. </el-tooltip>
    127. <el-tooltip :content="flyLang.map" placement="top">
    128. <div v-show="VisitorMapBtn!='true'" style="font-size: 22px;" class="el-icon-location" v-on:click="qqMap==true?qqMap=false:qqMap=true;"></div>
    129. </el-tooltip>
    130. <el-tooltip :content="flyLang.audio" placement="top">
    131. <div class="el-icon-phone-outline" @click="callPhone()" style="font-size: 20px;">
    132. </div>
    133. </el-tooltip>
    134. <el-tooltip :content="flyLang.video" placement="top">
    135. <div class="el-icon-video-camera" @click="callPeer()" style="font-size: 22px;">
    136. </div>
    137. </el-tooltip>
    138. <el-tooltip :content="flyLang.language" placement="top">
    139. <div @click="flagsDialog='true'">
    140. <img src="/static/images/lang.png" style="width: 20px;"/>
    141. </div>
    142. </el-tooltip>
    143. </div>
    144. <div class="faceBox visitorFaceBox" v-if="showFaceIcon">
    145. <ul class="faceBoxList">
    146. <li v-on:click="faceIconClick(i)" class="faceIcon" v-for="(v,i) in face" :title="v.name"><img :src=v.path></li>
    147. </ul>
    148. <div class="clear"></div>
    149. </div>
    150. <!--搜索建议-->
    151. <div class="searchList" v-show="searchList.length!=0">
    152. <div v-on:click="messageContent=item.title;chatToUser();searchList=[]" class="searchItem" v-for="item in searchList" v-html="item.htmlTitle"></div>
    153. </div>
    154. <!--//搜索建议-->
    155. <div class="visitorEditor">
    156. {{/* <div v-if="VisitorVoiceBtn!='true'" v-on:click="audioDialog==true?audioDialog=false:audioDialog=true" class="visitorEditorVoice visitorFaceBtn"></div>*/}}
    157. <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">
    158. </el-input>
    159. {{/* <div v-if="VisitorFaceBtn!='true'" :title="flyLang.emotions" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" class="visitorEditorSmile visitorFaceBtn"></div>*/}}
    160. {{/* <div v-if="VisitorUploadImgBtn!='true'&&VisitorPlusBtn=='true'" class="icono-image visitorEditorImg" id="uploadImg" v-on:click="uploadImg('/uploadimg')"></div>*/}}
    161. {{/* <div v-if="VisitorPlusBtn!='true'" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" v-show="messageContent==''" :title="flyLang.emotions" class="visitorEditorChoose"></div>*/}}
    162. </div>
    163. <el-button type="primary" size="mini" class="visitorEditorBtn" :disabled="sendDisabled||messageContent==''" v-on:click="chatToUser();showIconBtns=false"><{flyLang.sent}></el-button>
    164. <div class="footContact clear">
    165. <a href="{{.CopyrightUrl}}" target="_blank">{{.CopyrightTxt}}</a>
    166. </div>
    167. </div>
    168. </div>
    169. <div class="chatArticle">
    170. <div style="padding: 8px;"><img style="width: 100%" :src="entInfo.intro_pic" v-if="entInfo.intro_pic" :title="entInfo.username"/></div>
    171. <h3 class="hotQuestionTitle">
    172. <img src="/static/images/fire.svg" class="fire"/><{flyLang.hotQuestionTitle}>
    173. </h3>
    174. <ul>
    175. <li v-on:click="messageContent=item;chatToUser()" class="chatArticleItem" v-for="item in topQuestionList"><a><{item}></a></li>
    176. </ul>
    177. </div>
    178. <div class="clear"></div>
    179. <!--//客服代码-->
    180. <audio id="chatMessageAudio">
    181. <source id="chatMessageAudioSource" />
    182. </audio>
    183. <audio id="chatMessageSendAudio">
    184. <source id="chatMessageSendAudioSource" />
    185. </audio>
    186. <!--图片预览-->
    187. <el-image
    188. style="display: none;"
    189. ref="preview"
    190. class="hideImgDiv"
    191. :src="imgPreviewSrc[0]"
    192. :preview-src-list="imgPreviewSrc"
    193. z-index="9999"
    194. ></el-image>
    195. <!--评价-->
    196. <el-dialog
    197. center
    198. :title="flyLang.visitorCommentTitle"
    199. :close-on-click-modal="false"
    200. width="90%"
    201. :visible.sync="comment"
    202. >
    203. <div class="commentBox">
    204. <div style="line-height: 25px;"><{flyLang.commentDesc}></div>
    205. <el-rate v-model="commentScore" style="margin-bottom: 30px;"></el-rate>
    206. <el-input
    207. type="textarea"
    208. :rows="4"
    209. v-model="commentContent">
    210. </el-input>
    211. {{/* <el-tooltip content="good" placement="top">*/}}
    212. {{/* <span class="icono-smile" v-on:click="sendComment('good');comment = false"></span>*/}}
    213. {{/* </el-tooltip>*/}}
    214. {{/* <el-tooltip content="normal" placement="top">*/}}
    215. {{/* <span class="icono-meh" v-on:click="sendComment('normal');comment = false"></span>*/}}
    216. {{/* </el-tooltip>*/}}
    217. {{/* <el-tooltip content="bad" placement="top">*/}}
    218. {{/* <span class="icono-frown" v-on:click="sendComment('bad');comment = false"></span>*/}}
    219. {{/* </el-tooltip>*/}}
    220. </div>
    221. <span slot="footer" class="dialog-footer">
    222. <el-button type="primary" v-on:click="sendComment();comment = false"><{flyLang.sent}></el-button>
    223. </span>
    224. </el-dialog>
    225. <!--//评价-->
    226. <!--地图-->
    227. <iframe v-if="qqMap" style="position: fixed;top: 0;left: 0;z-index: 999999999" id="mapPage" width="100%" height="100%" frameborder=0
    228. src="https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=
    229. OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&referer=kefu">
    230. </iframe>
    231. <!--//地图-->
    232. <el-dialog
    233. :title="flyLang.leave"
    234. :visible.sync="allOffline"
    235. width="100%"
    236. top="0">
    237. <el-input style="margin-bottom: 10px;" :placeholder="flyLang.email" v-model="visitorContact.email"></el-input>
    238. <el-input style="margin-bottom: 10px;" :placeholder="flyLang.wechat" v-model="visitorContact.weixin"></el-input>
    239. <el-input style="margin-bottom: 10px;" :placeholder="flyLang.realname" v-model="visitorContact.name"></el-input>
    240. <el-input :placeholder="flyLang.content" type="textarea" v-model="visitorContact.msg"></el-input>
    241. <span slot="footer" class="dialog-footer">
    242. <el-button @click="sendEmailMsg"><{flyLang.sent}></el-button>
    243. <el-button @click="allOffline = false"><{flyLang.cancel}></el-button>
    244. </span>
    245. </el-dialog>
    246. <!--录音-->
    247. <el-dialog
    248. :visible.sync="audioDialog"
    249. width="100%"
    250. >
    251. <div class="dialogRecoder">
    252. <el-progress :color="colors" type="dashboard" :format="recoderFormat" :stroke-width="10" :percentage="recoderSecond"></el-progress>
    253. <br/>
    254. <audio v-show="recorderEnd" controls ref="audio" muted="muted" src="" id="audio"></audio>
    255. <br/>
    256. <el-button id="start" @click="startRecoder($event)" size="small" type="primary"><{flyLang.start}></el-button>
    257. <el-button @click="stopRecoder($event)" size="small" type="warning"><{flyLang.stop}></el-button>
    258. <el-button @click="cancelRecoder()" size="small" type="danger"><{flyLang.cancel}></el-button>
    259. <el-button @click="sendRecoder()" size="small" type="success"><{flyLang.sent}></el-button>
    260. </div>
    261. </el-dialog>
    262. <!--//录音-->
    263. <!--切换语言-->
    264. <el-dialog
    265. :visible.sync="flagsDialog"
    266. width="90%"
    267. top="30px">
    268. <el-button @click="selectLang('cn')" class="flagBtn" type="primary" plain>中文简体</el-button>
    269. <el-button @click="selectLang('tw')" class="flagBtn" type="primary" plain>中文繁体</el-button>
    270. <el-button @click="selectLang('en')" class="flagBtn" type="primary" plain>English</el-button>
    271. </el-dialog>
    272. <!--//切换语言-->
    273. <!--录音-->
    274. <!--视频-->
    275. <el-dialog
    276. center
    277. :close-on-click-modal="false"
    278. :visible="isCalling"
    279. width="90%"
    280. :show-close="false"
    281. @opened="initCallingDialog"
    282. >
    283. <video id="chatRtc" style="width: 100%;" controls autoplay></video>
    284. <video v-if="isVideo==true" id="chatLocalRtc" style="width: 100%;" controls muted></video>
    285. <span slot="footer" class="dialog-footer">
    286. <el-button @click="callClose()" type="danger" size="mini">挂断</el-button>
    287. </span>
    288. </el-dialog>
    289. <!--//录音-->
    290. </template>
    291. </div>
    292. </body>
    293. <script src="/static/js/xss.js"></script>
    294. <script src="/static/js/reconnecting-websocket.min.js"></script>
    295. <script src="/static/js/recoder.js"></script>
    296. <script src="/static/js/audio.js"></script>
    297. <script>
    298. var KEFU_ID='{{.KEFU_ID}}';
    299. var REFER=urlDecode('{{.Refer}}');
    300. var REFER_URL=urlDecode('{{.ReferUrl}}');
    301. var ENT_ID='{{.ENT_ID}}';
    302. var IS_TRY='{{.IS_TRY}}';
    303. var VISITOR_ID='{{.visitorId}}';
    304. var VISITOR_NAME='{{.visitorName}}';
    305. var ERR_MSG='{{.errMsg}}';
    306. var AVATOR='{{.avator}}';
    307. var LANG=checkLang();
    308. var SHOW_KEFU_NAME='{{.ShowKefuName}}';
    309. var EXTRA='{{.Extra}}';
    310. </script>
    311. <script src="/static/js/chat-lang.js?v=dsdsdgf45hkjk"></script>
    312. <script src="/static/js/chat-config.js?v=0.5.1"></script>
    313. <script src="/static/js/peer.js"></script>
    314. <script>
    315. new Vue({
    316. el: '#app',
    317. delimiters:["<{","}>"],
    318. data: {
    319. window:window,
    320. server:getWsBaseUrl()+"/ws_visitor",
    321. socket:null,
    322. msgList:[],
    323. imgPreviewSrc:[
    324. ""],
    325. msgListNum:[],
    326. messageContent:"",
    327. chatTitle:KEFU_LANG[LANG]['connecting'],
    328. visitor:{},
    329. face:emojiGifsMap(),
    330. showKfonline:false,
    331. socketClosed:false,
    332. focusSendConn:false,
    333. wsSocketClosed:true,
    334. timer:null,
    335. loadingTimer:null,
    336. sendDisabled:false,
    337. entLogo:"",
    338. entName:"",
    339. peer:null,
    340. peerjsId:"",
    341. kefuPeerId:"",
    342. loading:null,
    343. localStream:null,
    344. flyLang:KEFU_LANG[LANG],
    345. lang:LANG,
    346. textareaFocused:false,
    347. replys:[],
    348. noticeName:"",
    349. noticeAvatar:"",
    350. allOffline:false,
    351. visitorContact:{
    352. email:"",
    353. weixin:"",
    354. name:"",
    355. msg:"",
    356. },
    357. haveUnreadMessage:false,
    358. audioDialog:false,
    359. flagsDialog:false,
    360. recorder:null,
    361. recorderAudio:null,
    362. recordeTimer:null,
    363. recoderSecond:0,
    364. currentActiveTime:Date.now(),
    365. timeoutTimer:null,
    366. timeoutLongTime:20*60*1000,//20分钟没反应
    367. allTimeouter:[],
    368. currentPage:1,
    369. showLoadMore:false,
    370. loadMoreDisable:false,
    371. websocketOpenNum:0,//websocket打开次数
    372. websocketMaxOpenNum:10,//websocket最大打开次数
    373. talkBtnText:"按住 说话",
    374. recorderEnd:false,
    375. isMobile:false,
    376. isIframe:false,
    377. onlineType:"success",
    378. reconnectDialog:false,
    379. showIconBtns:false,
    380. showFaceIcon:false,
    381. showKefuName:SHOW_KEFU_NAME,
    382. comment:false,
    383. qqMap:false,
    384. hotQuestion:[],
    385. topQuestionList:[],
    386. topQuestionCount:0,
    387. topQuestionPage:1,
    388. topQuestionPagesize:5,
    389. entIntroduce:"",
    390. robotSwitch:"",
    391. robotNoAnswer:"",
    392. visitorNotice:"",
    393. autoWelcome:"",
    394. searchList:[],
    395. VisitorVoiceBtn:'{{.VisitorVoiceBtn}}',
    396. VisitorMapBtn:'{{.VisitorMapBtn}}',
    397. VisitorCommentBtn:'{{.VisitorCommentBtn}}',
    398. VisitorFaceBtn:'{{.VisitorFaceBtn}}',
    399. VisitorReadStatus:'{{.VisitorReadStatus}}',
    400. VisitorPlusBtn:'{{.VisitorPlusBtn}}',
    401. VisitorUploadImgBtn:'{{.VisitorUploadImgBtn}}',
    402. VisitorUploadFileBtn:'{{.VisitorUploadFileBtn}}',
    403. VisitorMaxLength:'{{.VisitorMaxLength}}'==''?100:parseInt('{{.VisitorMaxLength}}'),
    404. VisitorShowAvator:'{{.VisitorShowAvator}}',
    405. VisitorWechatQrcodeUrl:'{{.VisitorWechatQrcodeUrl}}',
    406. percentage:0,
    407. visitorMaxNumLimit:false,//客服达到接待上限
    408. visitorMaxNumNotice:"",//客服达到接待上限文案
    409. visitorCookie:"",
    410. scanWechatQrcode:"",
    411. entConfig:{},
    412. colors: [
    413. {color: '#f56c6c', percentage: 20},
    414. {color: '#e6a23c', percentage: 40},
    415. {color: '#5cb87a', percentage: 60},
    416. {color: '#1989fa', percentage: 80},
    417. {color: '#6f7ad3', percentage: 100}
    418. ],
    419. commentScore:0,
    420. commentContent:"",
    421. isCalling:false,
    422. call:null,
    423. videoElement:null,
    424. canvasElement:null,
    425. isVideo:false,
    426. entInfo:{},
    427. },
    428. methods: {
    429. //初始化websocket
    430. initConn:function() {
    431. this.socket = new ReconnectingWebSocket(this.server+"?visitor_id="+this.visitor.visitor_id+"&to_id="+this.visitor.to_id);//创建Socket实例
    432. this.socket.debug = true;
    433. this.socket.onmessage = this.OnMessage;
    434. this.socket.onopen = this.OnOpen;
    435. this.socket.onerror = this.OnError;
    436. this.socket.onclose = this.OnClose;
    437. this.ping();
    438. },
    439. OnOpen:function() {
    440. console.log("ws:onopen");
    441. //限制最大打开次数
    442. if(this.websocketOpenNum>=this.websocketMaxOpenNum){
    443. this.chatTitle=KEFU_LANG[LANG]['refresh'];
    444. this.socket.close();
    445. return;
    446. }
    447. this.websocketOpenNum++;
    448. this.chatTitle=this.noticeName;
    449. this.checkTimeout();
    450. this.socketClosed=false;
    451. this.focusSendConn=false;
    452. this.wsSocketClosed=false;
    453. this.sendVisitorLogin();
    454. this.getExtendInfo();
    455. this.reconnectDialog=false;
    456. this.showTitle(KEFU_LANG[LANG]['connectok']);
    457. this.getNotice();
    458. },
    459. OnMessage:function(e) {
    460. console.log("ws:onmessage");
    461. this.socketClosed=false;
    462. this.focusSendConn=false;
    463. const redata = JSON.parse(e.data);
    464. if (redata.type == "kfOnline") {
    465. let msg = redata.data
    466. if(this.showKfonline && this.visitor.to_id==msg.id){
    467. return;
    468. }
    469. this.visitor.to_id=msg.id;
    470. this.chatTitle=msg.name+","+KEFU_LANG[LANG]['chating'];
    471. $(".chatBox").append("
      chatTime\">"+this.chatTitle+"
      "
      );
    472. this.scrollBottom();
    473. this.showKfonline=true;
    474. }
    475. if (redata.type == "transfer") {
    476. var kefuId = redata.data
    477. if(!kefuId){
    478. return;
    479. }
    480. this.visitor.to_id=kefuId;
    481. }
    482. if (redata.type == "comment") {
    483. this.comment=true;
    484. }
    485. if (redata.type == "wechat_notice") {
    486. this.showTitle(KEFU_LANG[LANG]['wechatNotice']);
    487. }
    488. if (redata.type == "notice") {
    489. let msg = redata.data
    490. if(!msg){
    491. return;
    492. }
    493. this.chatTitle=msg
    494. $(".chatBox").append("
      chatTime\">"+this.chatTitle+"
      "
      );
    495. this.scrollBottom();
    496. }
    497. if (redata.type == "accept") {
    498. var _this=this;
    499. let msg = redata.data;
    500. if(!msg||_this.localStream==null){
    501. return;
    502. }
    503. // this.$confirm('请求与您通话?', '提示', {
    504. // confirmButtonText: '确定',
    505. // cancelButtonText: '取消',
    506. // type: 'warning'
    507. // }).then(() => {
    508. _this.kefuPeerId=msg;
    509. _this.isCalling=true;
    510. // }).catch(() => {
    511. // });
    512. }
    513. if (redata.type == "callPhone") {
    514. this.callPhone();
    515. }
    516. if (redata.type == "callVideo") {
    517. this.callPeer();
    518. }
    519. if (redata.type == "refuse") {
    520. this.$message({
    521. message: "已挂断",
    522. type: 'error'
    523. });
    524. this.callClear();
    525. return;
    526. }
    527. if (redata.type == "delete") {
    528. var msg = redata.data;
    529. for(var i=0;i<this.msgList.length;i++){
    530. if(this.msgList[i].msg_id==msg.msg_id){
    531. this.msgList.splice(i,1);
    532. break;
    533. }
    534. }
    535. }
    536. if (redata.type == "read") {
    537. var msg = redata.data;
    538. for(var i=0;i<this.msgList.length;i++){
    539. this.msgList[i].read_status=KEFU_LANG[LANG]['read'];
    540. }
    541. }
    542. if (redata.type == "message") {
    543. let msg = redata.data
    544. //this.visitor.to_id=msg.id;
    545. var _this=this;
    546. var msgArr=msg.content.split("[br]");
    547. for(var i in msgArr){
    548. let content = {}
    549. content.avator = msg.avator;
    550. content.name = msg.name;
    551. content.content =replaceSpecialTag(msgArr[i]);
    552. content.is_kefu = false;
    553. content.time = shortTime(msg.time);
    554. content.is_reply=true;
    555. content.msg_id = msg.msg_id;
    556. this.msgList.push(content);
    557. setTimeout(function () {
    558. _this.scrollBottom();
    559. },200);
    560. }
    561. // let content = {}
    562. // content.avator = msg.avator;
    563. // content.name = msg.name;
    564. // content.content =replaceSpecialTag(msg.content);
    565. // content.is_kefu = false;
    566. // content.time = msg.time;
    567. // content.msg_id = msg.msg_id;
    568. // this.msgList.push(content);
    569. notify(msg.name, {
    570. body: msg.content,
    571. icon: msg.avator
    572. },function(notification) {
    573. window.focus();
    574. notification.close();
    575. });
    576. //this.scrollBottom();
    577. flashTitle();//标题闪烁
    578. //clearInterval(this.timer);
    579. this.cleanAllTimeout();
    580. this.alertSound('/static/images/alert4.mp3');//提示音
    581. this.haveUnreadMessage=true;
    582. }
    583. if (redata.type == "close") {
    584. this.showTitle(KEFU_LANG[LANG]['closemes']);
    585. this.scrollBottom();
    586. this.socket.close();
    587. //this.socketClosed=true;
    588. this.focusSendConn=true;
    589. this.reconnectDialog=true;
    590. }
    591. if (redata.type == "force_close") {
    592. this.showTitle(KEFU_LANG[LANG]['forceclosemes']);
    593. this.scrollBottom();
    594. this.socket.close();
    595. this.socketClosed=true;
    596. this.reconnectDialog=true;
    597. }
    598. if (redata.type == "auto_close") {
    599. this.showTitle(KEFU_LANG[LANG]['autoclosemes']);
    600. this.scrollBottom();
    601. this.socket.close();
    602. this.socketClosed=true;
    603. this.reconnectDialog=true;
    604. }
    605. if (redata.type == "change_id") {
    606. var openId = redata.data;
    607. setFakeCookie("visitor_"+ENT_ID,openId,this.visitorCookie);
    608. location.reload();
    609. }
    610. window.parent.postMessage(redata,"*");
    611. },
    612. //发送给客户
    613. chatToUser:function() {
    614. this.searchList=[];
    615. if(this.sendDisabled){
    616. return;
    617. }
    618. var messageContent=this.messageContent.trim("\r\n");
    619. messageContent=messageContent.replace("\n","");
    620. messageContent=messageContent.replace("\r\n","");
    621. messageContent=filterXSS(messageContent);
    622. if(messageContent==""||messageContent=="\r\n"){
    623. this.messageContent="";
    624. return;
    625. }
    626. this.messageContent="";
    627. this.currentActiveTime=Date.now();
    628. if(this.socketClosed){
    629. this.initConn();
    630. // this.$message({
    631. // message: '连接关闭!请重新打开页面',
    632. // type: 'warning'
    633. // });
    634. //return;
    635. }
    636. this.sendDisabled=true;
    637. let _this=this;
    638. let content = {}
    639. content.avator=_this.visitor.avator;
    640. content.content = replaceSpecialTag(messageContent);
    641. content.name = _this.visitor.name;
    642. content.is_kefu = true;
    643. content.time = _this.getNowDate();
    644. content.show_time=false;
    645. let mes = {};
    646. mes.type = "visitor";
    647. mes.content = messageContent;
    648. mes.from_id = this.visitor.visitor_id;
    649. mes.to_id = this.visitor.to_id;
    650. //机器人回答
    651. if(this.robotSwitch=="true"){
    652. this.sendDisabled=false;
    653. this.messageContent="";
    654. //转接人工,没用处于排队状态
    655. if(this.turnToMan && this.turnToMan.includes(mes.content)){
    656. if(this.visitorMaxNumLimit){
    657. this.showTitle(this.visitorMaxNumNotice);
    658. return;
    659. }
    660. this.initConn();
    661. this.robotSwitch="";
    662. }else{
    663. content.read_status = KEFU_LANG[LANG]['read'];
    664. _this.msgList.push(content);
    665. _this.scrollBottom();
    666. _this.sendAjax("/2/robotMessage","post",{ent_id:ENT_ID,content:messageContent},function(msg){
    667. if(msg.content==""){
    668. msg.content=_this.robotNoAnswer;
    669. }
    670. if(msg.content==""){
    671. return;
    672. }
    673. let content = {}
    674. content.avator=msg.avator;
    675. content.content = replaceSpecialTag(msg.content);
    676. content.name = msg.username;
    677. content.is_kefu = false;
    678. content.read_status = KEFU_LANG[LANG]['read'];
    679. content.time = _this.getNowDate();
    680. content.show_time=false;
    681. _this.msgList.push(content);
    682. _this.scrollBottom();
    683. });
    684. return;
    685. }
    686. }
    687. content.read_status = KEFU_LANG[LANG]['unread'];
    688. _this.msgList.push(content);
    689. _this.scrollBottom();
    690. //发送人工消息
    691. $.post("/2/message?lang="+getQuery("lang"),mes,function(res){
    692. _this.sendDisabled=false;
    693. if(res.code!=200){
    694. _this.$message({
    695. message: res.msg,
    696. type: 'error'
    697. });
    698. if(res.code==401){
    699. setTimeout(function(){
    700. window.location.reload();
    701. },2000);
    702. }
    703. return;
    704. }
    705. var result=res.result
    706. if(result.isBlack){
    707. _this.msgList.pop();
    708. content.content=result.content;
    709. _this.msgList.push(content);
    710. }
    711. _this.messageContent = "";
    712. _this.cleanAllTimeout();
    713. _this.sendSound();
    714. _this.sendDisabled=false;
    715. });
    716. },
    717. //正在输入
    718. inputNextText:function(){
    719. var _this=this;
    720. this.sendInputingStrNow(this.messageContent);
    721. //是否进行搜索
    722. if(this.robotSwitch!="true"){
    723. return;
    724. }
    725. this.sendAjax("/2/searchQuestion","get",{ent_id:ENT_ID,content:this.messageContent},function(res){
    726. result=res.result;
    727. if(!result){
    728. return;
    729. }
    730. for(key in result){
    731. var str=result[key].title;
    732. str= str.replace(_this.messageContent,""+_this.messageContent+"");
    733. result[key].htmlTitle=str;
    734. }
    735. _this.searchList=result;
    736. });
    737. },
    738. sendInputingStrNow:function(str){
    739. if(this.socketClosed||!this.socket||this.wsSocketClosed){
    740. return;
    741. }
    742. var message = {}
    743. message.type = "inputing";
    744. message.data = {
    745. from : this.visitor.visitor_id,
    746. to : this.visitor.to_id,
    747. content:str
    748. };
    749. this.socket.send(JSON.stringify(message));
    750. },
    751. sendVisitorLogin:function(){
    752. var _this=this;
    753. setTimeout(function(){
    754. if(_this.socketClosed||!_this.socket||_this.wsSocketClosed){
    755. return;
    756. }
    757. var message = {}
    758. message.type = "visitor_login";
    759. message.data = {
    760. from : _this.visitor.visitor_id,
    761. to : _this.visitor.to_id,
    762. };
    763. _this.socket.send(JSON.stringify(message));
    764. }, 3000);
    765. },
    766. OnClose:function(event) {
    767. console.log("ws:onclose",event);
    768. this.focusSendConn=true;
    769. this.wsSocketClosed=true;
    770. this.closeTimeoutTimer();
    771. },
    772. OnError:function(event) {
    773. console.log("ws:onerror",event);
    774. this.closeTimeoutTimer();
    775. },
    776. //获取当前用户信息
    777. getUserInfo:function(){
    778. var _this=this;
    779. var visitor_id=getFakeCookie("visitor_"+ENT_ID);
    780. var to_id=KEFU_ID;
    781. var extra=EXTRA;
    782. var url=getQuery("url");
    783. var paramVisitorId=VISITOR_ID;
    784. if(paramVisitorId!=""){
    785. visitor_id=paramVisitorId;
    786. }
    787. var visitorName=VISITOR_NAME;
    788. var avator=AVATOR;
    789. if(extra==""){
    790. var ext={};
    791. var refer=document.referrer?document.referrer:"-";
    792. ext.refer=refer;
    793. ext.host=document.location.href;
    794. extra=utf8ToB64(JSON.stringify(ext));
    795. }else{
    796. try{
    797. var jsonStr=b64ToUtf8(extra)
    798. var extJson=JSON.parse(jsonStr)
    799. if(extJson.refer){
    800. if(REFER=="") REFER=extJson.refer;
    801. if(REFER_URL=="") REFER_URL=extJson.refer;
    802. }
    803. }catch (e) {}
    804. }
    805. if(REFER_URL==""){
    806. REFER_URL=document.referrer;
    807. }
    808. if(REFER==""){
    809. REFER=document.title;
    810. }
    811. //发送消息
    812. $.ajax({
    813. type: "post",
    814. url: "/visitor_login",
    815. data:{visitor_id:visitor_id,
    816. visitor_name:visitorName,
    817. avator:avator,
    818. refer:REFER,
    819. to_id:to_id,
    820. extra:extra,
    821. ent_id:ENT_ID,
    822. url:document.location.href,
    823. refer_url:REFER_URL,
    824. title:document.title
    825. },
    826. error:function(res){
    827. var data=JSON.parse(res.responseText);
    828. _this.$message({
    829. message: data.msg,
    830. type: 'error'
    831. });
    832. },
    833. success: function(res) {
    834. if(res.code==40012){
    835. _this.$message({
    836. message: res.msg,
    837. type: 'error'
    838. });
    839. _this.chatTitle=res.msg;
    840. _this.sendDisabled=true;
    841. return;
    842. }
    843. if(res.code==40016){
    844. _this.$message({
    845. message: KEFU_LANG[LANG]['freqLimit'],
    846. type: 'error'
    847. });
    848. _this.chatTitle=KEFU_LANG[LANG]['freqLimit'];
    849. _this.sendDisabled=true;
    850. return;
    851. }
    852. _this.entInfo=res.kefu;
    853. _this.noticeName=res.kefu.username;
    854. _this.noticeAvatar=res.kefu.avatar;
    855. _this.robotNoAnswer=res.robotNoAnswer;
    856. _this.getTopQuestion();
    857. //判断同时接待访客数
    858. if(res.code==40018){
    859. _this.visitorMaxNumLimit=true;
    860. _this.visitor=res.result;
    861. _this.robotSwitch="true";
    862. _this.chatTitle=_this.noticeName;
    863. _this.turnToMan=res.turnToMan.split(",");
    864. var visitorMaxNumNotice=res.visitorMaxNumNotice;
    865. if(visitorMaxNumNotice==""){
    866. visitorMaxNumNotice="当前有"+res.visitorMaxNum+"位访客正在咨询,请稍等一会再尝试 刷新";
    867. }
    868. _this.visitorMaxNumNotice=visitorMaxNumNotice;
    869. _this.showTitle(visitorMaxNumNotice);
    870. _this.sendDisabled=true;
    871. return;
    872. }
    873. if(res.code!=200){
    874. _this.$message({
    875. message: res.msg,
    876. type: 'error'
    877. });
    878. _this.chatTitle=res.msg;
    879. _this.sendDisabled=true;
    880. return;
    881. }
    882. if(res.alloffline){
    883. _this.onlineType="danger";
    884. }else{
    885. _this.onlineType="success";
    886. }
    887. if(KEFU_CONFIG.SHOW_OFFLINE_PAGE){
    888. _this.allOffline=res.alloffline;
    889. }
    890. _this.sendDisabled=false;
    891. _this.visitor=res.result;
    892. _this.noticeName=res.kefu.username;
    893. _this.noticeAvatar=res.kefu.avatar;
    894. _this.entIntroduce=res.entIntroduce;
    895. _this.robotSwitch=res.robotSwitch;
    896. _this.turnToMan=res.turnToMan.split(",");
    897. _this.chatTitle=_this.noticeName;
    898. _this.visitorNotice=res.visitorNotice;
    899. _this.autoWelcome=res.autoWelcome;
    900. _this.visitorCookie=res.visitorCookie;
    901. _this.scanWechatQrcode=res.scanWechatQrcode;
    902. document.title=res.kefu.username;
    903. if(!getFakeCookie("visitor_"+ENT_ID)){
    904. setFakeCookie("visitor_"+ENT_ID,_this.visitor.visitor_id,res.visitorCookie);
    905. }
    906. _this.loadMoreMessages();
    907. _this.showWechatTip();
    908. if(_this.robotSwitch!="true"){
    909. _this.initConn();
    910. }
    911. }
    912. });
    913. },
    914. //获取信息列表
    915. getMesssagesByVisitorId:function(isAll){
    916. let _this=this;
    917. $.ajax({
    918. type:"get",
    919. url:"/2/messages?visitor_id="+this.visitor.visitor_id,
    920. success: function(data) {
    921. if(data.code==200 && data.result!=null&&data.result.length!=0){
    922. let msgList=data.result;
    923. _this.msgList=[];
    924. for(var i=0;i<msgList.length;i++){
    925. let visitorMes=msgList[i];
    926. let content = {}
    927. if(visitorMes["mes_type"]=="kefu"){
    928. content.is_kefu = false;
    929. }else{
    930. content.is_kefu = true;
    931. }
    932. content.avator = visitorMes["avator"];
    933. content.name = visitorMes["name"];
    934. content.content = replaceContent(visitorMes["content"]);
    935. content.time = visitorMes["time"];
    936. _this.msgList.push(content);
    937. _this.scrollBottom();
    938. }
    939. }
    940. if(data.code!=200){
    941. _this.$message({
    942. message: data.msg,
    943. type: 'error'
    944. });
    945. _this.chatTitle=KEFU_LANG[LANG]['refresh'];
    946. }
    947. }
    948. });
    949. },
    950. //获取信息列表
    951. sendEmailMsg:function(){
    952. let _this=this;
    953. _this.visitorContact.ent_id=ENT_ID;
    954. $.ajax({
    955. type:"post",
    956. url:"/ent/email_message",
    957. data:_this.visitorContact,
    958. success: function(data) {
    959. if(data.code!=200){
    960. _this.$message({
    961. message: data.msg,
    962. type: 'error'
    963. });
    964. }else{
    965. _this.allOffline=false;
    966. }
    967. }
    968. });
    969. },
    970. //滚动到底部
    971. scrollBottom:function(){
    972. var _this=this;
    973. //$('.chatVisitorPage').animate({scrollTop:'99999999999999'},"slow");
    974. this.$nextTick(function(){
    975. var container = _this.$el.querySelector(".chatVisitorPage");
    976. container.scrollTop = 999999;
    977. //alert(1);
    978. //$('.chatVisitorPage').animate({scrollTop:'99999999999999'},'99999999');
    979. // $('.chatVisitorPage').scrollTop(9999999999999999999);
    980. });
    981. },
    982. //软键盘问题
    983. textareaFocus:function(){
    984. // if(/Android|webOS|iPhone|iPad|BlackBerry/i.test(navigator.userAgent)) {
    985. // //$(".chatContext").css("margin-bottom","0");
    986. // //$(".chatBoxSend").css("position","static");
    987. // this.textareaFocused=true;
    988. // }
    989. this.scrollBottom();
    990. },
    991. textareaBlur:function(){
    992. // if(this.textareaFocused&&/Android|webOS|iPhone|iPad|BlackBerry/i.test(navigator.userAgent)) {
    993. // var chatBoxSendObj=$(".chatBoxSend");
    994. // var chatContextObj=$(".chatContext");
    995. // if(this.textareaFocused&&chatBoxSendObj.css("position")!="fixed"){
    996. // //chatContextObj.css("margin-bottom","105px");
    997. // //chatBoxSendObj.css("position","fixed");
    998. // this.textareaFocused=false;
    999. // }
    1000. //
    1001. // }
    1002. this.scrollBottom();
    1003. },
    1004. sendReply:function(title){
    1005. var _this=this;
    1006. let msg = {}
    1007. msg.avator=_this.visitor.avator;
    1008. msg.content = replaceContent(title);
    1009. msg.name = _this.visitor.name;
    1010. msg.is_kefu = true;
    1011. msg.time = _this.getNowDate();
    1012. msg.show_time=false;
    1013. _this.msgList.push(msg);
    1014. _this.scrollBottom();
    1015. var mes = {};
    1016. mes.content = title;
    1017. mes.from_id = this.visitor.visitor_id;
    1018. mes.ent_id = ENT_ID;
    1019. _this.sendAjax("/2/message_ask","post",mes,function(msg){
    1020. var msgArr=msg.content.split("[b]");
    1021. for(var i in msgArr){
    1022. let content = {}
    1023. content.avator = msg.avator;
    1024. content.name = msg.name;
    1025. content.content =replaceSpecialTag(msgArr[i]);
    1026. content.is_kefu = false;
    1027. content.time = msg.time;
    1028. content.is_reply=true;
    1029. _this.msgList.push(content);
    1030. _this.scrollBottom();
    1031. }
    1032. _this.cleanAllTimeout();
    1033. _this.alertSound('/static/images/notification.mp3');//提示音
    1034. });
    1035. //this.chatToUser();
    1036. },
    1037. //获取日期
    1038. getNowDate : function() {// 获取日期
    1039. var d = new Date(new Date());
    1040. return d.getFullYear() + '-' + this.digit(d.getMonth() + 1) + '-' + this.digit(d.getDate())
    1041. + ' ' + this.digit(d.getHours()) + ':' + this.digit(d.getMinutes()) + ':' + this.digit(d.getSeconds());
    1042. },
    1043. //补齐数位
    1044. digit : function (num) {
    1045. return num < 10 ? '0' + (num | 0) : num;
    1046. },
    1047. setCache : function (key,obj){
    1048. if(navigator.cookieEnabled&&typeof window.localStorage !== 'undefined'){
    1049. localStorage.setItem(key, JSON.stringify(obj));
    1050. }
    1051. },getCache : function (key){
    1052. if(navigator.cookieEnabled&&typeof window.localStorage !== 'undefined') {
    1053. return JSON.parse(localStorage.getItem(key));
    1054. }
    1055. },
    1056. setNoticeWelcome(list){
    1057. var _this=this;
    1058. var msgs = list;
    1059. var delaySecond=0;
    1060. for(let i in msgs){
    1061. var msg=msgs[i];
    1062. if(msg.delay_second){
    1063. delaySecond+=msg.delay_second;
    1064. }else{
    1065. delaySecond+=4;
    1066. }
    1067. var timer = setTimeout(function (msg) {
    1068. msg.time=shortTime(getNowDate());
    1069. msg.content = replaceSpecialTag(msg.content);
    1070. msg.name=_this.entConfig.robotName;
    1071. _this.msgList.push(msg);
    1072. _this.scrollBottom();
    1073. _this.alertSound('/static/images/notification.mp3');
    1074. var redata={
    1075. type:"message",
    1076. data:msg
    1077. }
    1078. window.parent.postMessage(redata,"*");
    1079. },1000*delaySecond,msg);
    1080. _this.allTimeouter.push(timer);
    1081. }
    1082. },
    1083. //获取自动欢迎语句
    1084. getNotice : function (){
    1085. var _this=this;
    1086. var oldNotice=getFakeCookie("noticed_"+ENT_ID);
    1087. if(oldNotice){
    1088. if(_this.autoWelcome=="on"){
    1089. return;
    1090. }
    1091. $.get("/2/notices?visitor_id="+this.visitor.visitor_id+"&ent_id="+ENT_ID+"&kefu_name="+this.visitor.to_id,function(res) {
    1092. _this.entConfig=res.result.ent_config;
    1093. if (res.result.welcome != null) {
    1094. setFakeCookie("noticed_"+ENT_ID,res.result.welcome,7*3600*24);
    1095. _this.setNoticeWelcome(res.result.welcome);
    1096. }
    1097. });
    1098. return;
    1099. }
    1100. $.get("/2/notices?is_record=1&visitor_id="+this.visitor.visitor_id+"&ent_id="+ENT_ID+"&kefu_name="+this.visitor.to_id,function(res) {
    1101. _this.entConfig=res.result.ent_config;
    1102. if (res.result.welcome != null) {
    1103. setFakeCookie("noticed_"+ENT_ID,res.result.welcome,7*3600*24);
    1104. _this.setNoticeWelcome(res.result.welcome);
    1105. }
    1106. });
    1107. },
    1108. initCss:function(){
    1109. var _hmt = _hmt || [];
    1110. (function() {
    1111. var hm = document.createElement("script");
    1112. hm.src = "https://hm.baidu.com/hm.js?82938760e00806c6c57adee91f39aa5e";
    1113. var s = document.getElementsByTagName("script")[0];
    1114. s.parentNode.insertBefore(hm, s);
    1115. })();
    1116. var _this=this;
    1117. $(function () {
    1118. //手机端的样式问题
    1119. // if(_this.isMobile){
    1120. // $(".chatVisitorPage").css("height","calc(100% - 155px)");
    1121. // }
    1122. //展示表情
    1123. // var faces=placeFace();
    1124. // $.each(faceTitles, function (index, item) {
    1125. // _this.face.push({"name":item,"path":faces[item]});
    1126. // });
    1127. // $(".visitorFaceBtn").click(function(e){
    1128. // var status=$('.faceBox').css("display");
    1129. // if(status=="block"){
    1130. // $('.faceBox').hide();
    1131. // }else{
    1132. // $('.faceBox').show();
    1133. // }
    1134. // return false;
    1135. // });
    1136. $("body").on("click",".replyContentBtn a",function() {
    1137. var txt=$(this).text();
    1138. var href=$(this).attr("href");
    1139. if(href=="self"||!href){
    1140. _this.messageContent=txt;
    1141. _this.chatToUser();
    1142. return false;
    1143. }
    1144. });
    1145. //var windheight = $(window).height();
    1146. $(window).resize(function(){
    1147. //var docheight = $(window).height(); /*唤起键盘时当前窗口高度*/
    1148. //_this.scrollBottom();
    1149. _this.visitorPageHeight();
    1150. // if(docheight < windheight){ /*当唤起键盘高度小于未唤起键盘高度时执行*/
    1151. // $(".chatBoxSend").css("position","static");
    1152. // }else{
    1153. // $(".chatBoxSend").css("position","fixed");
    1154. // }
    1155. //_this.visitorPageHeight();
    1156. //document.getElementById('chatVisitorPage').style.height = (document.documentElement.clientHeight - 71) + 'px';
    1157. });
    1158. if(isMobile()){
    1159. _this.visitorPageHeight();
    1160. //document.getElementById('chatVisitorPage').style.height = (document.documentElement.clientHeight - 71) + 'px';
    1161. }
    1162. //自动问答
    1163. $("body").on("click",".visitorReplyContent",function() {
    1164. var txt=$(this).find("span").text();
    1165. _this.messageContent=txt;
    1166. _this.chatToUser();
    1167. });
    1168. //自动问答换一换
    1169. $("body").on("click",".visitorReplyTitle a",function() {
    1170. ++_this.topQuestionPage;
    1171. if(_this.topQuestionPage>_this.topQuestionCount){
    1172. _this.topQuestionPage=1;
    1173. }
    1174. var result=pagination(_this.topQuestionPage,_this.topQuestionPagesize,_this.topQuestionList);
    1175. _this.makeReplyItem(result,true);
    1176. });
    1177. });
    1178. },
    1179. //心跳
    1180. ping:function(){
    1181. let _this=this;
    1182. let mes = {}
    1183. mes.type = "ping";
    1184. mes.data = "visitor:"+_this.visitor.visitor_id;
    1185. setInterval(function () {
    1186. if(_this.socket!=null&&!_this.wsSocketClosed){
    1187. _this.socket.send(JSON.stringify(mes));
    1188. }
    1189. },10000);
    1190. },
    1191. //初始化
    1192. init:function(){
    1193. var _this=this;
    1194. _this.isMobile=isMobile();
    1195. this.initCss();
    1196. //已读消息
    1197. var ms= 1000*2;
    1198. var lastClick = Date.now() - ms;
    1199. $("body").mouseover(function(){
    1200. if(!_this.haveUnreadMessage){
    1201. return;
    1202. }
    1203. if (Date.now() - lastClick >= ms) {
    1204. lastClick = Date.now();
    1205. //如果有未读消息,调用已读接口
    1206. _this.sendAjax("/2/messages_read","post",{"visitor_id":_this.visitor.visitor_id,"kefu":_this.visitor.to_id},function(data){
    1207. _this.haveUnreadMessage=false;
    1208. });
    1209. }
    1210. });
    1211. $('body').click(function(){
    1212. clearFlashTitle();
    1213. window.parent.postMessage({type:"focus"},"*");
    1214. //$('.faceBox').hide();
    1215. //剪贴板
    1216. try{
    1217. var selecter = window.getSelection().toString();
    1218. if (selecter != null && selecter.trim() != ""){
    1219. var str=selecter.trim();
    1220. _this.sendInputingStrNow(str);
    1221. }
    1222. } catch (err){
    1223. var selecter = document.selection.createRange();
    1224. var s = selecter.text;
    1225. if (s != null && s.trim() != ""){
    1226. var str=s.trim();
    1227. _this.sendInputingStrNow(str);
    1228. }
    1229. }
    1230. });
    1231. $("body").on("click",".chatImagePic",function() {
    1232. var url=$(this).attr("data-src");
    1233. _this.imgPreviewSrc=[url];
    1234. _this.$refs.preview.clickHandler();
    1235. //new PinchZoom.default($(this)[0], {});
    1236. // _this.$alert("", "", {
    1237. // dangerouslyUseHTMLString: true
    1238. // });
    1239. return false;
    1240. });
    1241. window.onfocus = function () {
    1242. //_this.focusHandle();
    1243. }
    1244. //判断当前是否在iframe中
    1245. if(self!=top){
    1246. _this.isIframe=true;
    1247. }
    1248. },
    1249. //表情点击事件
    1250. faceIconClick:function(index){
    1251. this.showFaceIcon=false;
    1252. this.messageContent+="face"+this.face[index].name;
    1253. },
    1254. //上传图片
    1255. uploadImg:function (url){
    1256. let _this=this;
    1257. $('#uploadImg').after('');
    1258. $("#uploadImgFile").click();
    1259. $("#uploadImgFile").change(function (e) {
    1260. var formData = new FormData();
    1261. var file = $("#uploadImgFile")[0].files[0];
    1262. formData.append("imgfile",file); //传给后台的filekey值是可以自己定义的
    1263. filter(file) && $.ajax({
    1264. url: url || '',
    1265. type: "post",
    1266. data: formData,
    1267. contentType: false,
    1268. processData: false,
    1269. dataType: 'JSON',
    1270. mimeType: "multipart/form-data",
    1271. //添加自定义属性,监听上下文的进度
    1272. xhr: function() {
    1273. //创建原生的ajax请求对象
    1274. var xhr = $.ajaxSettings.xhr();
    1275. //监听进度的一个事件
    1276. xhr.upload.onprogress = function(e) {
    1277. console.log(e.total); //文件大小
    1278. console.log(e.loaded); //上传多少
    1279. var w = parseInt((e.loaded / e.total) * 100)
    1280. console.log(w);
    1281. _this.percentage=w;
    1282. if(w>=100){
    1283. _this.percentage=0;
    1284. }
    1285. }
    1286. return xhr
    1287. },
    1288. success: function (res) {
    1289. if(res.code!=200){
    1290. _this.$message({
    1291. message: res.msg,
    1292. type: 'error'
    1293. });
    1294. }else{
    1295. _this.$message({
    1296. message: KEFU_LANG[LANG]['uploadSuccess'],
    1297. type: 'success'
    1298. });
    1299. _this.messageContent+='img[' + res.result.path + ']';
    1300. _this.chatToUser();
    1301. setTimeout(function () {
    1302. _this.scrollBottom();
    1303. },2000);
    1304. }
    1305. },
    1306. error: function (data) {
    1307. console.log(data);
    1308. _this.$message({
    1309. message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
    1310. type: 'error'
    1311. });
    1312. }
    1313. });
    1314. });
    1315. },
    1316. //上传文件
    1317. uploadFile:function (url){
    1318. let _this=this;
    1319. $('#uploadFile').after('');
    1320. $("#uploadRealFile").click();
    1321. $("#uploadRealFile").change(function (e) {
    1322. var formData = new FormData();
    1323. var file = $("#uploadRealFile")[0].files[0];
    1324. formData.append("realfile",file); //传给后台的filekey值是可以自己定义的
    1325. console.log(formData);
    1326. $.ajax({
    1327. url: url || '',
    1328. type: "post",
    1329. data: formData,
    1330. contentType: false,
    1331. processData: false,
    1332. dataType: 'JSON',
    1333. mimeType: "multipart/form-data",
    1334. //添加自定义属性,监听上下文的进度
    1335. xhr: function() {
    1336. //创建原生的ajax请求对象
    1337. var xhr = $.ajaxSettings.xhr();
    1338. //监听进度的一个事件
    1339. xhr.upload.onprogress = function(e) {
    1340. console.log(e.total); //文件大小
    1341. console.log(e.loaded); //上传多少
    1342. var w = parseInt((e.loaded / e.total) * 100)
    1343. console.log(w);
    1344. _this.percentage=w;
    1345. if(w>=100){
    1346. _this.percentage=0;
    1347. }
    1348. }
    1349. return xhr
    1350. },
    1351. success: function (res) {
    1352. if(res.code!=200){
    1353. _this.$message({
    1354. message: res.msg,
    1355. type: 'error'
    1356. });
    1357. }else{
    1358. _this.$message({
    1359. message: KEFU_LANG[LANG]['uploadSuccess'],
    1360. type: 'success'
    1361. });
    1362. //_this.messageContent+='file[' + res.result.path + ']';
    1363. var data=JSON.stringify({
    1364. name:res.result.name,
    1365. ext:res.result.ext,
    1366. size:res.result.size,
    1367. path:res.result.path,
    1368. })
    1369. _this.messageContent+='mutiFile[' + data+ ']';
    1370. _this.chatToUser();
    1371. }
    1372. },
    1373. error: function (data) {
    1374. console.log(data);
    1375. _this.$message({
    1376. message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
    1377. type: 'error'
    1378. });
    1379. }
    1380. });
    1381. });
    1382. },
    1383. //粘贴上传图片
    1384. onPasteUpload:function(event){
    1385. let items = event.clipboardData && event.clipboardData.items;
    1386. let file = null
    1387. if (items && items.length) {
    1388. // 检索剪切板items
    1389. for (var i = 0; i < items.length; i++) {
    1390. if (items[i].type.indexOf('image') !== -1) {
    1391. file = items[i].getAsFile()
    1392. }
    1393. }
    1394. }
    1395. if (!file) {
    1396. return;
    1397. }
    1398. let _this=this;
    1399. var formData = new FormData();
    1400. formData.append('imgfile', file);
    1401. $.ajax({
    1402. url: '/uploadimg',
    1403. type: "post",
    1404. data: formData,
    1405. contentType: false,
    1406. processData: false,
    1407. dataType: 'JSON',
    1408. mimeType: "multipart/form-data",
    1409. //添加自定义属性,监听上下文的进度
    1410. xhr: function() {
    1411. //创建原生的ajax请求对象
    1412. var xhr = $.ajaxSettings.xhr();
    1413. //监听进度的一个事件
    1414. xhr.upload.onprogress = function(e) {
    1415. console.log(e.total); //文件大小
    1416. console.log(e.loaded); //上传多少
    1417. var w = parseInt((e.loaded / e.total) * 100)
    1418. console.log(w);
    1419. _this.percentage=w;
    1420. if(w>=100){
    1421. _this.percentage=0;
    1422. }
    1423. }
    1424. return xhr
    1425. },
    1426. success: function (res) {
    1427. if(res.code!=200){
    1428. _this.$message({
    1429. message: res.msg,
    1430. type: 'error'
    1431. });
    1432. }else{
    1433. _this.$message({
    1434. message: KEFU_LANG[LANG]['uploadSuccess'],
    1435. type: 'success'
    1436. });
    1437. _this.messageContent+='img[' + res.result.path + ']';
    1438. _this.chatToUser();
    1439. setTimeout(function () {
    1440. _this.scrollBottom();
    1441. },2000);
    1442. }
    1443. },
    1444. error: function (data) {
    1445. console.log(data);
    1446. _this.$message({
    1447. message: KEFU_LANG[LANG]['uploadFailed']+data.responseText,
    1448. type: 'error'
    1449. });
    1450. }
    1451. });
    1452. },
    1453. //自动
    1454. getTopQuestion:function(){
    1455. var _this=this;
    1456. $.get("/other/getTopQuestion?ent_id="+ENT_ID,function(res) {
    1457. if(res.code!=200||!res.result){
    1458. return;
    1459. }
    1460. var hotQuestion=res.result.hotQuestion;
    1461. if(hotQuestion!=""){
    1462. _this.hotQuestion=hotQuestion.split(",");
    1463. }
    1464. var questionList=res.result.questionList;
    1465. if(questionList.length==0){
    1466. return;
    1467. }
    1468. _this.topQuestionList=questionList;
    1469. _this.topQuestionCount=sumPage(_this.topQuestionPagesize,questionList);
    1470. var result=pagination(1,_this.topQuestionPagesize,questionList);
    1471. _this.makeReplyItem(result);
    1472. });
    1473. },
    1474. makeReplyItem:function(result,isPage){
    1475. var _this=this;
    1476. var msg={};
    1477. msg.type="card";
    1478. msg.avator = _this.noticeAvatar;
    1479. msg.name = _this.noticeName;
    1480. msg.show_time = true;
    1481. msg.time = _this.getNowDate();
    1482. msg.content="";
    1483. var i=1;
    1484. for(key in result){
    1485. msg.content+="
      "+i+". "+result[key]+"
      "
      ;
    1486. i++;
    1487. }
    1488. if(!isPage){
    1489. _this.msgList.push(msg);
    1490. _this.scrollBottom();
    1491. }else{
    1492. $(".cardBoxContent").html(msg.content);
    1493. }
    1494. },
    1495. //自动
    1496. getAutoReply:function(){
    1497. var _this=this;
    1498. $.get("/autoreply?ent_id="+ENT_ID,function(res) {
    1499. if(res.code!=200 || res.result.length==0){
    1500. return;
    1501. }
    1502. var result=res.result;
    1503. _this.replys.push(result);
    1504. });
    1505. },
    1506. //提示音
    1507. alertSound:function(soundUrl){
    1508. var b = document.getElementById("chatMessageAudio");
    1509. if (b.canPlayType('audio/ogg; codecs="vorbis"')) {
    1510. b.type= 'audio/mpeg';
    1511. b.src=soundUrl ;
    1512. var p = b.play();
    1513. p && p.then(function () {
    1514. }).catch(function (e) {
    1515. });
    1516. }
    1517. },
    1518. sendSound:function(){
    1519. var b = document.getElementById("chatMessageSendAudio");
    1520. if (b.canPlayType('audio/ogg; codecs="vorbis"')) {
    1521. b.type= 'audio/mpeg';
    1522. b.src= '/static/images/sent.ogg';
    1523. var p = b.play();
    1524. p && p.then(function(){}).catch(function(e){});
    1525. }
    1526. },
    1527. initPeerjs:function(){
    1528. var peer = new Peer();
    1529. this.peer=peer;
    1530. var _this=this;
    1531. peer.on('open', function(id) {
    1532. console.log('My peer ID is: ' + id);
    1533. _this.peerjsId=id;
    1534. });
    1535. peer.on('close', function() {
    1536. console.log('My peer close');
    1537. if(_this.loading!=null){
    1538. _this.loading.close();
    1539. }
    1540. });
    1541. peer.on('disconnected', function() {
    1542. console.log('My peer disconnected');
    1543. if(_this.loading!=null){
    1544. _this.loading.close();
    1545. }
    1546. });
    1547. peer.on('error', function() {
    1548. console.log('My peer error');
    1549. if(_this.loading!=null){
    1550. _this.loading.close();
    1551. }
    1552. });
    1553. },
    1554. //打电话
    1555. callPhone:function(){
    1556. var _this=this;
    1557. var media=(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
    1558. if(!media){
    1559. _this.$message({
    1560. type: 'error',
    1561. message: "not support"
    1562. });
    1563. return ;
    1564. }
    1565. var getUserMedia = media.bind(navigator);
    1566. this.$confirm(this.flyLang.videoAudio, this.flyLang.tips, {
    1567. confirmButtonText: this.flyLang.audio,
    1568. cancelButtonText: this.flyLang.cancel,
    1569. type: 'warning'
    1570. }).then(() => {
    1571. _this.messageContent="voice call...";
    1572. _this.chatToUser();
    1573. _this.loading = _this.$loading({
    1574. lock: true,
    1575. text: _this.flyLang.connecting,
    1576. spinner: 'el-icon-phone-outline',
    1577. background: 'rgba(0, 0, 0, 0.7)'
    1578. });
    1579. _this.initPeerjs();//初始化peerjs
    1580. _this.loadingTimerTimeoutClose();
    1581. _this.isVideo=false;
    1582. getUserMedia({video:false, audio: {
    1583. noiseSuppression: true,
    1584. echoCancellation: true,
    1585. }}, function(stream) {
    1586. _this.localStream=stream;
    1587. _this.sendAjax("/2/callKefu","post",{"action":"callpeer",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
    1588. });
    1589. }, function(err) {
    1590. _this.$message({
    1591. type: 'error',
    1592. message: err
    1593. });
    1594. if(_this.loading) _this.loading.close();
    1595. });
    1596. }).catch(() => {
    1597. });
    1598. },
    1599. callPeer:function(){
    1600. var _this=this;
    1601. var media=(navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia);
    1602. if(!media){
    1603. _this.$message({
    1604. type: 'error',
    1605. message: "not support"
    1606. });
    1607. return ;
    1608. }
    1609. var getUserMedia = media.bind(navigator);
    1610. this.$confirm(this.flyLang.videoAudio, this.flyLang.tips, {
    1611. confirmButtonText: this.flyLang.video,
    1612. cancelButtonText: this.flyLang.cancel,
    1613. type: 'warning'
    1614. }).then(() => {
    1615. _this.messageContent="video call...";
    1616. _this.chatToUser();
    1617. this.loading = this.$loading({
    1618. lock: true,
    1619. text: _this.flyLang.connecting,
    1620. spinner: 'el-icon-video-camera',
    1621. background: 'rgba(0, 0, 0, 0.5)'
    1622. });
    1623. this.loadingTimerTimeoutClose();
    1624. this.isVideo=true;
    1625. //初始化peerjs
    1626. this.initPeerjs();
    1627. getUserMedia({video:true, audio: {
    1628. noiseSuppression: true,
    1629. echoCancellation: true,
    1630. }}, function(stream) {
    1631. _this.localStream=stream;
    1632. _this.sendAjax("/2/callKefu","post",{"action":"callpeer",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
    1633. });
    1634. }, function(err) {
    1635. _this.$message({
    1636. type: 'error',
    1637. message: err
    1638. });
    1639. if(_this.loading) _this.loading.close();
    1640. });
    1641. }).catch(() => {
    1642. });
    1643. },
    1644. talkPeer:function(){
    1645. var _this=this;
    1646. var canvas = this.canvasElement;
    1647. if(this.loading!=null){
    1648. this.loading.close();
    1649. }
    1650. clearTimeout(this.loadingTimer);
    1651. this.$message({
    1652. message: '正在通话...',
    1653. type: 'success'
    1654. });
    1655. if(_this.kefuPeerId==""||_this.localStream==null){
    1656. return;
    1657. }
    1658. //本人摄像头
    1659. if(_this.isVideo){
    1660. var localVideo=document.querySelector("#chatLocalRtc");
    1661. localVideo.srcObject = _this.localStream;
    1662. localVideo.autoplay=true;
    1663. }
    1664. //localVideo.autoplay = true;
    1665. _this.call = _this.peer.call(_this.kefuPeerId, _this.localStream);
    1666. _this.call.on('stream', function(remoteStream) {
    1667. var remoteVideo = document.querySelector("#chatRtc");
    1668. remoteVideo.srcObject = remoteStream;
    1669. remoteVideo.autoplay = true;
    1670. });
    1671. _this.call.on('close', function() {
    1672. console.log("call close");
    1673. _this.loading.close();
    1674. _this.callClear();
    1675. });
    1676. _this.call.on('error', function(err) {
    1677. console.log(err);
    1678. _this.callClear();
    1679. _this.loading.close();
    1680. });
    1681. // _this.$alert('正在通话,请保持页面..', '提示', {
    1682. // confirmButtonText: '挂断',
    1683. // callback: function(){
    1684. // _this.sendAjax("/2/callKefu","post",{"action":"callCancel",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
    1685. // });
    1686. // if(call!=null){
    1687. // call.close();
    1688. // }
    1689. // }
    1690. // });
    1691. // 调用Vudio
    1692. // var vudio = new Vudio(_this.localStream, canvas, {
    1693. // accuracy: 256,
    1694. // width: 800,
    1695. // height: 100,
    1696. // waveform: {
    1697. // fadeSide: false,
    1698. // maxHeight: 100,
    1699. // verticalAlign: 'middle',
    1700. // horizontalAlign: 'center',
    1701. // color: '#2980b9'
    1702. // }
    1703. // })
    1704. //
    1705. // vudio.dance()
    1706. },
    1707. callClose(){
    1708. var _this=this;
    1709. if(this.call==null) return;
    1710. _this.sendAjax("/2/callKefu","post",{"action":"callCancel",kefu_id:_this.visitor.to_id,visitor_id:_this.visitor.visitor_id},function(result){
    1711. });
    1712. _this.callClear();
    1713. },
    1714. getExtendInfo:function(){
    1715. var _this=this;
    1716. var extra=getQuery("extra");
    1717. if(extra==""){
    1718. return;
    1719. }
    1720. try{
    1721. var extraString=b64ToUtf8(extra);
    1722. if(_this.getCache("extra")==extraString){
    1723. return;
    1724. }
    1725. var extra=JSON.parse(extraString);
    1726. if (typeof extra=="string"){
    1727. extra=JSON.parse(extra);
    1728. }
    1729. for(var key in extra){
    1730. if(extra[key]==""){
    1731. extra[key]="无";
    1732. }
    1733. if(key=="visitorProduct"){
    1734. _this.messageContent="product["+JSON.stringify(extra[key])+"]";
    1735. _this.chatToUser();
    1736. _this.setCache("extra",extraString);
    1737. };
    1738. }
    1739. }catch (e) {
    1740. }
    1741. },
    1742. sendAjax:function(url,method,params,callback){
    1743. let _this=this;
    1744. $.ajax({
    1745. type: method,
    1746. url: url,
    1747. data:params,
    1748. headers:{
    1749. "lang":getQuery("lang"),
    1750. },
    1751. error:function(res){
    1752. var data=JSON.parse(res.responseText);
    1753. console.log(data);
    1754. if(data.code!=200){
    1755. _this.$message({
    1756. message: data.msg,
    1757. type: 'error'
    1758. });
    1759. }
    1760. },
    1761. success: function(data) {
    1762. if(data.code!=200){
    1763. _this.$message({
    1764. message: data.msg,
    1765. type: 'error'
    1766. });
    1767. }else if(data.result!=null){
    1768. callback(data.result);
    1769. }else{
    1770. callback(data);
    1771. }
    1772. }
    1773. });
    1774. },
    1775. showTitle:function(title){
    1776. //this.chatTitle=title;
    1777. $(".chatBox").append("
      chatNotice\">
      "+title+"
      "
      );
    1778. this.scrollBottom();
    1779. },
    1780. //开始录音
    1781. startRecoder:function(e){
    1782. if(this.recorder){
    1783. this.recorder.destroy();
    1784. this.recorder=null;
    1785. }
    1786. var _this=this;
    1787. Recorder.getPermission().then(function() {
    1788. _this.recorder = new Recorder();
    1789. _this.recorderAudio = document.querySelector('#audio');
    1790. _this.recorder.start();
    1791. _this.recorder.onprogress = function (params) {
    1792. _this.recoderSecond = parseInt(params.duration);
    1793. }
    1794. this.talkBtnText = "松开 结束";
    1795. }, function(error){
    1796. _this.$message({
    1797. message: error,
    1798. type: 'error'
    1799. });
    1800. return;
    1801. });
    1802. e.preventDefault();
    1803. },
    1804. stopRecoder:function(e){
    1805. if(!this.recorder){
    1806. return;
    1807. }
    1808. var blob=this.recorder.getWAVBlob();
    1809. this.recorderAudio.src = URL.createObjectURL(blob);
    1810. this.recorderAudio.controls = true;
    1811. this.talkBtnText="按住 说话";
    1812. this.recorderEnd=true;
    1813. e.preventDefault();
    1814. },
    1815. sendRecoder:function(){
    1816. if(!this.recorder){
    1817. return;
    1818. }
    1819. var blob=this.recorder.getWAVBlob();
    1820. var formdata = new FormData(); // form 表单 {key:value}
    1821. formdata.append("realfile", blob); // form input type="file"
    1822. var _this=this;
    1823. this.loading = this.$loading({
    1824. lock: true,
    1825. text: '正在发送',
    1826. spinner: 'el-icon-loading',
    1827. background: 'rgba(0, 0, 0, 0.7)'
    1828. });
    1829. $.ajax({
    1830. url: "/2/uploadAudio",
    1831. type: 'post',
    1832. processData: false,
    1833. contentType: false,
    1834. data: formdata,
    1835. dataType: 'JSON',
    1836. mimeType: "multipart/form-data",
    1837. success: function (res) {
    1838. _this.loading.close();
    1839. if(res.code!=200){
    1840. _this.$message({
    1841. message: res.msg,
    1842. type: 'error'
    1843. });
    1844. }else{
    1845. _this.cancelRecoder();
    1846. _this.messageContent+='audio[' + res.result.path + ']';
    1847. _this.chatToUser();
    1848. }
    1849. }
    1850. })
    1851. },
    1852. cancelRecoder:function(){
    1853. this.audioDialog=false;
    1854. if(!this.recorder){
    1855. return;
    1856. }
    1857. this.recorder.destroy();
    1858. this.recorder=null;
    1859. this.recoderSecond=0;
    1860. },
    1861. recoderFormat:function(percentage){
    1862. return percentage+"s";
    1863. },
    1864. openNewWindow:function(){
    1865. var features = "height=800px, width=960px, top=0, left=0, toolbar=no, menubar=no,scrollbars=no,resizable=no, location=no, status=no"; //设置新窗口的特性
    1866. var me = window.open(location.href, "newW", features);
    1867. },
    1868. //超时关闭
    1869. checkTimeout:function(){
    1870. var _this=this;
    1871. this.timeoutTimer=setInterval(function(){
    1872. if (Date.now() - _this.currentActiveTime >= _this.timeoutLongTime) {
    1873. if(_this.VisitorCommentBtn!="true"){
    1874. _this.comment=true;
    1875. }
    1876. console.log("长时间无操作");
    1877. if(_this.socket!=null){
    1878. _this.reconnectDialog=true;
    1879. _this.showTitle(KEFU_LANG[LANG]['autoclosemes']);
    1880. _this.socket.close();
    1881. _this.socket=null;
    1882. }
    1883. }
    1884. },55000);
    1885. },
    1886. closeTimeoutTimer:function(){
    1887. clearInterval(this.timeoutTimer);
    1888. },
    1889. cleanAllTimeout:function(){
    1890. for(var i in this.allTimeouter){
    1891. clearTimeout(this.allTimeouter[i]);
    1892. }
    1893. },
    1894. loadMoreMessages:function(){
    1895. var _this=this;
    1896. var pagesize=5;
    1897. // if(this.currentPage>1){
    1898. // this.replys=[];
    1899. // }
    1900. if(_this.loadMoreDisable){
    1901. return;
    1902. }
    1903. var moreMessage=KEFU_LANG[LANG]['moremessage'];
    1904. this.flyLang.moremessage=this.flyLang.loading;
    1905. this.loadMoreDisable=true;
    1906. var hasUnread=false;
    1907. this.sendAjax("/2/messages_page","get",{pagesize:pagesize,ent_id:ENT_ID,page:this.currentPage,visitor_id:_this.visitor.visitor_id},function(result){
    1908. var len=result.list.length;
    1909. if(result.list.length!=0){
    1910. if(len<pagesize){
    1911. _this.showLoadMore=false;
    1912. }else{
    1913. _this.showLoadMore=true;
    1914. }
    1915. let msgList=result.list;
    1916. for(var i=0;i<msgList.length;i++) {
    1917. let visitorMes = msgList[i];
    1918. let content = {}
    1919. if (visitorMes["mes_type"] == "kefu") {
    1920. content.is_kefu = false;
    1921. content.content = replaceSpecialTag(visitorMes["content"]);
    1922. } else {
    1923. content.is_kefu = true;
    1924. content.content = replaceContent(visitorMes["content"]);
    1925. }
    1926. if (visitorMes["read_status"] == "read") {
    1927. content.read_status = KEFU_LANG[LANG].read;
    1928. } else {
    1929. content.read_status = KEFU_LANG[LANG].unread;
    1930. if(i==0){
    1931. hasUnread=true;
    1932. _this.haveUnreadMessage=true;
    1933. }
    1934. }
    1935. content.avator = visitorMes["avator"];
    1936. content.name = visitorMes["name"];
    1937. content.msg_id = visitorMes["msg_id"];
    1938. content.time = shortTime(visitorMes["time"]);
    1939. _this.msgList.unshift(content);
    1940. //_this.scrollBottom();
    1941. }
    1942. }else{
    1943. _this.showLoadMore=false;
    1944. }
    1945. if(_this.currentPage==1){
    1946. _this.scrollBottom();
    1947. //_this.getAutoReply();
    1948. }
    1949. _this.currentPage++;
    1950. _this.flyLang.moremessage=moreMessage;
    1951. _this.loadMoreDisable=false;
    1952. });
    1953. },
    1954. //展示微信公众号带参二维码
    1955. showWechatTip:function(){
    1956. var _this=this;
    1957. if(this.VisitorWechatQrcodeUrl==""||this.scanWechatQrcode!="true"){
    1958. return;
    1959. }
    1960. if(this.visitor.visitor_id.substr(0,2)=='wx'){
    1961. this.showTitle("微信访客用户已登录");
    1962. return;
    1963. }
    1964. var msg={};
    1965. msg.avator = _this.noticeAvatar;
    1966. msg.name = _this.noticeName;
    1967. msg.show_time = true;
    1968. msg.time = _this.getNowDate();
    1969. var child = '
      ';
    1970. child += '+this.VisitorWechatQrcodeUrl+'?visitor_id=' + this.visitor.visitor_id + '&ent_id='+ENT_ID+'">';
    1971. child += '扫描或长按左侧二维码关注公众号。
      可防止更换浏览器丢失消息、收不到回复。
      并可接收回复通知
      ';
  • 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>
  • 相关阅读:
    前后端分离开发
    2022.08.19 java web学习
    java-php-python-绿色生活基于PS、DW的绿色环保宣传网站计算机毕业设计
    HashMap 的底层数据结构是什么?
    Leetcode71. 简化路径
    Spring Boot开发之SpringSercurity(续)
    基于matlab的三维点云数据ICP拼接算法实现
    Docker DeskTop的安装(Windows版本)
    设计循环队列---力扣622
    【注解】注解解析与应用场景
  • 原文地址:https://blog.csdn.net/taoshihan/article/details/127812755