刘洋 1 éve
szülő
commit
61be60adb6

+ 6 - 0
src/api/report.js

@@ -134,3 +134,9 @@ export const sopWarningAnalysis = (params) =>
   });
 
 // -----------------------------------项目进度监控-----------------------------------------
+//项目阶段分布
+export const projectStageAnalysis = (params) =>
+  request({
+    url: '/api/sop/schedule/progress',
+    params,
+  });

+ 5 - 2
src/components/common/table-loop/index.vue

@@ -31,7 +31,9 @@
         v-for="(item, index) in data || []"
         :key="index"
       >
-        <div class="td index">{{ index + 1 }}</div>
+        <div class="td index"
+          ><div class="cell">{{ index + 1 }}</div></div
+        >
         <!-- <div class="td col2">{{ item.name }}</div>
         <div class="td col3"> {{ item.count }} </div>
         <div class="td col4">
@@ -45,7 +47,7 @@
           class="td"
           v-for="(v, i) in columns"
           :key="i"
-          :style="item.style || {}"
+          :style="v.style || {}"
         >
           <div class="cell">
             <span v-if="v.prop !== 'rate'">{{ item[v.prop] }}</span>
@@ -102,6 +104,7 @@ onMounted(() => {
       color: #262626;
       .cell {
         height: 100%;
+        width: 100%;
         display: flex;
         justify-content: center;
         align-items: center;

+ 120 - 32
src/utils/chart.js

@@ -1,4 +1,5 @@
 import 'echarts-liquidfill/src/liquidFill.js';
+import { cloneDeep } from 'lodash';
 const getTotal = (data) => {
   return data.reduce((num, item) => {
     return num + item.value;
@@ -457,42 +458,129 @@ export const createWaterBallOption = (
 };
 
 //基础百分比堆叠柱状图
+//originData:原始数据二维数组,每个子元素数组的内容累加和可能会不相等,会导致整体柱状集的尾部不对齐,所以要二次处理
 export const createPercentBarOption = (
-  {
-    xData = [],
-    seriesData = [],
-    legendData = ['准备', '扫描', '评卷', '收尾', '已完结'],
-    xName = '',
-  },
+  { xData = [], originData = [], legendData = [], xName = '' },
   extend = {}
 ) => {
-  return mergeParams({
-    tooltip: {
-      trigger: 'axis',
-      formatter: function (obj) {
-        let total = 0;
-        let str = '';
-        for (let i = 0; i < obj.length; i++) {
-          total += obj[i]['value'];
-        }
-        for (let i = 0; i < obj.length; i++) {
-          str =
-            str +
-            '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' +
-            obj[i]['color'] +
-            ';"></span>' +
-            ' ' +
-            obj[i]['seriesName'] +
-            ' : ' +
-            obj[i]['value'] +
-            ' (' +
-            ((obj[i]['value'] / total) * 100).toFixed(1) +
-            ' %)' +
-            '</br>';
+  const oData = cloneDeep(originData);
+  let newData = [];
+  if (originData.length) {
+    newData = originData.map((subArr, index) => {
+      return [];
+    });
+    for (let i = 0; i < originData[0].length; i++) {
+      let total = 0;
+      for (let j = 0; j < originData.length; j++) {
+        total += originData[j][i];
+      }
+      for (let x = 0; x < originData.length; x++) {
+        if (i != originData[0].length - 1) {
+          newData[x].push(Math.round((originData[x][i] / total) * 100));
+        } else {
+          let prevTotal = newData[x].reduce((num, v) => {
+            return v + num;
+          }, 0);
+          newData[x].push(100 - prevTotal);
         }
-        return str;
+      }
+    }
+  }
+  let sData = [];
+  if (newData.length) {
+    sData = newData[0].map(() => []);
+    for (let i = 0; i < newData[0].length; i++) {
+      for (let j = 0; j < newData.length; j++) {
+        sData[i].push(newData[j][i]);
+      }
+    }
+  }
+  return mergeParams(
+    {
+      tooltip: {
+        trigger: 'axis',
+        axisPointer: {
+          type: 'shadow',
+        },
+        formatter: function (obj, b, c) {
+          let total = 0;
+          let str = '';
+          for (let i = 0; i < obj.length; i++) {
+            total += obj[i]['value'];
+          }
+          for (let i = 0; i < obj.length; i++) {
+            let name = obj[i].name;
+            let dataIndex = xData.findIndex((item) => item == name);
+            let val = originData[dataIndex][obj[i].seriesIndex];
+
+            str =
+              str +
+              '<span style="display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:' +
+              obj[i]['color'] +
+              ';"></span>' +
+              ' ' +
+              obj[i]['seriesName'] +
+              ' : ' +
+              val +
+              ' (' +
+              ((obj[i]['value'] / total) * 100).toFixed(1) +
+              ' %)' +
+              '</br>';
+          }
+          return str;
+        },
+      },
+      color: colorList,
+      legend: {
+        data: legendData,
+        top: '1%',
+        left: 'center',
+        icon: 'circle',
+        textStyle: {
+          color: '#8C8C8C',
+        },
+        itemWidth: 10,
+        itemHeight: 10,
+      },
+      calculable: true,
+      grid: {
+        left: '1%',
+        right: '4%',
+        bottom: '1%',
+        containLabel: true,
       },
+      yAxis: [
+        {
+          type: 'category',
+          data: xData,
+          axisLabel: {
+            formatter: '{value}',
+            color: '#8c8c8c',
+            textStyle: {
+              fontSize: 12,
+            },
+          },
+          axisLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+        },
+      ],
+      xAxis: [
+        {
+          type: 'value',
+          splitArea: { show: true },
+        },
+      ],
+      series: sData.map((item, index) => ({
+        name: legendData[index],
+        type: 'bar',
+        stack: '百分比',
+        data: item,
+      })),
     },
-    color: colorList,
-  });
+    {}
+  );
 };

+ 89 - 8
src/views/report/project-analysis/index.vue

@@ -17,21 +17,27 @@
         <div class="row1 flex items-center">
           <div class="card">
             <div class="title">
-              <span class="label">大区在服务人数分布及对比</span>
+              <span class="label">大区项目阶段分布及对比</span>
+            </div>
+            <div class="chart-wrap">
+              <my-chart :options="options1"></my-chart>
             </div>
-            <div class="chart-wrap"> </div>
           </div>
           <div class="card">
             <div class="title">
-              <span class="label">设备供应商设备使用占比</span>
+              <span class="label">项目进度阶段总体分布占比</span>
+            </div>
+            <div class="chart-wrap">
+              <my-chart :options="options2"></my-chart>
             </div>
-            <div class="chart-wrap"> </div>
           </div>
           <div class="card">
             <div class="title">
               <span class="label">供应商项目阶段分布及对比</span>
             </div>
-            <div class="chart-wrap"> </div>
+            <div class="chart-wrap">
+              <my-chart :options="options3"></my-chart>
+            </div>
           </div>
         </div>
         <div class="row2">
@@ -43,10 +49,10 @@
 </template>
 
 <script setup name="DispatchAnalysis">
-import { ref, computed, onMounted } from 'vue';
+import { ref, computed, onMounted, watch } from 'vue';
 import { useRequest } from 'vue-request';
-import { createCakePieOption } from '@/utils/chart';
-import { serviceServiceList } from '@/api/report';
+import { createCakePieOption, createPercentBarOption } from '@/utils/chart';
+import { serviceServiceList, projectStageAnalysis } from '@/api/report';
 const curTimeRange = ref([]);
 const timeParams = computed(() => {
   return {
@@ -62,6 +68,78 @@ const timeChange = (time) => {
     res?.length && (serviceId.value = res[0].id);
   });
 };
+const sortKeys = ['prepare', 'scan', 'evaluation', 'summary', 'finish'];
+const legendData = ['准备', '扫描', '评卷', '收尾', '已完结'];
+const {
+  data: result1,
+  loading: loading1,
+  run: run1,
+} = useRequest(projectStageAnalysis);
+const {
+  data: result2,
+  loading: loading2,
+  run: run2,
+} = useRequest(projectStageAnalysis);
+const {
+  data: result3,
+  loading: loading3,
+  run: run3,
+} = useRequest(projectStageAnalysis);
+watch(serviceId, (serviceId) => {
+  run1({ serviceId, group: 'REGION' });
+  run2({ serviceId, group: 'POPULATION' });
+  run3({ serviceId, group: 'SUPPLIER' });
+});
+const options1 = computed(() => {
+  let res = result1.value || {};
+  let xData = Object.keys(res);
+  let originData = Object.values(res).map((item) => {
+    return sortKeys.map((v) => item[v] || 0);
+  });
+  return createPercentBarOption({
+    xData: xData,
+    originData: originData,
+    legendData: legendData,
+  });
+});
+const options2 = computed(() => {
+  let obj = result2.value?.POPULATION || {};
+  let data = sortKeys.map((prop, index) => {
+    return {
+      name: legendData[index],
+      value: obj[prop] || 0,
+    };
+  });
+  return createCakePieOption(
+    {
+      data: data,
+      radius: [0, 70],
+      center: ['50%', '40%'],
+      seriesName: '总体分布占比',
+    },
+    {
+      legend: {
+        orient: 'horizontal',
+        top: null,
+        right: null,
+        bottom: '0%',
+        left: 'center',
+      },
+    }
+  );
+});
+const options3 = computed(() => {
+  let res = result3.value || {};
+  let xData = Object.keys(res);
+  let originData = Object.values(res).map((item) => {
+    return sortKeys.map((v) => item[v] || 0);
+  });
+  return createPercentBarOption({
+    xData: xData,
+    originData: originData,
+    legendData: legendData,
+  });
+});
 </script>
 
 <style lang="less" scoped>
@@ -109,6 +187,9 @@ const timeChange = (time) => {
           // font-weight: bold;
         }
       }
+      .chart-wrap {
+        height: calc(100% - 36px);
+      }
     }
   }
 }