浏览代码

feat: 接入智能语音api

sunxiao 4 月之前
父节点
当前提交
490eb58273

+ 2 - 1
src/components/SearchItemWrapper/index.vue

@@ -35,6 +35,7 @@ defineProps({
   }
 
   .content {
+    flex: 1;
     width: 100%;
   }
 
@@ -58,7 +59,7 @@ defineProps({
   }
 
   // reset date-picker
-  :deep(.el-input__icon ) {
+  :deep(.el-range__icon ) {
     display: none;
   }
 }

+ 27 - 3
src/layout/components/HeaderGroup/TelCallBoard.vue

@@ -5,11 +5,14 @@ import useVoiceStore from "@/store/modules/voice";
 import iconCallOn from '@/assets/images/header/icon-call-on.svg';
 import iconCallOff from '@/assets/images/header/icon-call-off.svg';
 
+const { proxy } = getCurrentInstance();
+
 const voiceStore = useVoiceStore();
 const { callAnswered } = storeToRefs(voiceStore);
 
 const popoverVisible = ref(false);
 const popoverNums = ref(null);
+const inputNums = ref(null);
 
 const isPlay = ref(false);
 
@@ -19,10 +22,31 @@ const boardNums = [1, 2, 3, 4, 5, 6, 7, 8, 9,'*', 0, '#']
 // 计算属性 - 切换状态图标
 const phoneIcon = computed(() => isPlay.value ? iconCallOff : iconCallOn);
 
+const isValidPhoneNumber = phone => {
+  const regex = /^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$/;
+  
+  return regex.test(phone);
+}
+
 const handleMakingCall = () => {
-  voiceStore.onMakingCall(popoverNums.value);
+  if ( !isValidPhoneNumber(popoverNums.value) ) {
+    return proxy.$modal['msgWarning']('请输入正确的外呼电话号码');
+  }
+
   isPlay.value = !isPlay.value;
   popoverVisible.value = false;
+
+  voiceStore.onMakingCall(popoverNums.value);
+}
+
+// 拨打input电话
+const handleInpMakingCall = () => {
+  if (callAnswered.value) return;
+  if (!isValidPhoneNumber(inputNums.value)) {
+    return proxy.$modal['msgWarning']('请输入正确的外呼电话号码');
+  }
+  voiceStore.onMakingCall(inputNums.value);
+  inputNums.value = '';
 }
 
 // 事件 - 输入电话号码
@@ -40,7 +64,7 @@ const handleCleanNums = () => {
 <template>
   <div class="phone-inp-wrapper">
     <div class="inp-left">
-      <el-input-number v-model="num" :step="1" class="reset-inp-number" placeholder="请输入外呼电话号码" step-strictly :readonly="callAnswered"/>
+      <el-input-number v-model="inputNums" :step="1" class="reset-inp-number" placeholder="请输入外呼电话号码" step-strictly :readonly="callAnswered"/>
     </div>
     <div class="inp-right flex items-center">
       <el-popover
@@ -85,7 +109,7 @@ const handleCleanNums = () => {
       <div class="line"></div>
 
       <div :class="['pointer',{ forbid: callAnswered }]">
-        <img src="@/assets/images/header/icon-call-on.svg" alt=""></img>
+        <img src="@/assets/images/header/icon-call-on.svg" alt="" @click="handleInpMakingCall"></img>
       </div>
     </div>
   </div>

+ 3 - 2
src/layout/components/HeaderGroup/index.vue

@@ -27,9 +27,10 @@ watchEffect(() => {
 })
 
 // 修改在线状态
-const handlePopoverItem = ({ state, label }) => {
+const handlePopoverItem = async ({ state, label }) => {
+  state ? await voiceStore.setIdle() : await voiceStore.setBusy();
   systemState.value = state;
-  proxy.$modal[state ? 'msgSuccess' : 'msgWarning']('当前系统状态: ' + label);
+  proxy.$modal[state ? 'msgSuccess' : 'msgWarning']('当前坐席状态: ' + label);
 }
 
 // 

+ 2 - 2
src/layout/components/TelNoticeBar/index.vue

@@ -3,7 +3,7 @@ import { storeToRefs } from 'pinia';
 import useVoiceStore from "@/store/modules/voice";
 
 const voiceStore = useVoiceStore();
-const { callDialing, callTime, isMakingCall } = storeToRefs(voiceStore);
+const { callDialing, callTime, isMakingCall, telephoneNumber } = storeToRefs(voiceStore);
 
 // 切换展示窗口
 const handleToggle = () => {
@@ -41,7 +41,7 @@ const handleCallAnswered = () => {
           <span>牡丹江</span>
         </li>
         <li class="text-[18px] font-bold">
-          <span>18645683435</span>
+          <span>{{ telephoneNumber }}</span>
         </li>
       </ul>
     </div>

+ 3 - 3
src/layout/components/TelNoticeBox/index.vue

@@ -9,7 +9,7 @@ import VoiceToText from '@/components/VoiceToText';
 import { watchEffect } from 'vue';
 
 const voiceStore = useVoiceStore();
-const { callDialing, callTime, isMakingCall } = storeToRefs(voiceStore);
+const { callDialing, callTime, isMakingCall, telephoneNumber } = storeToRefs(voiceStore);
 
 const callRecords = ref([]);
 const cutOffWaters = ref([]);
@@ -54,7 +54,7 @@ const onSaveRemark = () => {
 
 // 弹框打开
 const onDialogOpen = async () => {
-  const { data } = await workbenchApi.getSessionInfo({ sessionId:199320 });
+  const { data } = await workbenchApi.getSessionInfo({ sessionId: voiceStore.sessionId });
   callRecords.value = data.callRecords;
   cutOffWaters.value = data.cutOffWaters;
   userInfos.value = data.userInfos;
@@ -84,7 +84,7 @@ const onDialogOpen = async () => {
               <img src="@/assets/images/tools/icon-avatar-call.svg" alt="">
             </div>
           </div>
-          <p class="text-[#fff] text-[18px] font-bold ml-[14px] mr-[54px]">18645683435</p>
+          <p class="text-[#fff] text-[18px] font-bold ml-[14px] mr-[54px]">{{ telephoneNumber }}</p>
           <p class="text-[#999]" v-show="callDialing">
             <span>通话中:</span>
             <span>{{ callTime }}</span>

+ 9 - 5
src/layout/index.vue

@@ -13,12 +13,16 @@ const useUser = useUserStore();
 
 onMounted(() => {
   if ( useVoice.isAuthPane ) {
-
-    workbenchApi.getSeatsByUser({ id: useUser.id }).then(res => {
-      // 用于初始化客户id
-      console.log("res", res);
+    workbenchApi.getSeatsByUser({ userId: useUser.id }).then(({ data }) => {
+      useVoice.HS_CTI_INSTANCE(data.outId);
+      setTimeout(() => {
+        try {
+          useVoice.setBusy();
+        } catch(error) {
+          console.log(error);
+        }
+      }, 1000);
     })
-    // useVoice.HS_CTI_INSTANCE(useUser.agentId);
   }
 })
 </script>

+ 1 - 2
src/permission.js

@@ -2,7 +2,7 @@ import router from './router'
 import { ElMessage } from 'element-plus'
 import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
-import { getToken,checkInKefu } from '@/utils/auth'
+import { getToken } from '@/utils/auth'
 import { isHttp } from '@/utils/validate'
 import { isRelogin } from '@/utils/request'
 import useUserStore from '@/store/modules/user'
@@ -36,7 +36,6 @@ router.beforeEach((to, from, next) => {
                 router.addRoute(route) // 动态添加可访问路由表
               }
             })
-            checkInKefu()
             next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
           })
         }).catch(err => {

+ 65 - 34
src/store/modules/voice.js

@@ -10,6 +10,8 @@ const useVoiceStore = defineStore('voice', () => {
 
   let HS_CTI = null;
 
+  const telephoneNumber = ref(null);
+
   // 是否是拨打电话   true: 外呼    false: 来电
   const isMakingCall = ref(false);
 
@@ -19,7 +21,7 @@ const useVoiceStore = defineStore('voice', () => {
   const callDialing = ref(false);
 
   // 横条来电显示
-  const noiceBarVisibleState = ref(true);
+  const noiceBarVisibleState = ref(false);
   // 盒子来电显示
   const noiceBoxVisibleState = ref(false);
   
@@ -31,11 +33,13 @@ const useVoiceStore = defineStore('voice', () => {
   const startTime = '';
   const endTime = '';
 
+  const sessionId = ref(null);
+
   // 是否有拨打电话权限
   const isAuthPane = computed(() => usePermission.routes.findIndex(({ name }) => name === 'Console') !== -1);
 
   // 拨打电话
-  const onMakingCall = (phoneNum) => {
+  const onMakingCall = async (phoneNum) => {
     if ( callAnswered.value ) {
       return ElMessage({
         message: '当前状态不允许操作,无法呼出',
@@ -43,6 +47,14 @@ const useVoiceStore = defineStore('voice', () => {
       })
     };
     
+    callAnswered.value = true;
+
+    const { data } = await makeCall();
+
+    sessionId.value = data;
+
+    telephoneNumber.value = phoneNum;
+
     isMakingCall.value = true;
 
     noiceBarVisibleState.value = true;
@@ -63,6 +75,9 @@ const useVoiceStore = defineStore('voice', () => {
     timer.resetTimer();
     const currentTimer = timer.updateDisplay();
     
+    // 挂断
+    // bye();
+
     ElMessage({
       message: '通话已经结束,挂断成功',
       type: 'success',
@@ -74,13 +89,15 @@ const useVoiceStore = defineStore('voice', () => {
 
   // 置忙
   const setBusy = () => {
-    HS_CTI.setBusy().then(res => { console.log(res) })
-  }
+    try {
+      HS_CTI.setBusy().then(res => { console.log(res) })
+    } catch(error) {
+      console.log("111",  error);
+    }
+  };
 
   // 置闲
-  const setIdle = () => {
-    HS_CTI.setIdle().then(res => { console.log(res) })
-  }
+  const setIdle = () => HS_CTI.setIdle();
 
   // 获取坐席状态
   const getAgentStatus = () => {
@@ -88,9 +105,7 @@ const useVoiceStore = defineStore('voice', () => {
   }
 
   // 主动外呼
-  const makeCall = () => {
-    HS_CTI.makeCall({ called: '' }).then(res => { console.log(res) })
-  }
+  const makeCall = called => HS_CTI.makeCall({ called })
 
   // 接听电话
   const answer = () => {
@@ -108,9 +123,8 @@ const useVoiceStore = defineStore('voice', () => {
   }
 
   // 下面开始事件监听
-  const listenScoketEvent = () => {
+  const listenScoketEvent = (CTIEvent) => {
     HS_CTI.on(CTIEvent.OnAgentWorkReport, ({ workStatus, description }) => {
-      message.info(`OnAgentWorkReport: ${workStatus}: ${description}`)
 
       // 销毁实例调用签出接口成功后 - 坐席签出
       if ( workStatus === -1 ) {
@@ -123,46 +137,55 @@ const useVoiceStore = defineStore('voice', () => {
 
       // 登录CTI 成功
       if ( workStatus === 0 ) {
-        ElMessage({
-          message: '坐席登入成功',
-          type: 'success',
-          plain: true,
-        })
+        // ElMessage({
+        //   message: '坐席登入成功',
+        //   type: 'success',
+        //   plain: true,
+        // })
       }
 
       // 登录CTI 成功
       if ( workStatus === 2 ) {
-        ElMessage({
-          message: '登入成功',
-          type: 'success',
-          plain: true,
-        })
+        // ElMessage({
+        //   message: '登入成功',
+        //   type: 'success',
+        //   plain: true,
+        // })
       }
 
       // 调用置闲接口成功后
       if ( workStatus === 2 ) {
-        ElMessage({
-          message: '坐席状态变更为:置闲',
-          type: 'success',
-          plain: true,
-        })
+        // ElMessage({
+        //   message: '坐席状态变更为:置闲',
+        //   type: 'success',
+        //   plain: true,
+        // })
       }
 
       // 调用置忙接口成功后
       if ( workStatus === 3 ) {
-        ElMessage({
-          message: '坐席状态变更为:置忙',
-          type: 'success',
-          plain: true,
-        })
+        // ElMessage({
+        //   message: '当前坐席状态:置忙',
+        //   type: 'warning',
+        //   plain: true,
+        // })
+      }
+
+      // 座席振铃
+      if ( workStatus === 5 ) {
+        // console.log("座席振铃");
+        // ElMessage({
+        //   message: '当前坐席状态:置忙',
+        //   type: 'warning',
+        //   plain: true,
+        // })
       }
     })
   }
 
   // 初始化 通话实例
-  const HS_CTI_INSTANCE = (agentId) => {
+  const HS_CTI_INSTANCE = (agent_id) => {
     const { Scene, getInstance, LoggerLevels , CTIEvent} = window.HS_CTI;
-
     HS_CTI = getInstance({
       // 业务返回的坐席outId
       agent_id,
@@ -177,9 +200,12 @@ const useVoiceStore = defineStore('voice', () => {
     })
 
     HS_CTI.init()
+
+    listenScoketEvent(CTIEvent);
   }
 
   return {
+    sessionId,
     isAuthPane,
 
     callTime,
@@ -194,8 +220,13 @@ const useVoiceStore = defineStore('voice', () => {
     noiceBarVisibleState,
     noiceBoxVisibleState,
 
+    // 电话号码
+    telephoneNumber,
+
     // 通话相关
     HS_CTI_INSTANCE,
+    setBusy,
+    setIdle
   }
 })
 

+ 0 - 3
src/views/login.vue

@@ -124,11 +124,8 @@ function handleLogin() {
   }).catch(() => {
     loading.value = false;
   });
-
 }
 
-
-
 function getCode() {
   getCodeImg().then(res => {
     captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;

+ 5 - 1
src/views/voice/analyse/index.vue

@@ -260,7 +260,11 @@ onMounted(() => {
             </li>
             <li class="status-item">
               <span class="text">累计通话时长</span>
-              <span class="num">{{ callRecordCountInfo.personTotal }}</span>
+              <p >
+                <span class="num">{{ callRecordCountInfo.personTotal }}</span>
+                <span class="text-[14px]"> h</span>
+              </p>
+              
             </li>
           </ul>
         </div>

+ 4 - 4
src/views/voice/call/index.vue

@@ -9,8 +9,6 @@ const router = useRouter();
 const { proxy } = getCurrentInstance();
 const { tableContainer, tableMaxHeight } = useTableHeight();
 
-const value = ref('');
-
 const dataPickerValue = ref([]);
 const loading = ref(false);
 const tableData = ref([]);
@@ -22,6 +20,7 @@ const queryParams = ref({
   pageSize: 10,
   userId: '',
   status: '',
+  category: '',
   phone: '',
 })
 
@@ -32,6 +31,7 @@ const handleCleanOptions = () => {
     pageSize: 10,
     userId: '',
     status: '',
+    category: '',
     phone: '',
   };
   dataPickerValue.value = [];
@@ -59,7 +59,7 @@ const handleDownload = ({ id }) => {
 }
 
 const getList = () => {
-  const [timeBegin, timeEnd] = dataPickerValue.value;
+  const [timeBegin, timeEnd] = dataPickerValue.value || [];
 
   loading.value = true;
 
@@ -116,7 +116,7 @@ onMounted(() => {
         </el-col>
         <el-col :span="6">
           <SearchItemWrapper label="通话类型">
-            <el-select v-model="value" placeholder="Select" size="large" :empty-values="[null, undefined]">
+            <el-select v-model="queryParams.category" placeholder="全部" size="large" :empty-values="[null, undefined]">
               <el-option label="全部" value="" />
               <el-option label="呼入" :value="0" />
               <el-option label="呼出" :value="1" />

+ 3 - 3
src/views/voice/notice/index.vue

@@ -21,7 +21,7 @@ const queryParams = ref({
 })
 
 const statusEnum = {
-  0: '未知',
+  // 0: '未知',
   1: '待停水',
   2: '停水中',
   3: '已恢复'
@@ -34,7 +34,7 @@ const onConfirm = async ({ id }) => {
 }
 
 const getList = async () => {
-  const [timeBegin, timeEnd] = dataPickerValue.value;
+  const [timeBegin, timeEnd] = dataPickerValue.value || [];
 
   loading.value = true;
 
@@ -99,7 +99,7 @@ onMounted(() => {
         <el-col :span="6">
           <SearchItemWrapper label="停水时间">
             <el-date-picker v-model="dataPickerValue" type="daterange" range-separator="-" start-placeholder="起始日期"
-              end-placeholder="结束日期" style="width: 100%;" :editable="false" value-format="YYYY-MM-DD"/>
+              end-placeholder="结束日期" style="width: 100%;" :editable="false" value-format="YYYY-MM-DD" :value-on-clear="null"/>
           </SearchItemWrapper>
         </el-col>
         <el-col :span="6">

+ 5 - 3
src/views/voice/workbench/index.vue

@@ -22,7 +22,7 @@ const tabEnum = ['通话呼入', '通话呼出'];
 
 const disabled = computed(() => loading.value || total.value == tabCallRecordList.value.length);
 
-const getTimeOfDayGreeting = computed(() => {
+const getTimeOfDayGreeting = () => {
   const date = new Date();
   const hour = date.getHours();
 
@@ -33,16 +33,18 @@ const getTimeOfDayGreeting = computed(() => {
   } else {
     return "晚上好";
   }
-})
+}
 
 // 切换tabs
 const handleChangeTab = (index) => {
   if ( queryParams.value.category != index ) {
     queryParams.value.pageNum = 1;
     queryParams.value.category = index;
+    queryParams.value.phone = '';
     tabCurrentActive.value = null;
     tabCallRecordList.value = [];
     initTabsData();
+    getTimeOfDayGreeting();
   }
 }
 
@@ -131,7 +133,7 @@ const loadMoreData = () => {
       <div class="empty-wrapper" v-show="!callDetails.id || tabCurrentActive === null">
         <img src="@/assets/images/workbench/img-empty.png" alt="">
         <p class="empty-text">
-          <span>Hi, {{ getTimeOfDayGreeting }}~</span>
+          <span>Hi, {{ getTimeOfDayGreeting() }}~</span>
           <span>欢迎登录智能语音客服</span>
         </p>
       </div>