Просмотр исходного кода

服务单元分析报表静态页面开发中...

刘洋 1 год назад
Родитель
Сommit
c5b1bb11a1

+ 1 - 0
package.json

@@ -28,6 +28,7 @@
     "crypto-js": "^4.1.1",
     "dayjs": "^1.11.7",
     "echarts": "^5.4.2",
+    "echarts-liquidfill": "^3.1.0",
     "element-resize-detector": "^1.2.4",
     "lodash": "^4.17.21",
     "mitt": "^3.0.1",

+ 37 - 6
src/components/common/table-loop/index.vue

@@ -20,9 +20,11 @@
       <swiper-slide class="slide">
         <div class="td col1">1</div>
         <div class="td col2">呼和浩特</div>
-        <div class="td col3">19</div>
+        <div class="td col3"> 22 </div>
         <div class="td col4">
-          <div>1</div>
+          <div class="process-box">
+            <div class="process" :style="{ width: '70%' }"></div>
+          </div>
         </div>
         <div class="td col5">+19</div>
       </swiper-slide>
@@ -31,7 +33,9 @@
         <div class="td col2">呼和浩特</div>
         <div class="td col3">19</div>
         <div class="td col4">
-          <div>2</div>
+          <div class="process-box">
+            <div class="process" :style="{ width: '70%' }"></div>
+          </div>
         </div>
         <div class="td col5">+19</div>
       </swiper-slide>
@@ -40,7 +44,9 @@
         <div class="td col2">呼和浩特</div>
         <div class="td col3">19</div>
         <div class="td col4">
-          <div>1</div>
+          <div class="process-box">
+            <div class="process" :style="{ width: '70%' }"></div>
+          </div>
         </div>
         <div class="td col5">+19</div>
       </swiper-slide>
@@ -49,7 +55,9 @@
         <div class="td col2">呼和浩特</div>
         <div class="td col3">19</div>
         <div class="td col4">
-          <div>1</div>
+          <div class="process-box">
+            <div class="process" :style="{ width: '70%' }"></div>
+          </div>
         </div>
         <div class="td col5">+19</div>
       </swiper-slide>
@@ -58,7 +66,9 @@
         <div class="td col2">呼和浩特</div>
         <div class="td col3">19</div>
         <div class="td col4">
-          <div>1</div>
+          <div class="process-box">
+            <div class="process" :style="{ width: '70%' }"></div>
+          </div>
         </div>
         <div class="td col5">+19</div>
       </swiper-slide>
@@ -104,9 +114,15 @@ onMounted(() => {
   }
   .col1 {
     width: 34px;
+
+    color: #262626;
   }
   .col2 {
     width: calc((100% - 84px) / 2);
+    color: #262626;
+  }
+  .col3 {
+    color: #262626;
   }
   .col3,
   .col5 {
@@ -117,6 +133,21 @@ onMounted(() => {
   }
   .td {
     float: left;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    .process-box {
+      background-color: #d9d9d9;
+      height: 6px;
+      border-radius: 3px;
+      width: 90%;
+      overflow: hidden;
+      .process {
+        background-color: #4080ff;
+        height: 100%;
+      }
+    }
   }
 }
 </style>

+ 5 - 1
src/components/global/custom-date-picker/index.vue

@@ -1,5 +1,9 @@
 <template>
-  <t-date-range-picker v-model="range" :presets="presets" />
+  <t-date-range-picker
+    style="width: 240px"
+    v-model="range"
+    :presets="presets"
+  />
 </template>
 <script setup name="CustomDatePicker">
 import { ref, onMounted } from 'vue';

+ 2 - 0
src/components/global/index.js

@@ -15,6 +15,7 @@ import {
   GraphicComponent,
   TitleComponent,
   GeoComponent,
+  LegendScrollComponent,
 } from 'echarts/components';
 
 import Chart from './chart/index.vue';
@@ -36,6 +37,7 @@ use([
   GraphicComponent,
   TitleComponent,
   GeoComponent,
+  LegendScrollComponent,
 ]);
 
 export default {

+ 4 - 2
src/components/global/report-header/index.vue

@@ -1,7 +1,9 @@
 <template>
   <div class="report-header flex items-center justify-between">
-    <div class="title"
-      >{{ props.title }} <CustomDatePicker v-model="range"></CustomDatePicker
+    <div class="title flex items-center"
+      ><span class="m-r-10px">{{ props.title }}</span>
+      <slot />
+      <CustomDatePicker v-model="range"></CustomDatePicker
     ></div>
     <div class="right-box flex h-full items-center">
       <img class="time-icon" src="../../../assets//imgs/time.png" />

+ 8 - 0
src/router/asyncRoutes.js

@@ -118,6 +118,14 @@ export const devPushMenuList = [
     sort: 1,
     type: 'MENU',
   },
+  {
+    id: '2002',
+    name: '服务单元分析',
+    parentId: '2000',
+    url: 'test2',
+    sort: 2,
+    type: 'MENU',
+  },
 ];
 
 export default asyncRoutes;

+ 11 - 0
src/router/modules/report.js

@@ -20,5 +20,16 @@ export default {
         icon: 'notice',
       },
     },
+    {
+      name: 'ServiceAnalysis',
+      path: '/report/service-analysis',
+      component: () => import('@/views/report/service-analysis/index.vue'),
+      meta: {
+        title: '服务单元分析',
+        sort: 2,
+        alias: 'test2',
+        icon: 'notice',
+      },
+    },
   ],
 };

+ 352 - 0
src/utils/chart.js

@@ -0,0 +1,352 @@
+import 'echarts-liquidfill/src/liquidFill.js';
+const getTotal = (data) => {
+  return data.reduce((num, item) => {
+    return num + item.value;
+  }, 0);
+};
+
+//mergeParams方法
+//调用生成对应图表的方法时,extend参数可以对本文件里的基础配置里的字段进行覆盖,
+//如果extend里的配置项里的字段为空,说明需要删除本文件里的配置项字段
+//例如 options为{ legend :{top:'center',right:'3%'} } , extend参数为 { legend: { top:'',right:'',bottom:'3%',left:'center' } }
+//那么合并后的结果对象就是  { legend: {bottom:'3%' ,left:'center' } }
+const mergeParams = (options, obj) => {
+  let keys = Object.keys(obj);
+  keys.forEach((key) => {
+    let optionsSubObj = options[key];
+    if (optionsSubObj) {
+      let subObj = obj[key];
+      Object.entries(subObj).forEach((arr) => {
+        if (!arr[1]) {
+          delete optionsSubObj[arr[0]];
+        } else {
+          optionsSubObj[arr[0]] = arr[1];
+        }
+      });
+    }
+  });
+  return options;
+};
+const colorList = [
+  '#4080FF',
+  '#23C343',
+  '#37a2da',
+  '#32c5e9',
+  '#9fe6b8',
+  '#ffdb5c',
+  '#ff9f7f',
+  '#fb7293',
+  '#e7bcf3',
+  '#8378ea',
+  '#47A2FF ',
+  '#53C8D1',
+  '#59CB74',
+  '#FBD444',
+  '#7F6AAD',
+  '#585247',
+  '#ff0000',
+];
+//该方法生成双柱状图,后期再根据情况改造成根据数据循环成多柱状图
+export const createDoubleBarOption = (
+  { legendData, xData, yDatas },
+  extend = {}
+) => {
+  return mergeParams(
+    {
+      tooltip: {
+        trigger: 'axis',
+      },
+      legend: [
+        {
+          icon: 'react',
+          right: '3%',
+          top: '3%',
+          textStyle: {
+            color: '#595959',
+            fontSize: 12,
+          },
+          itemWidth: 8,
+          itemHeight: 8,
+          data: legendData,
+        },
+      ],
+      grid: {
+        top: '22%',
+        right: '3%',
+        left: '16%',
+        bottom: '10%',
+      },
+      xAxis: [
+        {
+          type: 'category',
+          data: xData,
+          axisLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+          axisLabel: {
+            margin: 6,
+            color: '#8c8c8c',
+            textStyle: {
+              fontSize: 12,
+            },
+          },
+        },
+      ],
+      yAxis: [
+        {
+          axisLabel: {
+            formatter: '{value}',
+            color: '#8c8c8c',
+            textStyle: {
+              fontSize: 12,
+            },
+          },
+          axisLine: {
+            show: false,
+          },
+          axisTick: {
+            show: false,
+          },
+        },
+      ],
+      series: [
+        {
+          name: '当期',
+          type: 'bar',
+          data: yDatas[0],
+          barWidth: '12px',
+          itemStyle: {
+            normal: {
+              color: '#4080FF',
+            },
+          },
+        },
+        {
+          name: '同期',
+          type: 'bar',
+          data: yDatas[1],
+          barWidth: '12px',
+          itemStyle: {
+            normal: {
+              color: '#23C343',
+            },
+          },
+        },
+      ],
+    },
+    extend
+  );
+};
+//创建圆环状的基础饼图
+export const createRingPieOption = (
+  {
+    data,
+    title = '',
+    center = ['50%', '50%'],
+    seriesName = '',
+    radius = [40, 60],
+  },
+  extend = {}
+) => {
+  const total = getTotal(data);
+  return mergeParams(
+    {
+      tooltip: {
+        trigger: 'item',
+      },
+      legend: {
+        orient: 'vertical',
+        right: '0%',
+        top: 'center',
+        icon: 'circle',
+        data: data.map((item) => item.name),
+        textStyle: {
+          color: '#595959',
+        },
+        formatter: (name) => {
+          let target = data.find((item) => item.name == name);
+          let float = ((target.value * 100) / total).toFixed(1);
+          return `${name} ${Number(float) + '%'}`;
+          //   return `{a|${name}}{b|${Number(float) * 100 + '%'}}`;
+        },
+      },
+      color: colorList,
+      series: [
+        {
+          name: seriesName,
+          type: 'pie',
+          radius: radius,
+          center: center,
+          label: {
+            position: 'center',
+            formatter: () => {
+              return `${title}\n{total|${total}}`;
+            },
+            rich: {
+              total: {
+                fontSize: 16,
+                color: '#262626',
+              },
+            },
+            color: '#8c8c8c',
+            fontSize: 12,
+            lineHeight: 24,
+          },
+          labelLine: {
+            show: false,
+          },
+          itemStyle: {
+            borderWidth: 2,
+            borderColor: '#fff',
+          },
+          data: data,
+        },
+      ],
+    },
+    extend
+  );
+};
+//创建蛋糕试的饼图
+export const createCakePieOption = (
+  {
+    data,
+    title = '',
+    center = ['35%', '50%'],
+    seriesName = '',
+    radius = [0, 60],
+  },
+  extend = {}
+) => {
+  const total = getTotal(data);
+  return mergeParams(
+    {
+      color: colorList,
+      tooltip: {
+        trigger: 'item',
+        formatter: '{a} <br/>{b} : {c} ({d}%)',
+      },
+      toolbox: {
+        show: true,
+      },
+      legend: {
+        type: 'scroll',
+        orient: 'vertical',
+        right: '1%',
+        top: 'center',
+        icon: 'circle',
+        textStyle: {
+          color: '#8C8C8C',
+        },
+        // height: 150,
+      },
+      series: [
+        {
+          name: seriesName,
+          type: 'pie',
+          radius: radius,
+          center: center,
+          data: data,
+          label: {
+            show: false,
+          },
+        },
+      ],
+    },
+    extend
+  );
+};
+//基础水球图
+export const createWaterBallOption = (
+  { data, title, center, color = '64,128,255' },
+  extend = {}
+) => {
+  return mergeParams(
+    {
+      // backgroundColor: "#0e2147",
+      // title: {
+      //   show: true,
+      //   text: title,
+      //   x: 'center',
+      //   y: '50%',
+      //   z: 10,
+      //   textStyle: {
+      //     color: '#666',
+      //     fontSize: 12,
+      //     // fontWeight: 500,
+      //   },
+      // },
+      // title: {
+      //   //   text: (value * 100).toFixed(0) + '{a|%}',
+      //   text: 30 + '{a|%}',
+      //   textStyle: {
+      //     fontSize: 50,
+      //     fontFamily: 'Microsoft Yahei',
+      //     fontWeight: 'normal',
+      //     color: '#bcb8fb',
+      //     rich: {
+      //       a: {
+      //         fontSize: 28,
+      //       },
+      //     },
+      //   },
+      //   x: 'center',
+      //   y: '35%',
+      // },
+      graphic: [
+        {
+          type: 'group',
+          left: 'center',
+          top: '34%',
+          children: [
+            {
+              type: 'text',
+              z: 100,
+              left: '10',
+              top: 'middle',
+              style: {
+                fill: '#666',
+                text: title,
+                font: '12px Microsoft YaHei',
+              },
+            },
+          ],
+        },
+      ],
+      series: [
+        {
+          name: 'test',
+          type: 'liquidFill',
+          radius: '90%',
+          center: center || ['50%', '50%'],
+          data: [data],
+          label: {
+            normal: {
+              // formatter: '',
+              position: ['50%', '66%'],
+              textStyle: {
+                color: '#333',
+                fontSize: 20,
+              },
+            },
+          },
+          color: ['#4080FF'],
+          backgroundStyle: {
+            color: `rgba(${color},0.12)`,
+          },
+          outline: {
+            borderDistance: 0,
+            itemStyle: {
+              borderWidth: 10,
+              borderColor: `rgba(${color},0.5)`,
+            },
+          },
+          // amplitude: 0,
+        },
+      ],
+    },
+    extend
+  );
+};

+ 156 - 197
src/views/report/dispatch-analysis/index.vue

@@ -9,10 +9,7 @@
         <div class="row1 flex">
           <div class="col1">
             <div class="card chart1-box">
-              <!-- <div class="flex justify-between items-center"> -->
               <div class="title">客户类型</div>
-              <!-- <custom-date-picker v-model="curTimeRange"></custom-date-picker> -->
-              <!-- </div> -->
               <div class="chart-wrap"
                 ><my-chart :options="options1"></my-chart
               ></div>
@@ -54,11 +51,98 @@
               </div>
             </div>
           </div>
-          <div class="col2 flex-1">
-            <div class="card chart7-box"></div>
+          <div class="col2">
+            <div class="card chart7-box">
+              <div class="title m-b-8px">项目派单完成率</div>
+              <div class="chart-wrap overflow-auto service-box">
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元A</div>
+                    <div>91%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status">已发布</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 91 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元B</div>
+                    <div>100%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status finished">已完结</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 100 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元C</div>
+                    <div>100%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status finished">已完结</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 100 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元D</div>
+                    <div>100%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status finished">已完结</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 100 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元E</div>
+                    <div>100%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status finished">已完结</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 100 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+                <div class="service-item">
+                  <div class="head flex justify-between items-center">
+                    <div>服务单元F</div>
+                    <div>100%</div>
+                  </div>
+                  <div class="body flex items-center">
+                    <div class="label">状态:</div>
+                    <div class="status finished">已完结</div>
+                    <div class="process-box">
+                      <div class="process" :style="{ width: 100 + '%' }"></div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
           <div class="col3">
-            <div class="card chart8-box"></div>
+            <div class="card chart8-box">
+              <div class="title">教务处城市在执行派单排名</div>
+              <div class="chart-wrap">
+                <table-loop></table-loop>
+              </div>
+            </div>
           </div>
         </div>
       </div>
@@ -70,154 +154,23 @@
 import { ref, computed } from 'vue';
 import ChinaPointChart from '@/components/common/china-point-chart/index.vue';
 import TableLoop from '@/components/common/table-loop/index.vue';
+import {
+  createDoubleBarOption,
+  createRingPieOption,
+  createCakePieOption,
+} from '@/utils/chart';
 const curTimeRange = ref([]);
-const colorList = [
-  '#47A2FF ',
-  '#53C8D1',
-  '#59CB74',
-  '#FBD444',
-  '#7F6AAD',
-  '#585247',
-  '#ff0000',
-];
-const options1 = computed(() => ({
-  tooltip: {
-    trigger: 'item',
-  },
-  legend: {
-    orient: 'vertical',
-    right: '3%',
-    top: 'center',
-    icon: 'circle',
-    data: ['教务处', '研究生'],
-    textStyle: {
-      color: '#595959',
-    },
-  },
-  color: colorList,
-  series: [
-    {
-      name: '客户类型',
-      type: 'pie',
-      radius: [40, 60],
-      center: ['35%', '50%'],
-      label: {
-        position: 'center',
-        formatter: () => {
-          return '总数\n{total|300}';
-        },
-        rich: {
-          total: {
-            fontSize: 16,
-            color: '#262626',
-          },
-        },
-        color: '#8c8c8c',
-        fontSize: 12,
-        lineHeight: 24,
-      },
-      labelLine: {
-        show: false,
-      },
-      itemStyle: {
-        borderWidth: 2,
-        borderColor: '#fff',
-      },
-      data: [
-        { name: '教务处', value: 230 },
-        { name: '研究生', value: 120 },
-      ],
-    },
+
+const options1 = createRingPieOption({
+  data: [
+    { name: '教务处', value: 230 },
+    { name: '研究生', value: 120 },
   ],
-}));
+  title: '总数',
+  center: ['30%', '50%'],
+  seriesName: '客户类型',
+});
 
-const createDoubleBarOption = ({ legendData, xData, yDatas }) => {
-  return {
-    tooltip: {
-      trigger: 'axis',
-    },
-    legend: [
-      {
-        icon: 'react',
-        right: '3%',
-        top: '3%',
-        textStyle: {
-          color: '#595959',
-          fontSize: 12,
-        },
-        itemWidth: 8,
-        itemHeight: 8,
-        data: legendData,
-      },
-    ],
-    grid: {
-      top: '22%',
-      right: '3%',
-      left: '16%',
-      bottom: '10%',
-    },
-    xAxis: [
-      {
-        type: 'category',
-        data: xData,
-        axisLine: {
-          show: false,
-        },
-        axisTick: {
-          show: false,
-        },
-        axisLabel: {
-          margin: 6,
-          color: '#8c8c8c',
-          textStyle: {
-            fontSize: 12,
-          },
-        },
-      },
-    ],
-    yAxis: [
-      {
-        axisLabel: {
-          formatter: '{value}',
-          color: '#8c8c8c',
-          textStyle: {
-            fontSize: 12,
-          },
-        },
-        axisLine: {
-          show: false,
-        },
-        axisTick: {
-          show: false,
-        },
-      },
-    ],
-    series: [
-      {
-        name: '当期',
-        type: 'bar',
-        data: yDatas[0],
-        barWidth: '12px',
-        itemStyle: {
-          normal: {
-            color: '#4080FF',
-          },
-        },
-      },
-      {
-        name: '同期',
-        type: 'bar',
-        data: yDatas[1],
-        barWidth: '12px',
-        itemStyle: {
-          normal: {
-            color: '#23C343',
-          },
-        },
-      },
-    ],
-  };
-};
 const options2 = createDoubleBarOption({
   legendData: ['当期', '同期'],
   xData: ['23-01', '23-02', '23-03', '23-04', '23-05'],
@@ -227,52 +180,14 @@ const options2 = createDoubleBarOption({
   ],
 });
 
-const options4 = computed(() => ({
-  color: [
-    '#37a2da',
-    '#32c5e9',
-    '#9fe6b8',
-    '#ffdb5c',
-    '#ff9f7f',
-    '#fb7293',
-    '#e7bcf3',
-    '#8378ea',
-  ],
-  tooltip: {
-    trigger: 'item',
-    formatter: '{a} <br/>{b} : {c} ({d}%)',
-  },
-  toolbox: {
-    show: true,
-  },
-  legend: {
-    type: 'scroll',
-    orient: 'vertical',
-    right: '3%',
-    top: 'center',
-    textStyle: {
-      color: '#8C8C8C',
-    },
-    // height: 150,
-  },
-  series: [
-    {
-      name: '供应商派单分布',
-      type: 'pie',
-      radius: [0, 60],
-      center: ['35%', '50%'],
-      data: [
-        { value: 20, name: '国宝' },
-        { value: 30, name: '治安' },
-        { value: 30, name: 'rose7' },
-        { value: 40, name: 'rose8' },
-      ],
-      label: {
-        show: false,
-      },
-    },
+const options4 = createCakePieOption({
+  data: [
+    { value: 20, name: '国宝' },
+    { value: 30, name: '治安' },
+    { value: 30, name: 'rose7' },
+    { value: 40, name: 'rose8' },
   ],
-}));
+});
 </script>
 
 <style lang="less" scoped>
@@ -283,7 +198,7 @@ const options4 = computed(() => ({
     overflow: auto;
     .scroll-content {
       height: 100%;
-      min-height: 450px;
+      min-height: 600px;
     }
     .row1 {
       height: calc((100% - 30px) * 2 / 3 + 15px);
@@ -351,6 +266,50 @@ const options4 = computed(() => ({
       }
       .chart-wrap {
         height: calc(100% - 20px);
+        &.service-box {
+          display: flex;
+          flex-wrap: wrap;
+          justify-content: space-between;
+          align-content: start;
+        }
+        .service-item {
+          height: 54px;
+          width: calc(50% - 8px);
+          margin-bottom: 8px;
+          padding: 6px 8px;
+          background-color: #f7f7f7;
+          border-radius: 2px;
+
+          .head {
+            font-size: 12px;
+            color: #262626;
+          }
+          .body {
+            font-size: 12px;
+            .label {
+              color: #8c8c8c;
+            }
+            .status {
+              color: #ff7d00;
+              &.finished {
+                color: #00b42a;
+              }
+            }
+            .process-box {
+              flex: 1;
+              margin-left: 30px;
+              background-color: #d9d9d9;
+              height: 6px;
+              border-radius: 3px;
+              width: 90%;
+              overflow: hidden;
+              .process {
+                background-color: #4080ff;
+                height: 100%;
+              }
+            }
+          }
+        }
       }
     }
   }

+ 210 - 0
src/views/report/service-analysis/index.vue

@@ -0,0 +1,210 @@
+<template>
+  <div class="dispatch-analysis">
+    <report-header title="服务单元分析" v-model:dateRange="curTimeRange">
+      <select-service-unit
+        style="width: auto; margin-right: 10px"
+        v-model="serviceId"
+        clearable
+      ></select-service-unit>
+    </report-header>
+    <div class="page-main">
+      <div class="scroll-content">
+        <div class="col1">
+          <div class="card">
+            <div class="title">
+              <span class="label">服务单元概览</span>
+            </div>
+            <div style="height: calc(100% - 140px)"
+              ><my-chart :options="options1"></my-chart
+            ></div>
+            <div class="grid1-others">
+              <div class="item">
+                <p class="num">348</p>
+                <div class="label">设备出库总量(台)</div>
+              </div>
+              <div class="item">
+                <p class="num">348</p>
+                <div class="label">设备占用率(%)</div>
+              </div>
+              <div class="item">
+                <p class="num">348</p>
+                <div class="label">在服务人员总数(人)</div>
+              </div>
+              <div class="item">
+                <p class="num">348</p>
+                <div class="label">现场人员占用率(%)</div>
+              </div>
+            </div>
+          </div>
+          <div class="card">
+            <div class="title">
+              <span class="label">人力供应商项目数量分布及占比</span>
+            </div>
+            <div style="height: calc(100% - 36px)">
+              <my-chart :options="options2"></my-chart>
+            </div>
+          </div>
+        </div>
+        <div class="col2">
+          <div class="card" style="height: calc((100% - 206px) * 3 / 7)">
+            <div class="title">
+              <span class="label">人力供应商项目角色人员分布</span>
+            </div>
+            <div class="box3 flex">
+              <div style="width: 220px; height: 100%">
+                <my-chart :options="options3"></my-chart>
+              </div>
+              <div style="width: calc(100% - 220px); height: 100%"></div>
+            </div>
+            <div></div>
+          </div>
+          <div
+            class="card m-t-15px"
+            style="height: calc((100% - 206px) * 4 / 7)"
+          ></div>
+          <div class="card m-t-15px" style="height: 80px">
+            <div class="title">
+              <span class="label">项目配额完成进度</span>
+            </div>
+          </div>
+          <div class="card m-t-15px" style="height: 80px">
+            <div class="title">
+              <span class="label">人员配额完成进度</span>
+            </div>
+          </div>
+        </div>
+        <div class="col3"></div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="DispatchAnalysis">
+import { ref, computed } from 'vue';
+import SelectServiceUnit from '@/components/common/select-service-unit/index.vue';
+import {
+  createWaterBallOption,
+  createRingPieOption,
+  createCakePieOption,
+} from '@/utils/chart';
+const curTimeRange = ref([]);
+const serviceId = ref('');
+const options1 = createWaterBallOption({
+  data: 23.5 / 100,
+  title: '项目执行进度',
+});
+const options2 = createRingPieOption(
+  {
+    data: [
+      { name: '阳光雨露', value: 26 },
+      { name: '软通智服', value: 12 },
+      { name: 'AAAA', value: 13 },
+      { name: 'BBBB', value: 12 },
+      { name: 'CCCC', value: 12 },
+      { name: 'DDDD', value: 15 },
+      { name: 'EEEE', value: 42 },
+      { name: 'FFFF', value: 102 },
+      { name: 'GGGG', value: 12 },
+      { name: 'HHHH', value: 72 },
+    ],
+    title: '派单数',
+    center: ['50%', '35%'],
+    seriesName: '数量',
+    radius: [45, 65],
+  },
+  {
+    legend: {
+      type: 'scroll',
+      orient: 'horizontal',
+      top: '',
+      right: '',
+      bottom: '10%',
+      left: 'center',
+    },
+  }
+);
+
+const options3 = createCakePieOption(
+  {
+    data: [
+      { name: '实施工程师', value: 172 },
+      { name: '区域协调人', value: 39 },
+    ],
+    radius: [0, 50],
+    center: ['30%', '50%'],
+    seriesName: '角色分布',
+  },
+  {}
+);
+</script>
+
+<style lang="less" scoped>
+.dispatch-analysis {
+  .page-main {
+    height: calc(100vh - 57px);
+    padding: 15px;
+    overflow: auto;
+    .scroll-content {
+      height: 100%;
+      min-height: 600px;
+      display: flex;
+      .title {
+        height: 36px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .label {
+          color: #262626;
+          font-size: 14px;
+          font-weight: bold;
+        }
+      }
+      .col1,
+      .col3 {
+        width: 268px;
+        height: 100%;
+        .card {
+          height: calc((100% - 16px) / 2);
+          &:last-child {
+            margin-top: 15px;
+          }
+        }
+      }
+      .col2 {
+        width: calc(100% - 566px);
+        margin-left: 15px;
+        margin-right: 15px;
+        .box3 {
+          height: calc(100% - 36px);
+        }
+      }
+      .grid1-others {
+        height: 104px;
+        display: flex;
+        flex-wrap: wrap;
+        align-content: space-around;
+        .item {
+          width: 50%;
+          text-align: center;
+          .num {
+            color: #165dff;
+            font-size: 16px;
+            font-weight: bold;
+          }
+          .label {
+            font-size: 12px;
+            color: #8c8c8c;
+          }
+        }
+      }
+    }
+
+    .card {
+      padding: 4px 10px 10px 10px;
+      background-color: #fff;
+      border: 1px solid #e5e5e5;
+      border-radius: 4px;
+    }
+  }
+}
+</style>