Browse Source

feat: 语音客服大屏以及通话记录

sunxiao 1 month ago
parent
commit
48ec58f107

BIN
src/assets/images/dashboard/bg-count-01.png


BIN
src/assets/images/dashboard/bg-count-02.png


BIN
src/assets/images/dashboard/bg-count-03.png


BIN
src/assets/images/dashboard/bg-count-04.png


BIN
src/assets/images/dashboard/bg-middle-title-03.png


BIN
src/assets/images/dashboard/bg-middle-title-04.png


BIN
src/assets/images/dashboard/bg-middle-title-05.png


+ 119 - 0
src/components/NumberAnimation/index.vue

@@ -0,0 +1,119 @@
+<template>
+  <span :style="valueStyle">
+    {{ showValue }}
+  </span>
+</template>
+
+<script setup>
+import { ref, computed, watchEffect, onMounted, watch } from 'vue'
+import { useTransition, TransitionPresets } from '@vueuse/core'
+const props = defineProps({
+  from: {
+    type: Number,
+    default: 0
+  },
+  to: {
+    type: Number,
+    default: 1000
+  },
+  duration: {
+    type: Number,
+    default: 1.2 * 1000
+  },
+  autoplay:  {
+    type: Boolean,
+    default: true
+  },
+  precision:  {
+    type: Number,
+    default: 0
+  },
+  prefix:  {
+    type: String,
+    default: ''
+  },
+  suffix:  {
+    type: String,
+    default: ''
+  },
+  separator:  {
+    type: String,
+    default: ','
+  },
+  decimal:  {
+    type: String,
+    default: '.'
+  },
+  valueStyle: {
+    type: Object,
+    default: () => ({})
+  },
+  transition: {
+    type: String,
+    default: 'easeInOutCubic'
+  }
+})
+
+const source = ref(props.to)
+const emits = defineEmits(['started', 'finished'])
+
+watchEffect(() => source.value = props.from)
+watch(
+  () => [props.from, props.to],
+  () => {
+    if (props.autoplay) {
+      play()
+    }
+  },
+  {
+    deep: true
+  }
+)
+
+onMounted(() => {
+  props.autoplay && play()
+})
+
+const formatNumber = ( value, precision= 2, separator= ',', decimal = '.', prefix, suffix ) => {
+  if (typeof value !== 'number' && typeof value !== 'string') {
+    console.warn('Expected value to be of type number or string')
+  }
+  if (typeof precision !== 'number') {
+    console.warn('Expected precision to be of type number')
+  }
+  const numValue = Number(value)
+  if (isNaN(numValue) || !isFinite(numValue)) {
+    return ''
+  }
+  if (numValue === 0) {
+    return numValue.toFixed(precision)
+  }
+  let formatValue = numValue.toFixed(precision)
+  if (typeof separator === 'string' && separator !== '') {
+    const [integerPart, decimalPart] = formatValue.split('.')
+    formatValue =
+      integerPart.replace(/(\d)(?=(\d{3})+$)/g, '$1' + separator) + (decimalPart ? decimal + decimalPart : '')
+  }
+  return (prefix || '') + formatValue + (suffix || '')
+}
+
+const outputValue = useTransition(source, {
+  duration: props.duration,
+  transition: TransitionPresets[props.transition],
+  onFinished: () => emits('finished'),
+  onStarted: () => emits('started')
+})
+
+function play() {
+  source.value = props.to;
+}
+
+const showValue = computed(() => {
+  const { precision, separator, decimal, prefix, suffix } = props;
+  return formatNumber(outputValue.value, precision, separator, decimal, prefix, suffix);
+})
+
+defineExpose({
+  play
+})
+</script>

+ 1 - 1
src/layout/components/Sidebar/SidebarItem.vue

@@ -85,7 +85,7 @@ function toPage(path) {
     const { href } = router.resolve({
     const { href } = router.resolve({
       path: '/voice/dashboard'
       path: '/voice/dashboard'
     });
     });
-    window.open(href, '_blank');
+    window.open(location.origin + href, '_blank');
   } else {
   } else {
     router.push(path)
     router.push(path)
   }
   }

+ 10 - 4
src/router/index.js

@@ -70,6 +70,13 @@ export const constantRoutes = [
     //   }
     //   }
     // ]
     // ]
   // },
   // },
+  {
+    path: '/voice/dashboard',
+    component: () => import('@/views/voice/dashboard/index'),
+    hidden: true,
+    // roles: ['admin'],
+    name: 'VoiceDashboard',
+  },
   {
   {
     path: '/user',
     path: '/user',
     component: Layout,
     component: Layout,
@@ -83,7 +90,7 @@ export const constantRoutes = [
         meta: { title: '个人中心', icon: 'user' }
         meta: { title: '个人中心', icon: 'user' }
       }
       }
     ]
     ]
-  }
+  },
 ]
 ]
 
 
 // 动态路由,基于用户权限动态去加载
 // 动态路由,基于用户权限动态去加载
@@ -91,10 +98,9 @@ export const dynamicRoutes = [
   {
   {
     path: '/voice/dashboard',
     path: '/voice/dashboard',
     component: () => import('@/views/voice/dashboard/index'),
     component: () => import('@/views/voice/dashboard/index'),
-    hidden: false,
-    roles: ['admin'],
+    hidden: true,
+    // roles: ['admin'],
     name: 'VoiceDashboard',
     name: 'VoiceDashboard',
-    
   },
   },
   {
   {
     path: '/system/user-auth',
     path: '/system/user-auth',

+ 63 - 75
src/views/voice/dashboard/echartConfig.js

@@ -437,8 +437,8 @@ export const getEchart3dOption = (params) => {
   return option;
   return option;
 };
 };
 
 
-export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
-  let legendData = ["近七月", "同比"];
+export const getEchartBarOption = ({ barXAxisData, seriesData, seriesData1 }) => {
+  let legendData = ["AI处理量", "总电话量"];
   return {
   return {
     tooltip: {
     tooltip: {
       trigger: "axis",
       trigger: "axis",
@@ -482,7 +482,7 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
     },
     },
     xAxis: {
     xAxis: {
       type: "category",
       type: "category",
-      data: xAxisData,
+      data: barXAxisData,
       axisLine: {
       axisLine: {
         lineStyle: {
         lineStyle: {
           color: "#94A7BD",
           color: "#94A7BD",
@@ -529,18 +529,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         type: "bar",
         type: "bar",
         barWidth: 8,
         barWidth: 8,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(255,214,0, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(88, 55, 15, 0.6)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(255,214,0, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(88, 55, 15, 0.6)",
+            },
+          ]),
         },
         },
         data: seriesData,
         data: seriesData,
       },
       },
@@ -552,18 +550,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         barGap: 0,
         barGap: 0,
         data: seriesData,
         data: seriesData,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(178, 149, 0, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(255,138,0, 0.1)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(178, 149, 0, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(255,138,0, 0.1)",
+            },
+          ]),
         },
         },
         tooltip: {
         tooltip: {
           show: false,
           show: false,
@@ -579,18 +575,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         symbol: "diamond",
         symbol: "diamond",
         z: 12,
         z: 12,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(178, 138, 0, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(252, 255, 108, 0.8)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(178, 138, 0, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(252, 255, 108, 0.8)",
+            },
+          ]),
         },
         },
         tooltip: {
         tooltip: {
           show: false,
           show: false,
@@ -603,18 +597,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         type: "bar",
         type: "bar",
         barWidth: 8,
         barWidth: 8,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(0, 147, 221, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(0, 88, 255, 0.2)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(0, 147, 221, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(0, 88, 255, 0.2)",
+            },
+          ]),
         },
         },
         data: seriesData1,
         data: seriesData1,
       },
       },
@@ -625,18 +617,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         barWidth: 10,
         barWidth: 10,
         barGap: 0,
         barGap: 0,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(0, 67, 123, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(0, 67, 123, 0)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(0, 67, 123, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(0, 67, 123, 0)",
+            },
+          ]),
         },
         },
         data: seriesData1,
         data: seriesData1,
         tooltip: {
         tooltip: {
@@ -653,18 +643,16 @@ export const getEchartBarOption = ({ xAxisData, seriesData, seriesData1 }) => {
         symbol: "diamond",
         symbol: "diamond",
         z: 12,
         z: 12,
         itemStyle: {
         itemStyle: {
-          normal: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              {
-                offset: 0,
-                color: "rgba(0, 114, 221, 1)",
-              },
-              {
-                offset: 1,
-                color: "rgba(129, 228, 255, 1)",
-              },
-            ]),
-          },
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            {
+              offset: 0,
+              color: "rgba(0, 114, 221, 1)",
+            },
+            {
+              offset: 1,
+              color: "rgba(129, 228, 255, 1)",
+            },
+          ]),
         },
         },
         tooltip: {
         tooltip: {
           show: false,
           show: false,

+ 163 - 48
src/views/voice/dashboard/index.vue

@@ -5,8 +5,11 @@ import * as echarts from 'echarts';
 import useUserStore from '@/store/modules/user';
 import useUserStore from '@/store/modules/user';
 import { dashboardApi } from '@/api/voice/dashboard';
 import { dashboardApi } from '@/api/voice/dashboard';
 import { getEchartLineOption, getEchart3dOption, getEchartBarOption } from './echartConfig';
 import { getEchartLineOption, getEchart3dOption, getEchartBarOption } from './echartConfig';
+import NumberAnimation from '@/components/NumberAnimation';
+import usePermissionStore from '@/store/modules/permission';
 
 
 const userStore = useUserStore();
 const userStore = useUserStore();
+const permissionStore = usePermissionStore();
 
 
 const screenData = ref({});
 const screenData = ref({});
 
 
@@ -17,6 +20,7 @@ const dateInfo = ref({
 })
 })
 
 
 let timer = null;
 let timer = null;
+let dataTimer = null;
 let myLineChart = null;
 let myLineChart = null;
 let my3dChart = null;
 let my3dChart = null;
 let myBarChart = null;
 let myBarChart = null;
@@ -49,6 +53,7 @@ const formatSeconds = (seconds, num) => {
   return formattedMinutes;
   return formattedMinutes;
 }
 }
 
 
+// 将秒转换为小时、分钟和秒 00:12 d的函数
 const formatSecondsToTime = (seconds) => {
 const formatSecondsToTime = (seconds) => {
   // 计算完整的分钟数和剩余的秒数
   // 计算完整的分钟数和剩余的秒数
   let minutes = Math.floor(seconds / 60);
   let minutes = Math.floor(seconds / 60);
@@ -76,41 +81,40 @@ const initEchart = async() => {
   const { 
   const { 
     recent7DayAndCounts, recent7DayAndCountsLastYear,
     recent7DayAndCounts, recent7DayAndCountsLastYear,
     recent7MonthAndCounts, recent7MonthAndCountsLastYear,
     recent7MonthAndCounts, recent7MonthAndCountsLastYear,
-    businessTop7
+    businessTop7,
+    ai7DayAndCounts
    } = data;
    } = data;
 
 
   screenData.value = {
   screenData.value = {
     ...data,
     ...data,
-    inTimes: formatSeconds(data.inTimes, 60),
-    inTimesAvg: formatSecondsToTime(data.inTimesAvg),
-    totalTimes: formatSeconds(data.totalTimes, 3600),
-    robotInTimes: formatSeconds(data.robotInTimes, 60),
-    robotTimesAvg: formatSecondsToTime(data.robotTimesAvg),
-    humanTimesAvg: formatSecondsToTime(data.humanTimesAvg),
-    humanInTimes: formatSeconds(data.humanInTimes, 60)
+    todayInTimesAvg: formatSecondsToTime(data.todayInTimesAvg),
+    // inTimes: formatSeconds(data.inTimes, 60),
+    // inTimesAvg: formatSecondsToTime(data.inTimesAvg),
+    // totalTimes: formatSeconds(data.totalTimes, 3600),
+    // robotInTimes: formatSeconds(data.robotInTimes, 60),
+    // robotTimesAvg: formatSecondsToTime(data.robotTimesAvg),
+    // humanTimesAvg: formatSecondsToTime(data.humanTimesAvg),
+    // humanInTimes: formatSeconds(data.humanInTimes, 60)
   };
   };
 
 
   // 近7日电话呼入量 - 折线图
   // 近7日电话呼入量 - 折线图
   const lineSeriesData = [
   const lineSeriesData = [
     { name: '近七日', data:recent7DayAndCounts },
     { name: '近七日', data:recent7DayAndCounts },
-    { name: '同比', data:recent7DayAndCountsLastYear }
+    // { name: '同比', data:recent7DayAndCountsLastYear }
   ];
   ];
   const lineXAxisData = recent7DayAndCounts.map(item => dayjs(item.date).format('MM.DD'))
   const lineXAxisData = recent7DayAndCounts.map(item => dayjs(item.date).format('MM.DD'))
   
   
-  // 近七日呼入电话量趋势
-  const barXAxisData = recent7MonthAndCounts.map(item => dayjs(item.date).format('MM-DD'))
-  const seriesData = recent7MonthAndCounts.map(item => item.count)
-  const seriesData1 = recent7MonthAndCountsLastYear.map(item => item.count)
-
-  myLineChart = echarts.init(echartLineRef.value);
-  my3dChart = echarts.init(echart3dRef.value);
-  myBarChart = echarts.init(echartBarRef.value);
+  // 近七日Ai处理情况
+  const barXAxisData = ai7DayAndCounts.map(item => dayjs(item.date).format('MM.DD'))
+  const seriesData = ai7DayAndCounts.map(item => item.aiCounts)
+  const seriesData1 = ai7DayAndCounts.map(item => item.totalCounts)
 
 
   myLineChart.setOption(getEchartLineOption({
   myLineChart.setOption(getEchartLineOption({
     xAxisData: lineXAxisData,
     xAxisData: lineXAxisData,
     seriesData: lineSeriesData
     seriesData: lineSeriesData
   }));
   }));
 
 
+  // 本月业务类型 TOP7
   my3dChart.setOption(getEchart3dOption(businessTop7));
   my3dChart.setOption(getEchart3dOption(businessTop7));
 
 
   myBarChart.setOption(getEchartBarOption({
   myBarChart.setOption(getEchartBarOption({
@@ -123,11 +127,17 @@ const initEchart = async() => {
 const windowResize = () => {
 const windowResize = () => {
   setTimeout(() => {
   setTimeout(() => {
     myLineChart.resize();
     myLineChart.resize();
-    // my3dChart.resize();
-    // myBarChart.resize();
+    my3dChart.resize();
+    myBarChart.resize();
   }, 300)
   }, 300)
 }
 }
 
 
+const hasRouterAuth = () => {
+  return permissionStore.routes.some(route => {
+    return route.path === '/voice/dashboard'
+  })
+}
+
 onMounted(() => {
 onMounted(() => {
   autofit.init({
   autofit.init({
     dw: 1920,
     dw: 1920,
@@ -136,8 +146,13 @@ onMounted(() => {
     resize: true,
     resize: true,
   })
   })
 
 
-  timer = setInterval(updateTime, 1000);
+  myLineChart = echarts.init(echartLineRef.value);
+  my3dChart = echarts.init(echart3dRef.value);
+  myBarChart = echarts.init(echartBarRef.value);
 
 
+  timer = setInterval(updateTime, 1000);
+  dataTimer = setInterval(initEchart, 5 * 60 * 1000);
+  
   updateTime();
   updateTime();
 
 
   initEchart();
   initEchart();
@@ -148,6 +163,7 @@ onMounted(() => {
 
 
 onUnmounted(() => {
 onUnmounted(() => {
   clearInterval(timer);
   clearInterval(timer);
+  clearInterval(dataTimer);
 })
 })
 </script>
 </script>
 
 
@@ -168,15 +184,27 @@ onUnmounted(() => {
         <span>{{ userStore.nickName }}</span>
         <span>{{ userStore.nickName }}</span>
       </div>
       </div>
     </div>
     </div>
-    <div class="main space-y-[20px]">
+    <div class="main">
       <div class="top">
       <div class="top">
         <div class="count-list">
         <div class="count-list">
-          <div class="count-item"><p class="num">{{ screenData.inTotal }}</p><p>近7天呼入总量</p></div>
-          <div class="count-item"><p class="num">{{ screenData.successRate }}%</p><p>近7天接通率</p></div>
-          <div class="count-item"><p class="num">{{ screenData.inTimes }}<span class="text-[14px] text-[#fff] opacity-70">分钟</span></p><p>近7天呼入时长</p></div>
-          <div class="count-item"><p class="num">{{ screenData.inTimesAvg }}</p><p>近7天平均呼入时长</p></div>
-          <div class="count-item"><p class="num">{{ screenData.totalTimes }}<span class="text-[14px] text-[#fff] opacity-70">小时</span></p><p>累计呼入时长</p></div>
-          <div class="count-item"><p class="num">{{ screenData.totalCounts }}</p><p>累计呼入总量</p></div>
+          <div class="count-item">
+            <p class="num"><NumberAnimation :to="screenData.todayInTotal"></NumberAnimation></p>
+            <p>进入呼入总量</p>
+          </div>
+          <div class="count-item">
+            <p class="num"><NumberAnimation :to="screenData.todayAiTotal"></NumberAnimation></p>
+            <p>今日AI处理量<span class="text-[#00FCFF]">「占比{{screenData.todayAiRate || 0}}%」</span></p>
+          </div>
+          <div class="count-item">
+            <p class="num">{{ screenData.todayInTimesAvg || "00:00" }}</p>
+            <p>今日平均呼入时长</p>
+          </div>
+          <div class="count-item">
+            <p class="num"><NumberAnimation :to="screenData.totalCounts"></NumberAnimation></p>
+            <p>累计呼入总量</p>
+          </div>
+          <!-- <div class="count-item"><p class="num">{{ screenData.totalTimes }}<span class="text-[14px] text-[#fff] opacity-70">小时</span></p><p>累计呼入时长</p></div>
+          <div class="count-item"><p class="num">{{ screenData.totalCounts }}</p><p>累计呼入总量</p></div> -->
         </div>
         </div>
       </div>
       </div>
       <div class="middle">
       <div class="middle">
@@ -194,23 +222,77 @@ onUnmounted(() => {
       </div>
       </div>
       <div class="bottom">
       <div class="bottom">
         <div class="left">
         <div class="left">
+          <div class="title-status">当前在线人工坐席:3人   机器人坐席:10个</div>
           <ul class="statistic-list">
           <ul class="statistic-list">
-            <li class="statistic-item"><p class="title">今日呼入总量</p><p class="num">{{ screenData.robotInTotal }}</p></li>
-            <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.robotInTimes }}</span><span class="unit">分钟</span></p></li>
+            <li class="statistic-item">
+              <p class="title">AI处理量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.todayAiTotal"></NumberAnimation></p>
+                <span class="ratio green">占比 {{screenData.todayAiRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">人工处理量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.todayHumanTotal"></NumberAnimation></p>
+                <span class="ratio green">占比 {{screenData.todayHumanRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">电话接通量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.todaySuccessCounts"></NumberAnimation></p>
+                <span class="ratio green">占比 {{screenData.todaySuccessRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">今日呼入总量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.todayInTotal"></NumberAnimation></p>
+              </div>
+            </li>
+            <!-- <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.robotInTimes }}</span><span class="unit">分钟</span></p></li>
             <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.robotTimesAvg }}</p></li>
             <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.robotTimesAvg }}</p></li>
             <li class="statistic-item"><p class="title">转人工总量</p><p class="num">{{ screenData.transfer2Human }}</p></li>
             <li class="statistic-item"><p class="title">转人工总量</p><p class="num">{{ screenData.transfer2Human }}</p></li>
             <li class="statistic-item"><p class="title">当前排队总数</p><p class="num">300</p></li>
             <li class="statistic-item"><p class="title">当前排队总数</p><p class="num">300</p></li>
-            <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.robotStreamRate }}%</p></li>
+            <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.robotStreamRate }}%</p></li> -->
           </ul>
           </ul>
         </div>
         </div>
         <div class="right">
         <div class="right">
           <ul class="statistic-list">
           <ul class="statistic-list">
-            <li class="statistic-item"><p class="title">今日呼入总量</p><p class="num">{{ screenData.humanInTotal }}</p></li>
+            <li class="statistic-item">
+              <p class="title">AI处理量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.yesterdayAiTotal"></NumberAnimation></p>
+                <span class="ratio red">占比 {{screenData.yesterdayAiRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">人工处理量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.yesterdayHumanTotal"></NumberAnimation></p>
+                <span class="ratio red">占比 {{screenData.yesterdayHumanRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">电话接通量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.yesterdaySuccessCounts"></NumberAnimation></p>
+                <span class="ratio red">占比 {{screenData.yesterdaySuccessRate || 0}}%</span>
+              </div>
+            </li>
+            <li class="statistic-item">
+              <p class="title">今日呼入总量</p>
+              <div class="numeric-stats-box">
+                <p class="num"><NumberAnimation :to="screenData.yesterdayInTotal"></NumberAnimation></p>
+              </div>
+            </li>
+            <!-- <li class="statistic-item"><p class="title">今日呼入总量</p><p class="num">{{ screenData.humanInTotal }}</p></li>
             <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.humanInTimes }}</span><span class="unit">分钟</span></p></li>
             <li class="statistic-item"><p class="title">通话时长</p><p class="num space-x-[4px]"><span>{{ screenData.humanInTimes }}</span><span class="unit">分钟</span></p></li>
             <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.humanTimesAvg }}</p></li>
             <li class="statistic-item"><p class="title">平均通话时长</p><p class="num">{{ screenData.humanTimesAvg }}</p></li>
             <li class="statistic-item"><p class="title">置闲人数</p><p class="num">{{ screenData.onlineTotal }}</p></li>
             <li class="statistic-item"><p class="title">置闲人数</p><p class="num">{{ screenData.onlineTotal }}</p></li>
             <li class="statistic-item"><p class="title">当前排队总数</p><p class="num"></p></li>
             <li class="statistic-item"><p class="title">当前排队总数</p><p class="num"></p></li>
-            <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.humanStreamRate }}%</p></li>
+            <li class="statistic-item"><p class="title">分流比例</p><p class="num">{{ screenData.humanStreamRate }}%</p></li> -->
           </ul>
           </ul>
         </div>
         </div>
       </div>
       </div>
@@ -291,31 +373,33 @@ onUnmounted(() => {
 }
 }
 
 
 .main {
 .main {
-  justify-content: space-between;
-  padding: 30px 50px 18px 50px;
+  flex: 1;
+  display: flex;
+  flex-flow: column;
+  padding: 20px 50px 18px 50px;
   .top {
   .top {
     height: 120px;
     height: 120px;
     flex-shrink: 0;
     flex-shrink: 0;
     .count-list {
     .count-list {
       display: grid;
       display: grid;
       gap: 12px;
       gap: 12px;
-      grid-template-columns: repeat(6, minmax(260px, 1fr));
+      grid-template-columns: repeat(4, minmax(260px, 1fr));
       height: 100%;
       height: 100%;
       .count-item:nth-child(1) {
       .count-item:nth-child(1) {
         background: url("@/assets/images/dashboard/bg-count-01.png") no-repeat;
         background: url("@/assets/images/dashboard/bg-count-01.png") no-repeat;
-        background-size: contain;
+        background-size: 100% 100%;
       }
       }
       .count-item:nth-child(2) {
       .count-item:nth-child(2) {
         background: url("@/assets/images/dashboard/bg-count-02.png") no-repeat;
         background: url("@/assets/images/dashboard/bg-count-02.png") no-repeat;
-        background-size: contain;
+        background-size: 100% 100%;
       }
       }
       .count-item:nth-child(3) {
       .count-item:nth-child(3) {
         background: url("@/assets/images/dashboard/bg-count-03.png") no-repeat;
         background: url("@/assets/images/dashboard/bg-count-03.png") no-repeat;
-        background-size: contain;
+        background-size: 100% 100%;
       }
       }
       .count-item:nth-child(4) {
       .count-item:nth-child(4) {
         background: url("@/assets/images/dashboard/bg-count-04.png") no-repeat;
         background: url("@/assets/images/dashboard/bg-count-04.png") no-repeat;
-        background-size: contain;
+        background-size: 100% 100%;
       }
       }
       .count-item:nth-child(5) {
       .count-item:nth-child(5) {
         background: url("@/assets/images/dashboard/bg-count-05.png") no-repeat;
         background: url("@/assets/images/dashboard/bg-count-05.png") no-repeat;
@@ -326,7 +410,7 @@ onUnmounted(() => {
         background-size: contain;
         background-size: contain;
       }
       }
       .count-item {
       .count-item {
-        padding: 20px 0 0 22px;
+        padding: 10px 0 0 22px;
         color: #FFF;
         color: #FFF;
         .num {
         .num {
           font-family: D-DIN-PRO;
           font-family: D-DIN-PRO;
@@ -342,13 +426,11 @@ onUnmounted(() => {
     }
     }
   }
   }
   .middle {
   .middle {
-    height: 346px;
+    height: 350px;
+    margin-top: 36px;
     .echart-list {
     .echart-list {
       display: flex;
       display: flex;
       align-items: center;
       align-items: center;
-      // display: grid;
-      // grid-template-columns: repeat(3, 1fr);
-      // gap: 12px;
       height: 100%; 
       height: 100%; 
       .echart-item:nth-child(1) {
       .echart-item:nth-child(1) {
         margin-right: 12px;
         margin-right: 12px;
@@ -386,28 +468,60 @@ onUnmounted(() => {
     grid-template-columns: repeat(2, 1fr);
     grid-template-columns: repeat(2, 1fr);
     gap: 12px;
     gap: 12px;
     height: 350px;
     height: 350px;
+    margin-top: 24px;
     .left {
     .left {
+      position: relative;
       padding-top: 44px;
       padding-top: 44px;
       background: url('@/assets/images/dashboard/bg-middle-title-04.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
       background: url('@/assets/images/dashboard/bg-middle-title-04.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
-      background-size: 598px 44px, 100% 100%;
+      background-size: 100% 44px, 100% 100%;
+      .title-status {
+        position: absolute;
+        top: 0;
+        right: 0;
+        display: flex;
+        align-items: center;
+        justify-content: flex-end;
+        width: 100%;
+        height: 44px;
+        padding-right: 30px;
+        font-size: 14px;
+        color:  rgba(255, 255, 255, 0.8);
+      }
     }
     }
     .right {
     .right {
       padding-top: 44px;
       padding-top: 44px;
       background: url('@/assets/images/dashboard/bg-middle-title-05.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
       background: url('@/assets/images/dashboard/bg-middle-title-05.png') left top no-repeat, url('@/assets/images/dashboard/bg-bottom.png') left center no-repeat;
-      background-size: 598px 44px, 100% 100%;
+      background-size: 100% 44px, 100% 100%;
     }
     }
     .statistic-list {
     .statistic-list {
       display: grid;
       display: grid;
-      grid-template-columns: repeat(3, 1fr);
+      grid-template-columns: repeat(2, 1fr);
       grid-template-rows: repeat(2, 1fr);
       grid-template-rows: repeat(2, 1fr);
       gap: 15px;
       gap: 15px;
       height: 100%; 
       height: 100%; 
       padding: 30px 42px;
       padding: 30px 42px;
       .statistic-item {
       .statistic-item {
         height: 100%;
         height: 100%;
-        padding: 20px 0 0 36px;
+        padding: 20px 36px 0 36px;
         background: url('@/assets/images/dashboard/bg-bottom-item.png') center center no-repeat;
         background: url('@/assets/images/dashboard/bg-bottom-item.png') center center no-repeat;
         background-size: 100% 100%;
         background-size: 100% 100%;
+        overflow: hidden;
+        .numeric-stats-box {
+          display: flex;
+          justify-content: space-between;
+          align-items: flex-end;
+          height: 50px;
+          .ratio {
+            font-size: 14px;
+            line-height: 24px;
+          }
+          .green {
+            color: #00F040;
+          }
+          .red {
+            color: #FF7300
+          }
+        }
         .title {
         .title {
           color: #E6FFF5;
           color: #E6FFF5;
           font-size: 18px;
           font-size: 18px;
@@ -420,6 +534,7 @@ onUnmounted(() => {
           font-family: D-DIN-PRO;
           font-family: D-DIN-PRO;
           font-size: 36px;
           font-size: 36px;
           font-weight: bold;
           font-weight: bold;
+          line-height: 50px;
         }
         }
         .unit {
         .unit {
           color:  #9c9c9c;
           color:  #9c9c9c;

+ 25 - 4
src/views/voice/general/index.vue

@@ -79,13 +79,34 @@ onMounted(() => {})
         <el-table :data="tableListData" style="width: 100%" :max-height="tableMaxHeight" v-loading="loading">
         <el-table :data="tableListData" style="width: 100%" :max-height="tableMaxHeight" v-loading="loading">
           <el-table-column prop="date" label="统计日期" align="center" width="130" fixed />
           <el-table-column prop="date" label="统计日期" align="center" width="130" fixed />
           <el-table-column prop="inTotal" label="呼入总量" align="center" min-width="120" />
           <el-table-column prop="inTotal" label="呼入总量" align="center" min-width="120" />
-          <el-table-column prop="successTotal" label="接通总量" align="center" min-width="120" />
-          <el-table-column prop="failTotal" label="未接通总量" align="center" min-width="120" />
+          <el-table-column prop="successTotal" label="接通量" align="center" min-width="120" />
+          <el-table-column prop="aiTotal" label="AI处理量" align="center" min-width="120" />
+          <el-table-column prop="aiToHuman" label="AI转人工处理" align="center" min-width="120" />
+          <el-table-column prop="humanAndTransferTotal" label="AI人工处理量(含转人工)" align="center" min-width="120">
+            <template #header>
+              <span>AI人工处理量<br>(含转人工)</span>
+            </template>
+            <template #default="scope">
+              <span>{{ scope.row.humanAndTransferTotal }}</span>
+            </template>
+          </el-table-column>
+          <el-table-column prop="aiRate" label="AI处理率" align="center" min-width="120">
+            <template #default="scope">
+              {{ scope.row.aiRate }}%
+            </template>
+          </el-table-column>
+          <el-table-column prop="successRate" label="接通率" align="center" min-width="120">
+            <template #default="scope">
+              {{ scope.row.successRate }}%
+            </template>
+          </el-table-column>
+
+          <!-- <el-table-column prop="failTotal" label="未接通总量" align="center" min-width="120" />
           <el-table-column prop="robotHearTotal" label="机器人接听" align="center" min-width="120" />
           <el-table-column prop="robotHearTotal" label="机器人接听" align="center" min-width="120" />
           <el-table-column prop="transferTotal" label="机器人转人工" align="center" min-width="120" />
           <el-table-column prop="transferTotal" label="机器人转人工" align="center" min-width="120" />
           <el-table-column prop="humanTotal" label="人工接听" align="center" min-width="120" />
           <el-table-column prop="humanTotal" label="人工接听" align="center" min-width="120" />
           <el-table-column prop="robotHandleTotal" label="机器人处理量" align="center" min-width="120" />
           <el-table-column prop="robotHandleTotal" label="机器人处理量" align="center" min-width="120" />
-          <el-table-column prop="humanHandleTotal" label="人工处理量" align="center" min-width="120"/>
+          <el-table-column prop="humanHandleTotal" label="人工处理量" align="center" min-width="120"/> 
           <el-table-column prop="robotRate" label="机器人处理率" align="center" min-width="120">
           <el-table-column prop="robotRate" label="机器人处理率" align="center" min-width="120">
             <template #default="scope">
             <template #default="scope">
               {{ scope.row.robotRate }}%
               {{ scope.row.robotRate }}%
@@ -101,7 +122,7 @@ onMounted(() => {})
               {{ scope.row.failRate }}%
               {{ scope.row.failRate }}%
             </template>
             </template>
           </el-table-column>
           </el-table-column>
-          <!-- <el-table-column prop="handle" label="操作" align="center" fixed="right" width="150">
+          <el-table-column prop="handle" label="操作" align="center" fixed="right" width="150">
             <template #default="scope">
             <template #default="scope">
               <div class="flex justify-center space-x-[20px]">
               <div class="flex justify-center space-x-[20px]">
                 <span class="text-[#165DFF] cursor-pointer" @click="jumpDetails(scope.row)">详情</span>
                 <span class="text-[#165DFF] cursor-pointer" @click="jumpDetails(scope.row)">详情</span>