Bläddra i källkod

feat:增加按钮及关键词转人工客服功能

yangfeng 2 månader sedan
förälder
incheckning
e8e20eb7b7

+ 1 - 1
.env.development

@@ -1,6 +1,6 @@
 NODE_ENV=development
 VUE_APP_BASE_API='/jyapi'
-VUE_APP_BASE_URL='/page_pc_social'
+VUE_APP_BASE_URL='/'
 VUE_APP_BASE_PUBLIC='/'
 VUE_APP_WEBSOCKET_API='/socket'
 VUE_APP_DEFAULT_LOGO='https://www.jianyu360.cn/common-module/public/image/auto.png'

+ 9 - 0
src/api/modules/knowledge.js

@@ -54,3 +54,12 @@ export function aiChatFindAnswer (data) {
     data
   })
 }
+
+// 自动转人工的关键词/按钮
+export function getTurnCustomerKeywords (data) {
+  return request({
+    url: '/knowledge/keywordList',
+    method: 'post',
+    data
+  })
+}

BIN
src/assets/image/hand-gray.png


+ 45 - 11
src/components/action-list/index.vue

@@ -45,9 +45,10 @@
         </div>
       </el-popover>
     </div>
-    <div v-if="isLink">
+    <div v-if="isLink && form !== 'aside'" class="action-customer-container">
       <slot></slot>
-      <div class="turn-btn" @click.stop="onTurn">转人工</div>
+      <div class="action-customer-item turn-btn" @click.stop="onTurn('转人工')">转人工</div>
+      <div class="action-customer-item" v-for="(item, index) in btn" :key="index"  @click.stop="onTurn(item)">{{item}}</div>
       <slot></slot>
     </div>
   </div>
@@ -89,6 +90,12 @@ export default {
     isRobot: {
       type: Number,
       default: 0
+    },
+    btn:{
+      type: Array,
+      default: () => {
+        return []
+      }
     }
   },
   data () {
@@ -137,12 +144,16 @@ export default {
       return arr
     }
   },
+  created() {
+    // 是否是iframe侧边栏打开的
+    this.form = this.$route.query.from
+  },
   methods: {
     onClick (data) {
       this.$emit('action', data)
     },
-    onTurn () {
-      this.$emit('turn')
+    onTurn (data) {
+      this.$emit('turn', data)
     },
     async uploadImage (file) {
       // this.$emit('upload-image', file.file)
@@ -208,12 +219,24 @@ export default {
 </style>
 <style lang="scss" scoped>
 .action-container{
+  position: relative;
   width: 100%;
   height: 48px;
   display: flex;
   justify-content: space-between;
   padding: 8px 0 16px;
   box-sizing: border-box;
+  &::after{
+    content: '';
+    position: absolute;
+    top: 0;
+    width: calc(100% + 64px);
+    height: 1px;
+    background: #DADADA;
+    left: -32px;
+    right: -32px;
+    z-index: 99;
+  }
   .action-list{
     display: flex;
     align-items: center;
@@ -254,14 +277,25 @@ export default {
   }
   .rate:hover{
       background-image: url('~@/assets/image/rate_h.png');
-    }
-  .turn-btn{
-    padding: 4px 16px 4px 36px;
-    border: 1px solid $color_main;
-    border-radius: 4px;
-    color: $color_main;
+  }
+  .action-customer-container{
+    display: flex;
+    align-items: center;
+  }
+  .action-customer-item{
+    padding: 3px 9px;
+    margin-right: 8px;
     font-size: 12px;
-    background: url('~@/assets/image/hand.png') no-repeat 16px center;
+    line-height: 18px;
+    border-radius: 4px;
+    border: 1px solid #E0E0E0;
+    background: #fff;
+    color: #686868;
+    cursor: pointer;
+  }
+  .turn-btn{
+    padding-left: 44px;
+    background: url('~@/assets/image/hand-gray.png') no-repeat 8px center #fff;
     background-size: 18px 18px;
     cursor: pointer;
   }

+ 1 - 1
src/views/CustomerServiceView.vue

@@ -92,7 +92,7 @@ import { brace } from '@/utils/mixins/brace'
 import {cloneDeep} from "lodash";
 
 export default {
-  name: 'CustomerView',
+  name: 'CustomerServiceView',
   mixins: [brace],
   components: {
     MessageList,

+ 115 - 47
src/views/CustomerView.vue

@@ -28,7 +28,11 @@
               </div>
             </transition>
             <div class="group-chart-footer">
-              <ChartAction :options="getActionList" :isLink="isTurnPeople" :isRobot="isShowNps" @action="onChartAction" @upload-image="onUploadImage" @upload-attach="onUploadAttach" @turn="onTurnPeople"  @close="onCloseScreen"></ChartAction>
+              <div v-if="isTurnPeople && from === 'aside'" class="action-customer-aside-container">
+                <div class="action-customer-item turn-btn" @click.stop="onKeywordsBtn('转人工')">转人工</div>
+                <div class="action-customer-item" v-for="(item, index) in customerBtnList" :key="index"  @click.stop="onKeywordsBtn(item)">{{item}}</div>
+              </div>
+              <ChartAction :options="getActionList" :btn="customerBtnList" :isLink="isTurnPeople" :isRobot="isShowNps" @action="onChartAction" @upload-image="onUploadImage" @upload-attach="onUploadAttach" @turn="onKeywordsBtn"  @close="onCloseScreen"></ChartAction>
               <ChartInput ref="chartInput" v-model="msgVal" @on-input="onChartInput" @confirm="onSendMsg" @m-blur="messageBlur">
                 <div class="hint-box" v-show="hintShow">
                   <img src="@/assets/image/close_.png" alt="" class="close" @click="hintShow = false">
@@ -83,8 +87,8 @@ import { Container, Main, Avatar, Badge, Button, Input, Divider, Drawer } from '
 import AsideView from '@/components/aside/'
 import MessageList from '@/components/MessageList'
 import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
-import { getChartGroupInfo, robotInfo, createChatSession, getUserList, getMessageCount, closeChatSession, getWebSocketNode, robotFindAnswer, customerEval, joinChartGroup, createChartGroup, guessWantAsk } from '@/api/modules/'
-import { getLink, debounce, removeHtmlStyle, setTrack } from '@/utils/'
+import { getChartGroupInfo, robotInfo, createChatSession, getUserList, getMessageCount, closeChatSession, getWebSocketNode, robotFindAnswer, customerEval, joinChartGroup, createChartGroup, guessWantAsk, getTurnCustomerKeywords } from '@/api/modules/'
+import { getLink, debounce, removeHtmlStyle, setTrack, dateFormatter } from '@/utils/'
 import CommunicateBook from '@/components/communicateBook/index'
 import GroupInfo from '@/components/group-info/'
 
@@ -95,7 +99,7 @@ import ChartInput from '@/components/MessageInput.vue'
 import Rate from '@/components/rate/'
 
 export default {
-  name: 'CustomerServiceView',
+  name: 'CustomerView',
   components: {
     AsideView,
     MessageList,
@@ -175,7 +179,9 @@ export default {
       historyQuestion: [],
       from: '',
       resource: '',
-      showAside: false
+      showAside: false,
+      customerBtnList: [], // // 可转人工客服的按钮列表
+      customerKeywords: [] // 可转人工客服的关键词列表
     }
   },
   computed: {
@@ -306,6 +312,7 @@ export default {
       this.getUserNoLoginFn()
     }
     this.getMessageCountFn()
+    this.getTurnCustomerKey()
   },
   mounted () {
     this.$bus.$on('bus_customerText', this.onCustomerText)
@@ -529,10 +536,7 @@ export default {
             // 更新聊天列表最后消息及未读数量
             this.getUserListLastMsg(rUserId, 1, content, time)
           }
-          // 客服离线状态离线状态
-          if (noCustom) {
-            console.log('客服离线, 发送机器人欢迎语')
-            const apiParams = {
+          const apiParams = {
               ownType: 2,
               title: '机器人首次回复',
               content: this.excuse.welcome,
@@ -552,6 +556,9 @@ export default {
               headImg: this.autoUserInfo.img,
               nickName: this.autoUserInfo.name
             }
+          // 客服离线状态离线状态
+          if (noCustom) {
+            console.log('客服离线, 发送机器人欢迎语')
             // 只有离线状态且content返回值为空的情况下(自动转人工),才需要展示机器人欢迎语,其它情况不需要展示
             if (!content) {
               this.onSendCommon({ apiParams, socketParams }, true)
@@ -829,10 +836,10 @@ export default {
               content: `${this.autoUserInfo.name}和剑鱼标讯建立了会话`
             })
             // 创建完会话直接转人工
-            setTimeout(() => {
-              // 自动转人工
-              this.onTurnPeople('', 'auto')
-            }, 1000)
+            // setTimeout(() => {
+            //   // 自动转人工
+            //   this.onTurnPeople('', 'auto')
+            // }, 1000)
           })
         }
       }
@@ -856,42 +863,44 @@ export default {
         this.other.headImg = this.other.userType === 1 ? data?.headimage : ''
         callback && callback(data)
         // 首次回复 需保存
-        // if (isSave) {
-        //   const apiParams = {
-        //     ownType: 2,
-        //     title: '机器人首次回复',
-        //     content: data.reply?.Content,
-        //     item: 8,
-        //     itemType: 4, // 机器人信息
-        //     sendId: this.other.sessionId, // 发送人标识(客服发送信息时该值为会话标识,用户聊天时该值不传)
-        //     type: 1,
-        //     link: '',
-        //     fool: 2
-        //   }
-        //   // 新增:与机器人聊天也发websocket告知
-        //   const socketParams = {
-        //     type: 27,
-        //     rUserId: this.other.userId,
-        //     rUserType: this.other.userType,
-        //     contentType: 1,
-        //     headImg: this.autoUserInfo.img,
-        //     nickName: this.autoUserInfo.name
-        //   }
-        //   // 延迟1秒
-        //   setTimeout(() => {
-        //     this.onSendCommon({ apiParams, socketParams }, true)
-        //   }, 1000)
-        //   if (this.isShowNps === 0) {
-        //     this.robotOvertimeReply()
-        //   }
-        // }
+        if (isSave) {
+          const apiParams = {
+            ownType: 2,
+            title: '机器人首次回复',
+            content: data.reply?.Content,
+            item: 8,
+            itemType: 4, // 机器人信息
+            sendId: this.other.sessionId, // 发送人标识(客服发送信息时该值为会话标识,用户聊天时该值不传)
+            type: 1,
+            link: '',
+            fool: 2
+          }
+          // 新增:与机器人聊天也发websocket告知
+          const socketParams = {
+            type: 27,
+            rUserId: this.other.userId,
+            rUserType: this.other.userType,
+            contentType: 1,
+            headImg: this.autoUserInfo.img,
+            nickName: this.autoUserInfo.name
+          }
+          // 延迟1秒
+          setTimeout(() => {
+            this.onSendCommon({ apiParams, socketParams }, true)
+          }, 1000)
+          if (this.isShowNps === 0) {
+            this.robotOvertimeReply()
+          }
+        }
       }
     },
     // 机器人根据问题自动回复
     async robotAutoReply (question, type = 1) {
       // 如果是跟用户或者人工聊则不用自动回复
-      if (this.other.userType >= 2 || !this.other.isRobot) return
-      const kfKeywords = ['客服', '人工', '人工客服']
+      if (this.other.userType >= 2 || !this.other.isRobot || !this.userSessionBadge) return
+      // const kfKeywords = ['客服', '人工', '人工客服']
+      const kfKeywords = this.customerKeywords
+      console.log(kfKeywords, 'kfKeywords')
       if (kfKeywords.includes(question.replace(/\s+/g, ''))) {
         // 转人工客服
         this.onTurnPeople(question.replace(/\s+/g, ''), '')
@@ -1485,7 +1494,6 @@ export default {
       this.groupPerson = data
     },
     onFullScreen () {
-      console.log('全屏')
       try {
         // 通知父页面关闭当前会话窗口,并新打开会话页面
         window.parent.postMessage({
@@ -1494,12 +1502,49 @@ export default {
       } catch (error) {}
     },
     onCloseScreen () {
-      console.log('关闭会话窗口')
       try {
         window.parent.postMessage({
           action: 'close'
         }, location.origin)
       } catch (error) {}
+    },
+    // 获取可转人工的关键词/按钮名称
+    async getTurnCustomerKey () {
+      const { data } = await getTurnCustomerKeywords()
+      if (data) {
+       this.customerBtnList = data.filter(v => {
+        return v.state && v.state === 1 
+       }).map(n => n.name)
+       this.customerKeywords = data.map(v => v.name)
+      }
+    },
+    // 点击转人工/咨询/使用等按钮生成系统消息
+    onKeywordsBtn(content) {
+      const params = {
+        ownType: 1,
+        title: '点击按钮',
+        item: 8,
+        itemType: 6, // 系统信息
+        receiveId: this.other.sessionId,
+        type: 1,
+        link: '',
+        content: `${dateFormatter(new Date().getTime())} 点击了“${content}”`
+      }
+      this.saveMessageAction(params).then(res => {
+        const { error_code: code } = res
+        if (code === 0) {
+          params.create_time = res.data.create_time
+          params.fool = 2
+          params.messageId = res.data.messageId
+          this.$refs.chartContainer.pushMsg(params)
+          setTimeout(() => {
+            this.onTurnPeople(content)
+          }, 1000)
+        } else {
+          params.error = true
+          this.$refs.chartContainer.pushMsg(params)
+        }
+      })
     }
   }
 }
@@ -1550,7 +1595,7 @@ export default {
     max-height: 310px;
     padding: 0 32px 24px;
     background: #f6f6f6;
-    border-top: 1px solid #DADADA;
+    //border-top: 1px solid #DADADA;
   }
   .rate-container{
     padding: 0 32px;
@@ -1629,6 +1674,29 @@ export default {
     }
   }
 }
+.action-customer-aside-container{
+  display: flex;
+  align-items: center;
+  justify-content: flex-end;
+  padding: 8px 0;
+  .action-customer-item{
+    padding: 3px 9px;
+    margin-right: 8px;
+    font-size: 12px;
+    line-height: 18px;
+    border-radius: 4px;
+    border: 1px solid #E0E0E0;
+    background: #fff;
+    color: #686868;
+    cursor: pointer;
+    &.turn-btn{
+      padding-left: 44px;
+      background: url('~@/assets/image/hand-gray.png') no-repeat 8px center #fff;
+      background-size: 18px 18px;
+      cursor: pointer;
+    }
+  }
+}
 </style>
 <style lang="scss">
 .aside-layout {

+ 10 - 5
vue.config.js

@@ -39,27 +39,32 @@ module.exports = defineConfig({
     historyApiFallback: true,
     proxy: {
       '^/jyapi': {
-        target: 'https://jybx-webtest.jydev.jianyu360.com/',
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/publicapply': {
-        target: 'https://jybx-webtest.jydev.jianyu360.com/',
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/entbase': {
-        target: 'https://jybx-webtest.jydev.jianyu360.com/',
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/bigmember': {
-        target: 'https://jybx-webtest.jydev.jianyu360.com/',
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
         changeOrigin: true,
         logLevel: 'debug'
       },
       '^/aiChat': {
-        target: 'https://webdev-webtest.jydev.jianyu360.com/',
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
+        changeOrigin: true,
+        logLevel: 'debug'
+      },
+      '^/jypay': {
+        target: 'https://jybx3-webtest.jydev.jianyu360.com/',
         changeOrigin: true,
         logLevel: 'debug'
       }

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 619 - 639
yarn.lock


Vissa filer visades inte eftersom för många filer har ändrats