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

来源:https://www.cnblogs.com/taoshihan/archive/2022/11/11/16880444.html
-Advertisement-
Play Games

在這個系列文章里,我嘗試將自己開發唯一客服系統(gofly.v1kf.com)所涉及的經驗和技術點進行梳理總結。 文章寫作水平有限,有時候會表達不清楚,難免有所疏漏,歡迎批評指正 該系列將分成以下幾個部分 一. 需求分析 二. 初步技術方案選型,驗證 三. 資料庫結構設計 四. WEB訪客前端設計與 ...


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

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

 

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

一. 需求分析

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

三. 資料庫結構設計

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

五. WEB客服端設計與開發

六. 客戶端設計與開發

 

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

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

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

 

什麼是線上客服系統:

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

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

 

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

 

訪客彈窗入口界面

 

訪客端彈窗界面

 

 

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

彈窗效果是使用的layer.js進行的彈窗,點擊圖標,調用layer.js去iframe的形式載入了訪客鏈接,這個訪客鏈接就是下麵直接打開時的效果

 

 訪客端直接打開的界面

 

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

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

 客服端界面

 

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

總體來說,項目是偏向後端風格的,偏傳統的架構

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

<!DOCTYPE html>
<head>
    <meta charset="utf-8">
    <!--刪除蘋果預設的工具欄和菜單欄,預設為no顯示工具欄和菜單欄。-->
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    <!--QQ強制全屏-->
    <meta name="x5-fullscreen" content="true">
    <!--UC強制全屏-->
    <meta name="fullscreen" content="yes">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0;" name="viewport" />
    <title>{{.Title}}</title>
    <link rel="stylesheet" href="/static/cdn/element-ui/2.15.1/theme-chalk/index.min.css">
    <script src="/static/cdn/vue/2.6.11/vue.min.js"></script>
    <script src="/static/cdn/element-ui/2.15.1/index.js"></script>
    <script src="/static/cdn/jquery/3.6.0/jquery.min.js"></script>

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

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


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



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

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

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


                    </el-row>

                </div>
            </div>
            <div class="chatBoxSend">
                <div class="chatBoxSendMask" v-if="reconnectDialog">
                    <a @click="initConn" href="javascript:void(0);"><{flyLang.socketclose}></a>
                </div>

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

                    <el-tooltip :content="flyLang.emotions" placement="top">
                        <div class="icono-smile visitorIconBtns visitorFaceBtn" v-on:click="showFaceIcon==true?showFaceIcon=false:showFaceIcon=true"></div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.photo" placement="top">
                        <div v-show="VisitorUploadImgBtn!='true'" :title="flyLang.photo" class="el-icon-picture" id="uploadImg" v-on:click="uploadImg('/uploadimg')" style="font-size: 22px;"></div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.file" placement="top">
                        <div v-show="VisitorUploadFileBtn!='true'" :title="flyLang.file" class="el-icon-upload" id="uploadFile" v-on:click="uploadFile('/2/uploadFile')" style="font-size: 22px;"></div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.recoder" placement="top">
                        <div v-show="VisitorVoiceBtn!='true'" :title="flyLang.recoder" class="el-icon-microphone"  v-on:click="audioDialog=true" style="font-size: 22px;"></div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.map" placement="top">
                        <div v-show="VisitorMapBtn!='true'" style="font-size: 22px;" class="el-icon-location"  v-on:click="qqMap==true?qqMap=false:qqMap=true;"></div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.audio" placement="top">
                        <div class="el-icon-phone-outline" @click="callPhone()" style="font-size: 20px;">
                        </div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.video" placement="top">
                        <div class="el-icon-video-camera" @click="callPeer()" style="font-size: 22px;">
                        </div>
                    </el-tooltip>
                    <el-tooltip :content="flyLang.language" placement="top">
                        <div  @click="flagsDialog='true'">
                            <img src="/static/images/lang.png" style="width: 20px;"/>
                        </div>
                    </el-tooltip>
                </div>
                <div class="faceBox visitorFaceBox" v-if="showFaceIcon">
                    <ul class="faceBoxList">
                        <li v-on:click="faceIconClick(i)" class="faceIcon" v-for="(v,i) in face"  :title="v.name"><img :src=v.path></li>
                    </ul>
                    <div class="clear"></div>
                </div>
                <!--搜索建議-->
                <div class="searchList" v-show="searchList.length!=0">
                    <div v-on:click="messageContent=item.title;chatToUser();searchList=[]" class="searchItem" v-for="item in searchList" v-html="item.htmlTitle"></div>
                </div>
                <!--//搜索建議-->
                <div class="visitorEditor">
{{/*                    <div v-if="VisitorVoiceBtn!='true'"  v-on:click="audioDialog==true?audioDialog=false:audioDialog=true" class="visitorEditorVoice visitorFaceBtn"></div>*/}}
                    <el-input :placeholder="flyLang.textarea" show-word-limit :maxlength="VisitorMaxLength" :rows="2" type="textarea" resize="none" class="visitorEditorArea"  @focus="scrollBottom;showIconBtns=false" @blur="scrollBottom;showIconBtns=false" v-model="messageContent"  @keyup.native="inputNextText" v-on:keyup.enter.native="chatToUser">
                    </el-input>
{{/*                    <div v-if="VisitorFaceBtn!='true'" :title="flyLang.emotions" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" class="visitorEditorSmile visitorFaceBtn"></div>*/}}
{{/*                    <div v-if="VisitorUploadImgBtn!='true'&&VisitorPlusBtn=='true'" class="icono-image visitorEditorImg" id="uploadImg" v-on:click="uploadImg('/uploadimg')"></div>*/}}
{{/*                    <div v-if="VisitorPlusBtn!='true'" v-on:click="showIconBtns==true?showIconBtns=false:showIconBtns=true" v-show="messageContent==''" :title="flyLang.emotions" class="visitorEditorChoose"></div>*/}}
                </div>
                <el-button type="primary" size="mini"  class="visitorEditorBtn"   :disabled="sendDisabled||messageContent==''" v-on:click="chatToUser();showIconBtns=false"><{flyLang.sent}></el-button>

                <div class="footContact clear">
                    <a href="{{.CopyrightUrl}}" target="_blank">{{.CopyrightTxt}}</a>
                </div>
            </div>
        </div>
        <div class="chatArticle">
            <div style="padding: 8px;"><img style="width: 100%" :src="entInfo.intro_pic" v-if="entInfo.intro_pic" :title="entInfo.username"/></div>
            <h3 class="hotQuestionTitle">
                <img src="/static/images/fire.svg" class="fire"/><{flyLang.hotQuestionTitle}>
            </h3>
            <ul>
                <li v-on:click="messageContent=item;chatToUser()" class="chatArticleItem" v-for="item in topQuestionList"><a><{item}></a></li>
            </ul>
        </div>
        <div class="clear"></div>

        <!--//客服代碼-->
        <audio id="chatMessageAudio">
            <source id="chatMessageAudioSource"  />
        </audio>
        <audio id="chatMessageSendAudio">
            <source id="chatMessageSendAudioSource"  />
        </audio>


        <!--圖片預覽-->

        <el-image
                style="display: none;"
                ref="preview"
                class="hideImgDiv"
                :src="imgPreviewSrc[0]"
                :preview-src-list="imgPreviewSrc"
                z-index="9999"
        ></el-image>


        <!--評價-->
        <el-dialog
                center
                :title="flyLang.visitorCommentTitle"
                :close-on-click-modal="false"
                width="90%"
                :visible.sync="comment"
        >
            <div class="commentBox">
                <div style="line-height: 25px;"><{flyLang.commentDesc}></div>
                <el-rate v-model="commentScore" style="margin-bottom: 30px;"></el-rate>
                <el-input
                        type="textarea"
                        :rows="4"
                        v-model="commentContent">
                </el-input>
{{/*                <el-tooltip content="good" placement="top">*/}}
{{/*                    <span class="icono-smile" v-on:click="sendComment('good');comment = false"></span>*/}}
{{/*                </el-tooltip>*/}}
{{/*                <el-tooltip content="normal" placement="top">*/}}
{{/*                <span class="icono-meh" v-on:click="sendComment('normal');comment = false"></span>*/}}
{{/*                </el-tooltip>*/}}
{{/*                <el-tooltip content="bad" placement="top">*/}}
{{/*                <span class="icono-frown" v-on:click="sendComment('bad');comment = false"></span>*/}}
{{/*                </el-tooltip>*/}}
            </div>
            <span slot="footer" class="dialog-footer">
                <el-button type="primary"  v-on:click="sendComment();comment = false"><{flyLang.sent}></el-button>
            </span>
        </el-dialog>
        <!--//評價-->
        <!--地圖-->
        <iframe v-if="qqMap" style="position: fixed;top: 0;left: 0;z-index: 999999999" id="mapPage" width="100%" height="100%" frameborder=0
                src="https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=
OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77&referer=kefu">
        </iframe>
        <!--//地圖-->
        <el-dialog
                :title="flyLang.leave"
                :visible.sync="allOffline"
                width="100%"
                top="0">
            <el-input style="margin-bottom: 10px;" :placeholder="flyLang.email"  v-model="visitorContact.email"></el-input>
            <el-input style="margin-bottom: 10px;" :placeholder="flyLang.wechat"  v-model="visitorContact.weixin"></el-input>
            <el-input style="margin-bottom: 10px;" :placeholder="flyLang.realname"  v-model="visitorContact.name"></el-input>
            <el-input :placeholder="flyLang.content" type="textarea"  v-model="visitorContact.msg"></el-input>
            <span slot="footer" class="dialog-footer">
                        <el-button @click="sendEmailMsg"><{flyLang.s

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • AIR32F103CBT6的存儲容量加上206MHz頻率, 跑RTOS才能充分利用它的性能. 關於FreeRTOS的介紹和集成, 網路上已經有不少文章, 可以直接百度搜索查看, 這裡主要介紹一下項目中的FreeRTOS集成步驟和代碼說明. ...
  • 在物聯網、監控、感測器、金融等應用領域,數據在時間維度上流式的產生,而且數據量非常龐大。 例如我們經常看到的性能監控視圖,就是很多點在時間維度上描繪的曲線。 又比如金融行業的走勢數據等等。 我們想象一下,如果每個感測器或指標每100毫秒產生1個點,一天就是864000個點。 而感測器或指標是非... ...
  • 你聽過多少款無伺服器架構(Serverless)資料庫? 什麼是Serverless呢?簡單理解,Serverless 分為 FaaS 和 BaaS 兩個部分,其中 FaaS 指的是函數即服務,BaaS 是後端即服務。 舉個例子,用戶瀏覽網頁,可能涉及CDN資源。如果是靜態內容,從對象存儲下載照片、 ...
  • 1 、MySQL資料庫的性能監控 1.1、如何查看MySQL資料庫的連接數 連接數是指用戶已經創建多少個連接,也就是MySQL中通過執行 SHOW PROCESSLIST命令輸出結果中運行著的線程個數的詳情,如圖所示。 SHOW PROCESSLIST預設情況下只顯示前100條記錄的詳情,如果超過1 ...
  • 一. 單行函數:可以理解為向函數傳入一個參數,返回一個值。 單行函數是指對每一題記錄輸入值進行計算,並得到相應的計算結果,然後返回給用戶,也就是說,每條記錄作為一個輸入參數,經過函數計算得到每條記錄的計算結果。 單行函數 -- 函數舉例: select empno,ename, lower(enam ...
  • 作為一個批流統一的數據集成框架,秉承著易用、穩定、高效的目標,ChunJun於2018年4月29日在Github上將內核源碼正式開放。 從還被叫作FlinkX,寫下第一行代碼開始,ChunJun已經走過了第六個年頭,經歷了從分散式離線/實時數據同步插件,晉級為批流統一數據集成框架的蛻變時刻。越來越多 ...
  • 3D儲能、3D配電站、3D太陽能、三維儲能、使用three.js(webgl)搭建智慧樓宇、3D園區、3D廠房、3D倉庫、設備檢測、數字孿生、物聯網3D、物業3D監控、物業基礎設施可視化運維、3d建築,3d消防,消防演習模擬,3d庫房,webGL,threejs,3d機房,bim管理系統 ...
  • 隨著移動互聯網的飛速發展,無數移動APP琳琅滿目;在移動App的發展的基礎上,衍生了小程式、輕應用技術,它隨時可用,但又無需安裝卸載。uni-app 是一個使用 Vue.js 開發所有前端應用的框架,開發者編寫一套代碼,可發佈到iOS、Android、Web(響應式)、以及各種小程式等多個平臺。AI... ...
一周排行
    -Advertisement-
    Play Games
  • 概述:本文代碼示例演示瞭如何在WPF中使用LiveCharts庫創建動態條形圖。通過創建數據模型、ViewModel和在XAML中使用`CartesianChart`控制項,你可以輕鬆實現圖表的數據綁定和動態更新。我將通過清晰的步驟指南包括詳細的中文註釋,幫助你快速理解並應用這一功能。 先上效果: 在 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • openGauss(GaussDB ) openGauss是一款全面友好開放,攜手伙伴共同打造的企業級開源關係型資料庫。openGauss採用木蘭寬鬆許可證v2發行,提供面向多核架構的極致性能、全鏈路的業務、數據安全、基於AI的調優和高效運維的能力。openGauss深度融合華為在資料庫領域多年的研 ...
  • 概述:本示例演示了在WPF應用程式中實現多語言支持的詳細步驟。通過資源字典和數據綁定,以及使用語言管理器類,應用程式能夠在運行時動態切換語言。這種方法使得多語言支持更加靈活,便於維護,同時提供清晰的代碼結構。 在WPF中實現多語言的一種常見方法是使用資源字典和數據綁定。以下是一個詳細的步驟和示例源代 ...
  • 描述(做一個簡單的記錄): 事件(event)的本質是一個委托;(聲明一個事件: public event TestDelegate eventTest;) 委托(delegate)可以理解為一個符合某種簽名的方法類型;比如:TestDelegate委托的返回數據類型為string,參數為 int和 ...
  • 1、AOT適合場景 Aot適合工具類型的項目使用,優點禁止反編 ,第一次啟動快,業務型項目或者反射多的項目不適合用AOT AOT更新記錄: 實實在在經過實踐的AOT ORM 5.1.4.117 +支持AOT 5.1.4.123 +支持CodeFirst和非同步方法 5.1.4.129-preview1 ...
  • 總說周知,UWP 是運行在沙盒裡面的,所有許可權都有嚴格限制,和沙盒外交互也需要特殊的通道,所以從根本杜絕了 UWP 毒瘤的存在。但是實際上 UWP 只是一個應用模型,本身是沒有什麼許可權管理的,許可權管理全靠 App Container 沙盒控制,如果我們脫離了這個沙盒,UWP 就會放飛自我了。那麼有沒... ...
  • 目錄條款17:讓介面容易被正確使用,不易被誤用(Make interfaces easy to use correctly and hard to use incorrectly)限制類型和值規定能做和不能做的事提供行為一致的介面條款19:設計class猶如設計type(Treat class de ...
  • title: 從零開始:Django項目的創建與配置指南 date: 2024/5/2 18:29:33 updated: 2024/5/2 18:29:33 categories: 後端開發 tags: Django WebDev Python ORM Security Deployment Op ...
  • 1、BOM對象 BOM:Broswer object model,即瀏覽器提供我們開發者在javascript用於操作瀏覽器的對象。 1.1、window對象 視窗方法 // BOM Browser object model 瀏覽器對象模型 // js中最大的一個對象.整個瀏覽器視窗出現的所有東西都 ...