主頁 > 企業開發 > 在線客服系統原始碼開發實戰總結:需求分析及前端代碼基本技術方案

在線客服系統原始碼開發實戰總結:需求分析及前端代碼基本技術方案

2022-11-12 07:57:17 企業開發

在這個系列文章里,我嘗試將自己開發唯一客服系統(gofly.v1kf.com)所涉及的經驗和技術點進行梳理總結,

文章寫作水平有限,有時候會表達不清楚,難免有所疏漏,歡迎批評指正

 

該系列將分成以下幾個部分

一. 需求分析

二. 初步技術方案選型,驗證

三. 資料庫結構設計

四. WEB訪客前端設計與開發

五. WEB客服端設計與開發

六. 客戶端設計與開發

 

在這個系列的文章中,您將了解并學習到以下技術知識:

MySQL、VUE、WebSocket、Golang+Gin、UniApp 等

如果這些技術對您有用,還請您 推薦 一下本文章,謝謝!

 

什么是在線客服系統:

常見的用法是,點擊立即咨詢按鈕,直接跳轉到聊天視窗,或者是只需將系統生成的一段JavaScript代碼嵌入網站頁面,即可在網站上顯示代表客服的浮動小圖示,邀請框,點擊按鈕后在當前頁面彈窗展示,

而客服端可以在WEB客服后臺,查看網站正在溝通的實時在線訪客、瀏覽軌跡等,能直接和網站訪客進行在線即時交流,目的是提升客戶滿意度,及時解決客戶的問題,進一步提升網站的銷售額,

 

由此分析,在線客服系統大至分為三大塊:1)訪客端,2)客服端,3)客服移動端,但是僅僅分為這三大塊是不夠的,后面我們還將對每一塊進行進一步的分析,

 

訪客彈窗入口界面

 

訪客端彈窗界面

 

 

前端界面是使用的elementui,是基于vue.js的UI框架,作為后端開發程式員,非常不習慣用node.js編譯開發前端,所以我還是選擇了使用cdn引入的形式去使用這個框架

彈窗效果是使用的layer.js進行的彈窗,點擊圖示,呼叫layer.js去iframe的形式加載了訪客鏈接,這個訪客鏈接就是下面直接打開時的效果

 

 訪客端直接打開的界面

 

此界面為回應式設計,綜合運用了css3的媒體查詢功能,在大螢屏和小螢屏都能適配展示,所以該訪客界面是可以直接接入微信和APP中,

這個界面可以說的還是比較多的,后面我再去詳細總結

 客服端界面

 

 客服端也是使用的elementUI框架,整體結構是iframe框出來的,然后點擊不同的選單加載URL展示出來

總體來說,專案是偏向后端風格的,偏傳統的架構

下面是訪客端界面的代碼,就可以看出這個作業量有多大~~

<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <!--洗掉蘋果默認的工具列和選單欄,默認為no顯示工具列和選單欄,-->
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <!--QQ強制全屏-->
    <meta name="x5-fullscreen" content="true">
    <!--UC強制全屏-->
    <meta name="fullscreen" content="yes">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" name="viewport" />
    <title>{{.Title}}</title>
    <link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/cdn/element-ui/2.15.1/theme-chalk/index.min.css">
    <script src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/cdn/vue/2.6.11/vue.min.js"></script>
    <script src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/cdn/element-ui/2.15.1/index.js"></script>
    <script src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/cdn/jquery/3.6.0/jquery.min.js"></script>

    <script src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/js/functions.js?v=0.6.9"></script>
    <link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/css/common.css?v=yuyfgfgfg" />
    <link rel="stylesheet" href=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/css/icono.min.css" />
    <link rel="icon" href=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/images/favicon.ico">
    <style>
        .el-message-box{
            width: auto;
            max-width: 100%;
            max-height: 100%;
            overflow: auto;
        }
    </style>
</head>
<body class="visitorBody">
<div id="app"  class="chatCenter">
    <template>
        <!--客服代碼-->

        <div class="chatEntTitle" v-show="!isIframe">
            <el-badge :type="onlineType" is-dot class="item">
                <el-avatar class="chatEntTitleLogo" :size="35" :src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"noticeAvatar"></el-avatar>
            </el-badge>
            <div>
                <div><{chatTitle}></div>
                <div class="entIntro" v-show="entIntroduce!=''"><{entIntroduce}></div>
            </div>
        </div>
        <div class="chatEntBox">
            <!--公告欄-->
            <div v-show="visitorNotice!=''"
                    class="visitorNotice"
                    >
                <img src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/'/static/images/laba.svg'/>
                <span><{visitorNotice}></span>
                <img v-on:click="visitorNotice=''" src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"/static/images/cha.png" class="visitorNoticeClose"/>
            </div>
            <!--//公告欄-->


            <div  ref="chatVisitorPage" id="chatVisitorPage" class="chatContext chatVisitorPage" v-on:click="showIconBtns=false;showFaceIcon=false">
                <div class="chatBox">
                    <div class="chatNotice" v-on:click="loadMoreMessages" v-show="showLoadMore">
                        <a class="chatNoticeContent"><{flyLang.moremessage}></a>
                    </div>



                    <el-row :gutter="2" v-for="v in msgList" v-bind:class="{'chatBoxMe': v.is_kefu==true}">

                        <div class="messageBox questionBox" v-if="v.type=='question'">
                            <div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
                            <div class="left" v-if="v.is_kefu!=true" style="display: flex;">
                                <el-avatar style="margin-right:10px;flex-shrink: 0;"  :size="36" :src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"v.avator"></el-avatar>
                                <div class="chatMsgContent">
                                    <div class="chatUser"  v-if="showKefuName!='off'"><{v.name}></div>
                                    <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
                                </div>
                            </div>
                        </div>
                        <!--猜你想問-->
                        <div class="cardBox" v-else-if="v.type=='card'">
                            <div class='visitorReplyTitle'><{flyLang.guess}><a><i class='el-icon-refresh-left'></i> <{flyLang.huanyihuan}></a></div>
                            <div class="cardBoxContent" v-html="v.content"></div>
                        </div>
                        <!--//猜你想問-->

                        <!--訊息模板-->
                        <div class="messageBox" v-else>
                            <div class="chatTime left" v-bind:class="{'chatTimeHide': v.show_time==false}"><span><{v.time}></span></div>
                            <div class="left" v-if="v.is_kefu!=true" style="display: flex;">
                                <el-avatar style="margin-right:10px;flex-shrink: 0;"  :size="36" :src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"v.avator"></el-avatar>
                                <div class="chatMsgContent">
                                    <div class="chatUser"  v-if="showKefuName!='off'"><{v.name}></div>
                                    <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
                                </div>
                            </div>
                            <div class="kefuMe right" v-if="v.is_kefu==true" style="display: flex;justify-content: flex-end;">
                                <div>
                                    <div class="chatContent chatContent2 replyContentBtn" v-html="v.content"></div>
                                    <div class="chatReadStatus" v-show="VisitorReadStatus!='true'"><{v.read_status}></div>
                                </div>
                                <el-avatar v-if="VisitorShowAvator=='true'" style="margin-left:10px;flex-shrink: 0;"  :size="36" :src=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"v.avator"></el-avatar>
                            </div>
                            <div class="clear"></div>
                        </div>
                        <!--//訊息模板-->


                    </el-row>

                </div>
            </div>
            <div class="chatBoxSend">
                <div class="chatBoxSendMask" v-if="reconnectDialog">
                    <a @click="initConn" href=https://www.cnblogs.com/taoshihan/archive/2022/11/11/"javascript:void(0);"><{flyLang.socketclose}></a>
                </div>

                <div class="hotQuestion" v-if="hotQuestion.length!=0">
                    <a
                            class="slideInRightItem"
                            v-for="item in hotQuestion"
                            v-on:click="messageContent=item;chatToUser()">
                        <{item}>
                    </a>
                </div>
                <!--進度條-->
                <div class="progressLine">
                    <el-progress :stroke-width="6"  :percentage="percentage" v-show="percentage!=0" :text-inside="true"></el-progress>
                </div>
                <!--//進度條-->
                <div class="iconBtns visitorIconBox">

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

 

十年開發經驗程式員,離職全心創業中,歷時三年開發出的產品《唯一客服系統》

一款基于Golang+Vue開發的在線客服系統,軟體著作權編號:2021SR1462600,一套可私有化部署的網站在線客服系統,編譯后的二進制檔案可直接使用無需搭開發環境,下載zip解壓即可,僅依賴MySQL資料庫,是一個開箱即用的全渠道在線客服系統,致力于幫助廣大開發者/公司快速部署整合私有化客服功能, 開源地址:唯一客服(開源學習版) 官網地址:唯一客服官網

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/531857.html

標籤:其他

上一篇:Vue3 企業級優雅實戰 - 組件庫框架 - 3 搭建組件庫開發環境

下一篇:uniapp之uni-starter小程式多端研發框架搭建與專案實踐

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more