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

来源: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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...