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

来源: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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...