ソースを参照

feat: 近期问题修改

sunxiao 2 ヶ月 前
コミット
cb32166f12

+ 13 - 0
src/api/voice/shunt.js

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+export const shuntApi = {
+  getBucketRecordList: params => request({
+    url: `/business/bucketRecord/list`,
+    params
+  }),
+  postBucketRecord: data => request({
+    url: `/business/bucketRecord/editConf`,
+    method: 'put',
+    data
+  })
+}

+ 7 - 0
src/assets/images/svgs/icon-save.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
+<path d="M2 3C2 2.44772 2.44772 2 3 2H11.4271L14 4.40217V13C14 13.5523 13.5523 14 13 14H3C2.44772 14 2 13.5523 2 13V3Z" stroke="#165DFF" stroke-linejoin="round"/>
+<path d="M8.0026 2L7.99984 4.46153C7.99984 4.57483 7.8506 4.66667 7.6665 4.66667H4.99984C4.81574 4.66667 4.6665 4.57483 4.6665 4.46153V2H8.0026Z" stroke="#165DFF" stroke-linejoin="round"/>
+<path d="M3 2H11.4271" stroke="#165DFF" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.6665 8.66602H11.3332" stroke="#165DFF" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M4.6665 11.334H8.0026" stroke="#165DFF" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>

+ 43 - 17
src/layout/components/HeaderGroup/index.vue

@@ -4,8 +4,8 @@ import useVoiceStore from "@/store/modules/voice";
 import { ElMessageBox } from 'element-plus';
 import TelCallBoard from './TelCallBoard.vue'
 import useUserStore from '@/store/modules/user';
-import { watchEffect } from 'vue';
 import ResetPwdDialog from '@/components/ResetPwdDialog'
+import { workbenchApi } from '@/api/voice/workbench';
 
 const voiceStore = useVoiceStore();
 const { proxy } = getCurrentInstance();
@@ -15,33 +15,49 @@ const { callAnswered, systemState } = storeToRefs(voiceStore);
 const userStore = useUserStore();
 const dialogVisible = ref(false);
 
-
 const SYSTEM_STAT_ENUM = [
   {label: '置闲', state: true, icon: 'online-icon'},
   {label: '置忙', state: false, icon: 'offline-icon'}
 ];
 
-watchEffect(() => {
-  // const routes = usePermission.routes;
-  // isShowCallPanel.value = routes.findIndex(({ name }) => name === 'Console') !== -1;
-})
+
 
 // 修改在线状态
 const handlePopoverItem = async ({ state, label }) => {
-  // console.log( voiceStore, voiceStore.HSCTIERRORCODE );
-  // console.log( "voiceStore.HSCTIERRORCODE", voiceStore.HSCTIERRORCODE );
-  // console.log(" voiceStore.HSCTIERRORCODE ",  voiceStore.HSCTIERRORCODE  );
-  // if( voiceStore.HSCTIERRORCODE == 100002) {
-  //   return alert("有问题");
-  // }
+  /**
+   * HSCTIERRORCODE :
+   * 100001: SDK 状态不可用,CTIStatus 的状态为 Terminated
+   * 100002: 获取坐席媒体权限失败
+   * 
+   * AGENTSTATUS : 
+   * 1 置忙 2 置闲
+   * */ 
+
+  /**
+   * 疑问:中途切换 获取坐席状态
+   * */ 
   await voiceStore.getAgentStatus();
-  
+  console.log( "坐席状态", voiceStore.AGENTSTATUS );
+  console.log( "系统状态", voiceStore.HSCTIERRORCODE );
   if ( voiceStore.AGENTSTATUS == 0 ) {
-    return proxy.$modal.msgError('当前当前坐席没有嵌入成功, 状态切换失败');
+    workbenchApi.getSeatsByUser({ userId: userStore.id }).then(async({ data }) => {
+      if (!data) return;
+      voiceStore.HS_CTI_INSTANCE(data.outId);
+      setTimeout(async () => {
+        await voiceStore.getAgentStatus();
+        console.log( "坐席状态", voiceStore.AGENTSTATUS );
+        console.log( "系统状态", voiceStore.HSCTIERRORCODE );
+        // if ( !([100001, 100002].includes( Number(voiceStore.HSCTIERRORCODE)) ) ) {
+        if ( voiceStore.AGENTSTATUS != 0 ) {
+          state ? await voiceStore.setIdle() : await voiceStore.setBusy();
+          proxy.$modal[state ? 'msgSuccess' : 'msgWarning']('当前坐席状态: ' + label);
+        }
+      }, 3000);
+    })
+  } else {
+    state ? await voiceStore.setIdle() : await voiceStore.setBusy();
+    proxy.$modal[state ? 'msgSuccess' : 'msgWarning']('当前坐席状态: ' + label);
   }
-
-  state ? await voiceStore.setIdle() : await voiceStore.setBusy();
-  proxy.$modal[state ? 'msgSuccess' : 'msgWarning']('当前坐席状态: ' + label);
 }
 
 // 
@@ -78,6 +94,16 @@ const logout = () => {
   }).catch(() => { });
 }
 
+onMounted( () => {
+  if ( voiceStore.isAuthPane ) {
+    setTimeout(async() => {
+      // await voiceStore.getAgentStatus();
+      // voiceStore.AGENTSTATUS 
+      
+    }, 2000)
+  }
+})
+
 </script>
 
 <template>

+ 37 - 15
src/layout/index.vue

@@ -21,31 +21,53 @@ onMounted(() => {
     workbenchApi.getSeatsByUser({ userId: useUser.id }).then(async({ data }) => {
       if (!data) return;
       outId.value = data.outId;
+      
       useVoice.HS_CTI_INSTANCE(data.outId);
 
       const voiceStatus = sessionStorage.getItem('VOICE_STATUS');
 
-      await useVoice.getAgentStatus();
+      setTimeout(async () => {
+        /**
+         * 如果用户在有登入的情况下,中途关闭麦克风,获取坐席状态仍是 1( getAgentStatus )
+         * HSCTIERRORCODE :- - 100001: SDK 状态不可用,CTIStatus 的状态为 Terminated 100002: 获取坐席媒体权限失败
+         * AGENTSTATUS : 1 置忙 2 置闲
+         * */ 
 
-      if ( voiceStatus && [1,2].includes(useVoice.AGENTSTATUS)) {
-        if ( voiceStatus === 'busy' ) {
-          setTimeout(() => useVoice.setBusy(), 1000);
+        // 目前先只考虑 100001 100002 错误码 处理
+        if ( [100001, 100002].includes( Number(useVoice.HSCTIERRORCODE) ) ) {
+          // 有错误
+          useVoice.systemState = false;
         } else {
-          setTimeout(() => useVoice.setIdle(), 1000);
-        }
-      } else {
-        setTimeout(() => {
-          try {
-            useVoice.AGENTSTATUS!=0 && useVoice.setIdle();
-          } catch(error) {
-            console.log(error);
+          // 正常
+          await useVoice.getAgentStatus();
+
+          if ( voiceStatus && [1, 2].includes( Number(useVoice.AGENTSTATUS) )) {
+            voiceStatus === 'busy' ? useVoice.setBusy() : useVoice.setIdle();
+          } else {
+            useVoice.AGENTSTATUS != 0 && useVoice.setIdle();
           }
-        }, 3000);
-      }
+        }
+      }, 2000);
+
+      // if ( voiceStatus && [1,2].includes(useVoice.AGENTSTATUS)) {
+      //   if ( voiceStatus === 'busy' ) {
+      //     setTimeout(() => useVoice.setBusy(), 1000);
+      //   } else {
+      //     setTimeout(() => useVoice.setIdle(), 1000);
+      //   }
+      // } else {
+      //   setTimeout(() => {
+      //     try {
+      //       useVoice.AGENTSTATUS!=0 && useVoice.setIdle();
+      //     } catch(error) {
+      //       console.log(error);
+      //     }
+      //   }, 3000);
+      // }
     })
     intervalTimer.value = setInterval(async () => {
       const sdkRegister = Cookies.get('sdkRegister')
-      console.log(useVoice.AGENTSTATUS)
+      // console.log(useVoice.AGENTSTATUS)
       if (!sdkRegister && !useVoice.noiceBarVisibleState &&useVoice.AGENTSTATUS !=0) {
         await useVoice.unInit()
         useVoice.HS_CTI_INSTANCE(outId.value);

+ 1 - 0
src/permission.js

@@ -33,6 +33,7 @@ router.beforeEach((to, from, next) => {
           isRelogin.show = false
           usePermissionStore().generateRoutes().then(accessRoutes => {
             if (accessRoutes.length == 0) {
+              ElMessage.error("当前账号未配置角色权限,请联系管理员")
                next({ path: '/login' })
                return
             }

+ 11 - 4
src/store/modules/voice.js

@@ -12,6 +12,8 @@ const useVoiceStore = defineStore('voice', () => {
   const usePermission = usePermissionStore();
   const userStore = useUserStore();
 
+  let isLoad = false;
+
   let HS_CTI = null;
 
   // 当前系统状态 - 闲 or 忙
@@ -144,9 +146,9 @@ const useVoiceStore = defineStore('voice', () => {
 
   // 获取坐席状态
   const getAgentStatus = async () => {
-    return await HS_CTI.getAgentStatus().then(res => { 
-      AGENTSTATUS.value = res.data;
-    })
+    console.log( "HS_CTI", HS_CTI );
+    const res = await HS_CTI.getAgentStatus();
+    AGENTSTATUS.value = res.data;
   }
 
   // 主动外呼
@@ -283,12 +285,17 @@ const useVoiceStore = defineStore('voice', () => {
       domainName: VITE_HS_CTI_BASE_URL
     })
 
+    
     // HS_CTI.domainName = VITE_HS_CTI_BASE_URL
 
     HS_CTI.init();
     setCookieWithExpireAt1150('sdkRegister', 'true',24,0)
     // getAgentStatus()
-    listenScoketEvent(CTIEvent);
+   
+    if (!isLoad) {
+      listenScoketEvent(CTIEvent);
+    }
+    isLoad = true
   }
 
   return {

+ 1 - 1
src/views/login.vue

@@ -148,7 +148,7 @@ function handleLogin() {
         // }
       } else {
         loading.value = false;
-        proxy.$modal.msgError("当前账号未分配权限");
+        ElMessage.error("当前账号未配置角色权限,请联系管理员")
         removeToken();
 
       }

+ 243 - 0
src/views/voice/aiWhiteList/index.vue

@@ -0,0 +1,243 @@
+<script setup>
+import { ElMessage } from 'element-plus'
+import SearchItemWrapper from '@/components/SearchItemWrapper';
+import useTableHeight from '@/composables/useTableHeight';
+import { waiteListApi } from '@/api/voice/whiteList';
+
+const { tableContainer, tableMaxHeight } = useTableHeight();
+
+const loading = ref(false);
+const total = ref(0);
+const tableData = ref([]);
+const dialogVisible = ref(false);
+const formRef = ref(null);
+const rules = {
+  phone: [
+    { required: true, message: '请输入电话号码', trigger: 'blur' },
+  ],
+  description: [
+    { required: true, message: '请输入备注', trigger: 'blur' }
+  ]
+}
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+  phone: '',
+  description: ''
+})
+
+const formData = ref({
+  id: '',
+  phone: '',
+  description: ''
+});
+
+// 气泡弹窗 - 是否删除该项
+const onConfirm = async (row) => {
+  await waiteListApi.delWhiteList(row.id);
+  getList();
+  ElMessage.success('删除成功');
+}
+
+const getList = async () => {
+
+  loading.value = true;
+
+  const { rows, total: t } = await waiteListApi.getWhiteList({...queryParams.value, type: 1});
+
+  tableData.value = rows
+  
+  loading.value = false;
+
+  total.value = t;
+};
+
+// 弹窗 - 新增
+const handleAddPhoneNum = () => {
+  dialogVisible.value = true;
+}
+
+// 弹窗 - 编辑
+const handleEditRow = ({ id, phone, description }) => {
+  dialogVisible.value = true;
+  formData.value = { id, phone, description };
+}
+
+// 弹窗 - 确定
+const onDialogConfirm = () => {
+  formRef.value.validate(async (valid) => {
+    if ( valid ) {
+      dialogVisible.value = false;
+      if ( !formData.value.id ) {
+        await waiteListApi.postWhiteList({...formData.value, type: 1});
+        ElMessage.success('新增电话号码成功');
+      } else {
+        await waiteListApi.putWhiteList({...formData.value, type: 1});
+        ElMessage.success('更新电话号码成功');
+      }
+      getList();
+    }
+  })
+}
+
+// 弹窗 - 取消
+const onDialogCancel = () => {
+  formData.value = {
+    id: '',
+    phone: '',
+    description: ''
+  };
+  dialogVisible.value = false;
+}
+
+// 清除检索条件
+const handleCleanOptions = () => {
+  queryParams.value = {
+    pageNum: 1,
+    pageSize: 10,
+    phone: '',
+    description: '' 
+  };
+  getList();
+}
+
+onMounted(() => {
+  getList();
+})
+
+</script>
+
+<template>
+  <div class="notice-viewprot">
+    <div class="search-card">
+      <el-row :gutter="24" class="mb-[24px]">
+        <el-col :span="6">
+          <SearchItemWrapper>
+            <el-input class="search-input" placeholder="电话号码" v-model="queryParams.phone"></el-input>
+          </SearchItemWrapper>
+        </el-col>
+        <el-col :span="6">
+          <SearchItemWrapper>
+            <el-input class="search-input" placeholder="备注" v-model="queryParams.description"></el-input>
+          </SearchItemWrapper>
+        </el-col>
+        <el-col :span="6" :offset="6">
+          <div class="flex items-center justify-end space-x-[10px]">
+            <div class="custom-btn custom-btn_primary" @click="getList">搜索</div>
+            <div class="custom-btn custom-btn_default" @click="handleCleanOptions">重置</div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+    
+    <div class="add-btn-card space-x-[15px]">
+      <div class="btn space-x-[5px]" @click="handleAddPhoneNum">
+        <el-icon><CirclePlus /></el-icon>
+        <span>新增机器人白名单</span>
+      </div>
+      <span class="text-[#999]">本功能主要用于测试, 配置完成后1分钟生效。</span>
+    </div>
+
+    <div class="table-card">
+      <div style="height: 100%;" ref="tableContainer">
+        <el-table :data="tableData" style="width: 100%" :max-height="tableMaxHeight" v-loading="loading">
+          <el-table-column prop="phone" label="电话号码" align="center" :width="130"/>
+          <el-table-column prop="description" label="备注" align="center" />
+        </el-table>
+        
+        <pagination
+            v-show="total >= 0"
+            :total="total"
+            v-model:page="queryParams.pageNum"
+            v-model:limit="queryParams.pageSize"
+            @pagination="getList"
+          />
+      </div>
+    </div>
+
+    <el-dialog
+      v-model="dialogVisible"
+      title="新增电话号码"
+      width="600"
+      modal-class="custom-workbench-dialog"
+      align-center
+      @closed="resetDialogStatus"
+    >
+      <template #header>
+        <div class="dialog-header">
+          <h4>新增电话号码</h4>
+        </div>
+      </template>
+      <div class="dialog-body">
+        <div class="dialog-form_inner">
+          <el-form :model="formData" label-width="auto" style="width: 100%;" :rules="rules" ref="formRef">
+            <el-form-item label="电话号码" prop="phone">
+              <el-input v-model.trim="formData.phone" placeholder="请输入"/>
+            </el-form-item>
+            <el-form-item label="备注" prop="description">
+              <el-input v-model.trim="formData.description" type="textarea" :autosize="{ minRows: 5, maxRows: 6 }" resize="none" placeholder="请输入"/>
+            </el-form-item>
+          </el-form>
+        </div>
+      </div>
+      <template #footer>
+        <div class="dialog-footer space-x-[14px]">
+          <div class="custom-btn custom-btn_primary" @click="onDialogConfirm">确定</div>
+          <div class="custom-btn custom-btn_default" @click="onDialogCancel">取消</div>
+        </div>
+      </template>
+    </el-dialog>
+    
+  </div>
+</template>
+
+
+<style lang="scss" scoped>
+.notice-viewprot {
+  display: flex;
+  flex-flow: column;
+  width: 100%;
+  height: 100%;
+  padding: 28px 24px 18px 24px;
+  border-radius: 8px;
+  background: #fff;
+
+  .search-card {
+    border-bottom: 1px dashed #E5E6EB;
+  }
+
+  .add-btn-card {
+    display: flex;
+    align-items: center;
+    justify-content: flex-start;
+    margin: 20px 0;
+
+    .btn {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 14px;
+      border-radius: 8px;
+      padding: 7px 16px;
+      background: #165DFF;
+      color: #fff;
+      cursor: pointer;
+    }
+  }
+  
+  .table-card {
+    height: 100%;
+  }
+}
+.dialog-form_inner {
+  width: 500px;
+  margin: 0 auto;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.dialog-footer {
+  display: flex;
+  justify-content: center;
+}
+</style>

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

@@ -275,7 +275,6 @@ onMounted(() => {
     padding: 20px;
     border-radius: 8px;
     background: #fff;
-
   }
 
   .title {

+ 187 - 0
src/views/voice/shunt/index.vue

@@ -0,0 +1,187 @@
+<script setup>
+import { shuntApi } from '@/api/voice/shunt';
+import useTableHeight from '@/composables/useTableHeight';
+import { ElMessage } from 'element-plus';
+
+const value = ref(0);
+const loading = ref(false);
+const tableData = ref([]);
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10,
+});
+const total = ref(0);
+
+const { tableContainer, tableMaxHeight } = useTableHeight();
+
+const percent = computed(() => {
+  const num = value.value;
+  return {
+    aiPercent: 100 - num,
+    traditionPercent: num,
+  }
+});
+
+const getList = () => {
+  loading.value = true;
+  shuntApi.getBucketRecordList({ ...queryParams.value }).then(res => {
+    const { total: size, rows } = res;
+    tableData.value = rows;
+    value.value = rows.length ? rows[0].traditionPercent : 0;
+    loading.value = false;
+    total.value = size;
+  });
+}
+
+const onConfirm = () => {
+  shuntApi.postBucketRecord({ ...percent.value }).then(() => {
+    ElMessage.success("保存成功");
+    getList();
+  })
+}
+
+onMounted(() => getList());
+</script>
+
+<template>
+  <div class="shunt-container">
+    <div class="layout-card">
+      <div class="title">
+        <h4 class="text">分流策略配置</h4>
+        
+          <el-popconfirm
+            width="250"
+            icon-color="#626AEF"
+            title="是否要保存本次分流策略配置?"
+            @confirm="onConfirm"
+          >
+            <template #reference>
+              <p class="save-btn">
+                <img src="@/assets/images/svgs/icon-save.svg"></img>
+                <span>保存</span>
+              </p>
+            </template>
+            <template #actions="{ confirm, cancel }">
+              <el-button size="small" @click="cancel">否</el-button>
+              <el-button type="primary" size="small" @click="confirm">是</el-button>
+            </template>
+          </el-popconfirm>
+      </div>
+      <div class="progress-wrapper">
+        <div class="progress-inner">
+          <ul class="text-card">
+            <li class="space-x-[8px]">
+              <span class="label">传统服务占比</span>
+              <span class="value text-[#FF6F16]">{{percent.traditionPercent}}%</span>
+            </li>
+            <li class="space-x-[8px]">
+              <span class="label">AI服务占比</span>
+              <span class="value text-[#20B044]">{{percent.aiPercent}}%</span>
+            </li>
+          </ul>
+          <el-slider v-model="value" @input="onProgress" />
+        </div>
+      </div>
+    </div>
+    <div class="table-card" ref="tableContainer">
+      <el-table :data="tableData" style="width: 100%" :max-height="tableMaxHeight" v-loading="loading">
+        <el-table-column prop="traditionPercent" label="传统服务占比" align="center" :width="320" show-overflow-tooltip/>
+        <el-table-column prop="aiPercent" label="AI服务占比" align="center" />
+        <el-table-column prop="create" label="创建人" align="center" />
+        <el-table-column prop="createBy" label="创建时间" align="center" />
+      </el-table>
+      <pagination
+        v-show="total >= 0"
+        :total="total"
+        v-model:page="queryParams.pageNum"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.shunt-container {
+  display: flex;
+  flex-flow: column;
+  width: 100%;
+  height: 100%;
+  background: #eceff6;
+
+  .title {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 12px;
+    color: #1D2129;
+    font-size: 18px;
+    font-weight: bold;
+    line-height: 26px;
+    .save-btn {
+      display: flex;
+      align-items: center;
+      font-size: 14px;
+      color: #165DFF;
+      cursor: pointer;
+      span {
+        margin-left: 3px;
+      }
+    }
+  }
+
+  .layout-card {
+    width: 100%;
+    padding: 20px;
+    border-radius: 8px;
+    background: #fff;
+  }
+
+  .progress-wrapper {
+    display: flex;
+    align-items: center;
+    width: 100%;
+    height: 94px;
+    padding: 0 56px;
+    border-radius: 8px;
+    background: #FAFBFF;
+    .progress-inner {
+      width: 100%;
+
+      .text-card {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding-bottom: 10px;
+        .label {
+          color: #4E5969;
+          font-size: 14px;
+        }
+        .value {
+          font-size: 24px;
+          font-family: D-DIN-PRO;
+          font-weight: bold;
+        }
+      }
+    }
+  }
+}
+
+.table-card {
+  height: 100%;
+}
+</style>
+
+<style lang="scss">
+.shunt-container {
+  .el-slider__runway, .el-slider__bar {
+    height: 8px;
+  }
+  .el-slider__runway {
+    background: #20B044;
+  }
+  .el-slider__bar {
+    background: #FF6F16;
+  }
+}
+</style>

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

@@ -43,7 +43,7 @@ const getList = async () => {
 
   loading.value = true;
 
-  const { rows, total: t } = await waiteListApi.getWhiteList({...queryParams.value});
+  const { rows, total: t } = await waiteListApi.getWhiteList({...queryParams.value, type: 0});
 
   tableData.value = rows
   
@@ -69,10 +69,10 @@ const onDialogConfirm = () => {
     if ( valid ) {
       dialogVisible.value = false;
       if ( !formData.value.id ) {
-        await waiteListApi.postWhiteList(formData.value);
+        await waiteListApi.postWhiteList({...formData.value, type: 0});
         ElMessage.success('新增电话号码成功');
       } else {
-        await waiteListApi.putWhiteList(formData.value);
+        await waiteListApi.putWhiteList({...formData.value, type: 0});
         ElMessage.success('更新电话号码成功');
       }
       getList();