Эх сурвалжийг харах

Merge branch 'dev_1.0.1' of http://git.qmth.com.cn/sop/web into dev_1.0.1

刘洋 1 жил өмнө
parent
commit
4462bb8045

+ 26 - 0
src/api/report.js

@@ -170,3 +170,29 @@ export const resourceWarningTable = (params) =>
     url: '/api/sys/device/monitor/countByServiceUnitDetail',
     params,
   });
+
+// -----------------------------------质量分析监控-----------------------------------------
+// 总体盘点饼图
+export const qualityAnalysisPie = (params) =>
+  request({
+    url: '/api/quality/analyse/pie',
+    params,
+  });
+// 总体盘点归因雷达图
+export const qualityAnalysisRadar = (params) =>
+  request({
+    url: '/api/quality/analyse/radar',
+    params,
+  });
+// 审核进度
+export const qualityAnalysisProgress = (params) =>
+  request({
+    url: '/api/quality/analyse/progress',
+    params,
+  });
+// 影响度/归因柱状图
+export const qualityAnalysisInfluence = (params) =>
+  request({
+    url: '/api/quality/analyse/influence',
+    params,
+  });

+ 9 - 1
src/router/asyncRoutes.js

@@ -144,12 +144,20 @@ export const devPushMenuList = [
   },
   {
     id: '2005',
-    name: '设备保障监控',
+    name: '质量监控分析',
     parentId: '2000',
     url: 'test5',
     sort: 5,
     type: 'MENU',
   },
+  {
+    id: '2006',
+    name: '设备保障监控',
+    parentId: '2000',
+    url: 'test6',
+    sort: 6,
+    type: 'MENU',
+  },
 ];
 
 export default asyncRoutes;

+ 13 - 1
src/router/modules/report.js

@@ -53,6 +53,18 @@ export default {
         icon: 'report-project',
       },
     },
+    {
+      name: 'QualityAnalysis',
+      path: '/report/quality-analysis',
+      component: () => import('@/views/report/quality-analysis/index.vue'),
+      meta: {
+        title: '质量监控分析',
+        sort: 5,
+        // alias: 'qualityProblemControl',
+        alias: 'test5',
+        icon: 'report-quality',
+      },
+    },
     {
       name: 'DeviceAnalysis',
       path: '/report/device-analysis',
@@ -61,7 +73,7 @@ export default {
         title: '设备保障监控',
         sort: 5,
         alias: 'test5',
-        icon: 'report-project',
+        icon: 'report-device',
       },
     },
   ],

+ 69 - 4
src/utils/chart.js

@@ -31,9 +31,9 @@ const mergeParams = (options, obj) => {
 export const colorList = [
   '#4080FF',
   '#23C343',
-  '#37a2da',
-  '#32c5e9',
-  '#9fe6b8',
+  '#F76560',
+  '#FF9A2E',
+  '#86909C',
   '#ffdb5c',
   '#ff9f7f',
   '#fb7293',
@@ -77,6 +77,8 @@ export const createStackingBarOption = ({ xData, seriesData }, extend = {}) => {
         right: '1%',
         bottom: '1%',
         containLabel: true,
+        show: true,
+        borderColor: '#e5e5e5',
       },
       xAxis: {
         type: 'value',
@@ -93,6 +95,12 @@ export const createStackingBarOption = ({ xData, seriesData }, extend = {}) => {
         axisTick: {
           show: false,
         },
+        splitLine: {
+          show: true,
+          lineStyle: {
+            color: '#e5e5e5',
+          },
+        },
       },
       yAxis: {
         type: 'category',
@@ -115,7 +123,7 @@ export const createStackingBarOption = ({ xData, seriesData }, extend = {}) => {
         name: item.name,
         type: 'bar',
         data: item.data,
-        barWidth: '8px',
+        barWidth: item.barWidth ?? '8px',
         stack: '总量',
         // label: {
         //   normal: {
@@ -634,3 +642,60 @@ export const createLineOption = ({ sData = [], xData = [] }, extend = {}) => {
     {}
   );
 };
+
+// 雷达图
+export const createRadarOption = ({ names, sData }, extend = {}) => {
+  return mergeParams(
+    {
+      color: colorList,
+      legend: {
+        data: sData.map((item) => item.name),
+        bottom: 10,
+        itemWidth: 8,
+        itemHeight: 8,
+        textStyle: {
+          color: '#595959',
+          fontSize: 12,
+        },
+      },
+      radar: {
+        radius: '65%',
+        indicator: names.map((item) => {
+          return { name: item };
+        }),
+        axisName: {
+          color: '#8c8c8c',
+          fontSize: 10,
+        },
+        axisLine: {
+          lineStyle: {
+            color: '#e5e5e5',
+          },
+        },
+        splitLine: {
+          lineStyle: {
+            color: '#e5e5e5',
+          },
+        },
+        splitNumber: 4,
+        splitArea: {
+          show: false,
+        },
+      },
+      series: [
+        {
+          type: 'radar',
+          data: sData,
+          itemStyle: {
+            borderWidth: 1,
+            borderColor: '#fff',
+          },
+          areaStyle: {
+            opacity: 0.35,
+          },
+        },
+      ],
+    },
+    extend
+  );
+};

+ 394 - 0
src/views/report/quality-analysis/index.vue

@@ -0,0 +1,394 @@
+<template>
+  <div class="project-analysis">
+    <report-header
+      title="质量监控分析"
+      v-model:dateRange="curTimeRange"
+      @timeChange="timeChange"
+    >
+      <t-select
+        style="width: 200px"
+        :options="serviceOptions"
+        v-model="serviceId"
+        :keys="{ label: 'name', value: 'id' }"
+      ></t-select>
+    </report-header>
+    <div class="page-main">
+      <div class="col1">
+        <div class="card">
+          <div class="title">
+            <span class="label">质量问题总体盘点</span>
+            <t-select
+              style="width: 100px"
+              :options="groupOptions"
+              v-model="group"
+            ></t-select>
+          </div>
+          <div class="chart-wrap">
+            <div class="chart-part">
+              <my-chart :options="overallPieOptions"></my-chart>
+            </div>
+            <div class="chart-part">
+              <my-chart :options="overallRadarOptions"></my-chart>
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="col2">
+        <div class="col2-head">
+          <div class="card">
+            <div class="title">
+              <span class="label">质量问题审核进度</span>
+            </div>
+            <div class="chart-wrap p-t-7px">
+              <t-progress :percentage="auditProgress || 0" />
+            </div>
+          </div>
+        </div>
+        <div class="col2-body">
+          <div class="col2-row">
+            <div class="card">
+              <div class="title">
+                <span class="label">影响度供应商分布及对比</span>
+                <t-button variant="outline">
+                  <template #icon><FullscreenIcon /></template>
+                </t-button>
+              </div>
+              <div class="chart-wrap">
+                <my-chart :options="options11"></my-chart>
+              </div>
+            </div>
+            <div class="card">
+              <div class="title">
+                <span class="label">执行协调类归因供应商分布及对比</span>
+                <t-button variant="outline">
+                  <template #icon><FullscreenIcon /></template>
+                </t-button>
+              </div>
+              <div class="chart-wrap">
+                <my-chart :options="options12"></my-chart>
+              </div>
+            </div>
+          </div>
+          <div class="col2-row">
+            <div class="card">
+              <div class="title">
+                <span class="label">影响度大区分布及对比TOP5</span>
+                <t-button variant="outline">
+                  <template #icon><FullscreenIcon /></template>
+                </t-button>
+              </div>
+              <div class="chart-wrap">
+                <my-chart :options="options21"></my-chart>
+              </div>
+            </div>
+            <div class="card">
+              <div class="title">
+                <span class="label">执行协调类归因大区分布及对比TOP5</span>
+                <t-button variant="outline">
+                  <template #icon><FullscreenIcon /></template>
+                </t-button>
+              </div>
+              <div class="chart-wrap">
+                <my-chart :options="options22"></my-chart>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div> </div
+></template>
+
+<script setup name="QualityAnalysis">
+import { ref, computed, watch } from 'vue';
+import {
+  createRingPieOption,
+  createStackingBarOption,
+  createRadarOption,
+} from '@/utils/chart';
+import { useRequest } from 'vue-request';
+import {
+  serviceServiceList,
+  qualityAnalysisPie,
+  qualityAnalysisRadar,
+  qualityAnalysisProgress,
+  qualityAnalysisInfluence,
+} from '@/api/report';
+import { FullscreenIcon } from 'tdesign-icons-vue-next';
+const curTimeRange = ref([]);
+const timeParams = computed(() => {
+  return {
+    startTime: new Date(curTimeRange.value[0]).getTime(),
+    endTime: new Date(curTimeRange.value[1]).getTime(),
+  };
+});
+const groupOptions = ref([
+  { label: '按供应商', value: 'REASON_SUPPLIER' },
+  { label: '按大区', value: 'REASON_REGION' },
+]);
+let group = ref('REASON_SUPPLIER');
+const serviceOptions = ref([]);
+const serviceId = ref('');
+const timeChange = (time) => {
+  serviceServiceList(timeParams.value).then((res) => {
+    serviceOptions.value = res || [];
+    res?.length && (serviceId.value = res[0].id);
+  });
+};
+// 总体盘点饼图
+const { data: overallPieData, run: overallPieRun } =
+  useRequest(qualityAnalysisPie);
+// 总体盘点归因雷达图
+const { data: overallRadarData, run: overallRadarRun } =
+  useRequest(qualityAnalysisRadar);
+// 总体盘点饼图
+const { data: auditProgress, run: progressRun } = useRequest(
+  qualityAnalysisProgress
+);
+// 影响度/归因柱状图
+const { data: result11, run: run11 } = useRequest(qualityAnalysisInfluence);
+const { data: result12, run: run12 } = useRequest(qualityAnalysisInfluence);
+const { data: result21, run: run21 } = useRequest(qualityAnalysisInfluence);
+const { data: result22, run: run22 } = useRequest(qualityAnalysisInfluence);
+
+watch(serviceId, (serviceUnitId) => {
+  const param = { serviceUnitId };
+  overallPieRun(param);
+  overallRadarRun({ ...param, group: group.value });
+  progressRun(param);
+  run11({ ...param, group: 'INFLUENCE_SUPPLIER' });
+  run12({ ...param, group: 'INFLUENCE_REGION' });
+  run21({ ...param, group: 'REASON_SUPPLIER' });
+  run22({ ...param, group: 'REASON_REGION' });
+});
+watch(group, () => {
+  overallRadarRun({ serviceUnitId: serviceId.value, group: group.value });
+});
+
+const buildBarData = (result = {}) => {
+  let xData = Object.keys(result);
+  let names = Object.values(result)
+    ? Object.keys(Object.values(result)[0] || {})
+    : [];
+  let sData = [];
+  for (let i = 0; i < names.length; i++) {
+    let data = [];
+    for (let j = 0; j < xData.length; j++) {
+      data.push(result[xData[j]][names[i]] || 0);
+    }
+    sData.push({
+      name: names[i],
+      data: data,
+      barWidth: '16px',
+    });
+  }
+  return { xData, seriesData: sData };
+};
+// 总体盘点饼图
+const overallPieOptions = computed(() => {
+  const result = Object.entries(overallPieData.value || {});
+  return createRingPieOption(
+    {
+      data: result.map(([name, value]) => {
+        return {
+          name,
+          value,
+        };
+      }),
+      center: ['50%', '40%'],
+      radius: [50, 80],
+      title: '质量问题累计',
+    },
+    {
+      legend: {
+        right: 'auto',
+        top: 'auto',
+        bottom: 30,
+        orient: 'horizontal',
+      },
+    }
+  );
+});
+// 总体盘点归因雷达图
+const overallRadarOptions = computed(() => {
+  const result = overallRadarData.value || {};
+  // const result = {
+  //   all: {
+  //     EXEC: 4,
+  //     EXEC1: 3,
+  //     EXEC2: 5,
+  //     EXEC3: 6,
+  //   },
+  //   小熊U: {
+  //     EXEC: 2,
+  //     EXEC1: 4,
+  //     EXEC2: 7,
+  //     EXEC3: 4,
+  //   },
+  // };
+  let xData = Object.keys(result);
+  let names = Object.values(result)
+    ? Object.keys(Object.values(result)[0] || {})
+    : [];
+  let sData = [];
+  for (let i = 0; i < xData.length; i++) {
+    let data = [];
+    for (let j = 0; j < names.length; j++) {
+      data.push(result[xData[i]][names[j]] || 0);
+    }
+    sData.push({
+      name: xData[i] === 'all' ? '全部' : xData[i],
+      value: data,
+    });
+  }
+  return createRadarOption({
+    names,
+    sData,
+  });
+});
+
+// 影响度/归因柱状图
+const barExtendOption = {
+  grid: {
+    top: 50,
+  },
+  yAxis: {
+    name: '单位:件',
+    nameTextStyle: {
+      color: '#595959',
+    },
+    nameGap: 28,
+  },
+};
+const options11 = computed(() => {
+  return createStackingBarOption(
+    buildBarData(result11.value || {}),
+    barExtendOption
+  );
+});
+const options12 = computed(() => {
+  return createStackingBarOption(
+    buildBarData(result12.value || {}),
+    barExtendOption
+  );
+});
+const options21 = computed(() => {
+  return createStackingBarOption(
+    buildBarData(result21.value || {}),
+    barExtendOption
+  );
+});
+const options22 = computed(() => {
+  return createStackingBarOption(
+    buildBarData(result22.value || {}),
+    barExtendOption
+  );
+});
+</script>
+
+<style lang="less" scoped>
+.project-analysis {
+  .page-main {
+    height: calc(100vh - 57px);
+    padding: 16px;
+    overflow: auto;
+    display: flex;
+
+    .col1 {
+      height: 100%;
+      width: 360px;
+      margin-right: 16px;
+      flex-grow: 0;
+      flex-shrink: 0;
+      .card {
+        height: 100%;
+      }
+      .chart-wrap {
+        position: relative;
+
+        &::after {
+          content: '';
+          display: block;
+          position: absolute;
+          top: 50%;
+          border-bottom: 1px solid #e5e5e5;
+          left: 14px;
+          right: 14px;
+          z-index: 9;
+        }
+      }
+
+      .chart-part {
+        height: 50%;
+      }
+    }
+    .col2 {
+      flex-grow: 2;
+      display: flex;
+      flex-direction: column;
+      height: 100%;
+
+      .col2-head {
+        flex-grow: 0;
+        flex-shrink: 0;
+      }
+      .col2-body {
+        flex-grow: 2;
+      }
+
+      .col2-row {
+        padding-top: 16px;
+        display: flex;
+        justify-content: stretch;
+        height: 50%;
+
+        .card {
+          width: calc(50% - 8px);
+          &:first-child {
+            margin-right: 8px;
+          }
+          &:last-child {
+            margin-left: 8px;
+          }
+        }
+      }
+    }
+  }
+  .card {
+    padding: 16px;
+    background-color: #fff;
+    border: 1px solid #e5e5e5;
+    border-radius: 4px;
+    display: flex;
+    flex-direction: column;
+
+    .title {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      flex-grow: 0;
+      flex-shrink: 0;
+      .label {
+        height: 22px;
+        font-size: 14px;
+        font-weight: 500;
+        color: #262626;
+        line-height: 22px;
+      }
+      .t-button {
+        padding: 0;
+        border: none !important;
+        outline: none;
+        background-color: transparent !important;
+        height: auto;
+        color: #595959;
+        :deep(div) {
+          display: none;
+        }
+      }
+    }
+    .chart-wrap {
+      flex-grow: 2;
+    }
+  }
+}
+</style>