Kaynağa Gözat

1.1.1二期

刘洋 9 ay önce
ebeveyn
işleme
32e97acb01

+ 40 - 0
src/api/report.js

@@ -358,4 +358,44 @@ export const humanDingException = (params) =>
     url: '/api/admin/resource/security/human_ding_exception/report',
     params,
   });
+
+//流程检查
+export const flowcheckByRegion = (params) =>
+  request({
+    url: '/api/admin/flow/msg/refer/area_manager/report',
+    params,
+  });
+export const flowcheckByCoordinator = (params) =>
+  request({
+    url: '/api/admin/flow/msg/refer/regional_coordinator/report',
+    params,
+  });
+
+//质量监控分析
+export const qualityByRegion = (params) =>
+  request({
+    url: '/api/admin/quality/analyse/area_manager/report',
+    params,
+  });
+export const qualityBySupplier = (params) =>
+  request({
+    url: '/api/admin/quality/analyse/human_supplier/report',
+    params,
+  });
+export const qualityReasonTypes = (params) =>
+  request({
+    url: '/api/admin/quality/analyse/type/report',
+    params,
+  });
+export const qualityDetail = (params) =>
+  request({
+    url: '/api/admin/quality/analyse/report/view',
+    params,
+  });
+export const qualityExport = (params) =>
+  request({
+    url: '/api/admin/quality/analyse/report/view/export',
+    params,
+    loading: true,
+  });
 /******************************************************************** */

+ 4 - 0
src/components/global/chart/index.vue

@@ -63,6 +63,8 @@ const hasData = computed(() => {
   let pieHasData = IS_PIE?.data?.length;
   let IS_RADAR = props.options?.series?.find((item) => item.type === 'radar');
   let hasRadarData = IS_RADAR?.data?.length;
+  let IS_BAR = props.options?.series?.find((item) => item.type === 'bar');
+  let hasBarData = IS_BAR?.data?.length;
   let IS_NORMAL = props.options?.customType === 'NORMAL';
   let hasNormalData = props.options?.series?.length;
 
@@ -70,6 +72,8 @@ const hasData = computed(() => {
     ? pieHasData
     : IS_RADAR
     ? hasRadarData
+    : IS_BAR
+    ? hasBarData
     : IS_NORMAL
     ? hasNormalData
     : true;

+ 19 - 6
src/router/modules/report.js

@@ -49,17 +49,30 @@ export default {
       meta: {
         title: '项目进度监控',
         sort: 4,
-        alias: 'sopSchedule',
+        alias: 'FlowCheck',
         icon: 'report-project',
       },
     },
     {
-      name: 'DeviceAnalysis',
-      path: '/report/device-analysis',
-      component: () => import('@/views/report/device-analysis/index.vue'),
+      name: 'FlowcheckAnalysis',
+      path: '/report/flowcheck-analysis',
+      component: () => import('@/views/report/flowcheck-analysis/index.vue'),
       meta: {
-        title: '设备保障监控',
+        title: '流程检查统计',
         sort: 5,
+        alias: 'sopSchedule',
+        icon: 'ding-configure',
+      },
+    },
+    {
+      name: 'ResourceAnalysis',
+      path: '/report/resource-analysis',
+      // component: () => import('@/views/report/device-analysis/index.vue'),
+      component: () => import('@/views/report/resource-analysis/index.vue'),
+      meta: {
+        // title: '设备保障监控',
+        title: '资源保障分析',
+        sort: 6,
         alias: 'deviceMonitor',
         icon: 'report-device',
       },
@@ -70,7 +83,7 @@ export default {
       component: () => import('@/views/report/quality-analysis/index.vue'),
       meta: {
         title: '质量监控分析',
-        sort: 6,
+        sort: 7,
         // alias: 'qualityProblemControl',
         alias: 'qualityAnalyse',
         icon: 'report-quality',

+ 1 - 1
src/router/modules/sop.js

@@ -104,7 +104,7 @@ export default {
             title: '项目监控',
             sort: 3,
             alias: 'ProjectMonitor',
-            icon: 'project-change',
+            icon: 'hour-submit',
           },
         },
       ],

+ 164 - 0
src/views/report/flowcheck-analysis/index.vue

@@ -0,0 +1,164 @@
+<template>
+  <div class="flow-check-analysis">
+    <report-header title="服务单元分析" hideTimePicker>
+      <select-service-unit
+        v-model="serviceId"
+        clearable
+        defaultSelect
+        style="width: 220px"
+      ></select-service-unit>
+    </report-header>
+    <div class="page-main">
+      <div class="scroll-content">
+        <div class="col1">
+          <div class="module-title">按大区步骤查阅统计</div>
+          <t-table
+            size="small"
+            row-key="leadId"
+            :columns="columns1"
+            :data="tableData1 || []"
+            :loading="loading1"
+            bordered
+            :max-height="tableMaxHeight"
+            :fixed-rows="[0, 1]"
+          >
+            <template #name="{ col, row }">
+              <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
+              <span v-else style="font-weight: bold">合计</span>
+            </template>
+            <template #readMsg="{ col, row }">
+              {{ row.readMsg }}({{ row.readRate + '%' }})
+            </template>
+            <template #unReadMsg="{ col, row }">
+              {{ row.readMsg }}({{ row.unReadRate + '%' }})
+            </template>
+          </t-table>
+        </div>
+        <div class="col2">
+          <div class="module-title">按区域协调人步骤查阅统计</div>
+          <t-table
+            size="small"
+            row-key="leadId"
+            :columns="columns2"
+            :data="tableData2 || []"
+            :loading="loading1"
+            bordered
+            :max-height="tableMaxHeight"
+            :fixed-rows="[0, 1]"
+          >
+            <template #name="{ col, row }">
+              <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
+              <span v-else style="font-weight: bold">合计</span>
+            </template>
+            <template #readMsg="{ col, row }">
+              {{ row.readMsg }}({{ row.readRate + '%' }})
+            </template>
+            <template #unReadMsg="{ col, row }">
+              {{ row.readMsg }}({{ row.unReadRate + '%' }})
+            </template>
+          </t-table>
+        </div>
+      </div>
+    </div>
+    <DetailDrawer
+      v-model="showDetailDrawer"
+      :process="curProcess"
+      ref="DetailDrawerRef"
+    />
+  </div>
+</template>
+
+<script setup name="FlowcheckAnalysis">
+import { ref, computed, watch, onMounted } from 'vue';
+import ChinaPointChart from '@/components/common/china-point-chart/index.vue';
+import { useRequest } from 'vue-request';
+import { FullscreenIcon } from 'tdesign-icons-vue-next';
+import { cloneDeep } from 'lodash-es';
+import DetailDrawer from '../service-analysis/detail-drawer.vue';
+import { flowcheckByRegion, flowcheckByCoordinator } from '@/api/report';
+
+const tableMaxHeight = ref(0);
+onMounted(() => {
+  tableMaxHeight.value = parseInt(window.innerHeight) - 117;
+});
+const showDetailDrawer = ref(false);
+const curProcess = ref('');
+const {
+  data: mergeData1,
+  run: run1,
+  loading: loading1,
+} = useRequest(flowcheckByRegion);
+const {
+  data: mergeData2,
+  run: run2,
+  loading: loading2,
+} = useRequest(flowcheckByCoordinator);
+const toArr = (obj) => {
+  if (!obj || !Object.keys(obj || {}).length) {
+    return [];
+  }
+  let records = obj.records || [];
+  let push = {
+    name: '',
+    sum: obj.total,
+    readMsg: obj.readTotal,
+    unReadMsg: obj.unReadTotal,
+    readRate: obj.readRateTotal,
+    unReadRate: obj.unReadRateTotal,
+  };
+  return [...records, push];
+};
+const tableData1 = computed(() => {
+  return toArr(mergeData1.value);
+});
+const tableData2 = computed(() => {
+  return toArr(mergeData2.value);
+});
+
+const serviceId = ref('');
+watch(serviceId, (serviceId) => {
+  run1({ serviceId });
+  run2({ serviceId });
+});
+const baseColumns = [
+  { colKey: 'sum', title: '查阅总数' },
+  { colKey: 'readMsg', title: '已查阅' },
+  { colKey: 'unReadMsg', title: '未查阅' },
+];
+const columns1 = [{ colKey: 'name', title: '大区经理' }, ...baseColumns];
+const columns2 = [{ colKey: 'name', title: '区域协调人' }, ...baseColumns];
+</script>
+
+<style lang="less" scoped>
+.flow-check-analysis {
+  .page-main {
+    height: calc(100vh - 57px);
+    padding: 15px;
+    overflow: auto;
+    .scroll-content {
+      height: 100%;
+      min-width: 1000px;
+      display: flex;
+      justify-content: space-between;
+      .col1,
+      .col2 {
+        width: calc(50% - 8px);
+        height: 100%;
+        :deep(.t-table__body) {
+          font-size: 13px !important;
+          .t-link {
+            font-size: 13px !important;
+          }
+        }
+        .module-title {
+          height: 30px;
+          font-size: 16px;
+          font-weight: bold;
+          color: #000;
+          text-align: center;
+        }
+      }
+    }
+  }
+}
+</style>

+ 13 - 4
src/views/report/project-analysis/index.vue

@@ -19,6 +19,7 @@
             :data="tableData1 || []"
             :loading="loading1"
             bordered
+            :max-height="tableMaxHeightHalf"
           >
             <template #leadName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -105,6 +106,7 @@
             :data="tableData2 || []"
             bordered
             :loading="loading2"
+            :max-height="tableMaxHeightHalf"
           >
             <template #supplierName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -194,6 +196,7 @@
             :data="tableData3 || []"
             bordered
             :loading="loading3"
+            :max-height="tableMaxHeight"
           >
             <template #coordinatorName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -281,10 +284,9 @@
 </template>
 
 <script setup name="ProjectAnalysis">
-import { ref, computed, watch } from 'vue';
+import { ref, computed, watch, onMounted } from 'vue';
 import ChinaPointChart from '@/components/common/china-point-chart/index.vue';
 import { useRequest } from 'vue-request';
-import {} from '@/api/report';
 import { FullscreenIcon } from 'tdesign-icons-vue-next';
 import { cloneDeep } from 'lodash-es';
 import DetailDrawer from '../service-analysis/detail-drawer.vue';
@@ -293,6 +295,13 @@ import {
   processBySupplier,
   processByCoordinator,
 } from '@/api/report';
+const tableMaxHeightHalf = ref(0);
+const tableMaxHeight = ref(0);
+onMounted(() => {
+  tableMaxHeight.value = parseInt(window.innerHeight) - 117;
+  tableMaxHeightHalf.value = parseInt((parseInt(window.innerHeight) - 147) / 2);
+});
+
 const showDetailDrawer = ref(false);
 const curProcess = ref('');
 const {
@@ -365,9 +374,9 @@ const columns3 = [
       .col2 {
         width: calc(50% - 8px);
         :deep(.t-table__body) {
-          font-size: 12px !important;
+          font-size: 13px !important;
           .t-link {
-            font-size: 12px !important;
+            font-size: 13px !important;
           }
         }
         .module-title {

+ 0 - 142
src/views/report/quality-analysis/degree-drill-dialog.vue

@@ -1,142 +0,0 @@
-<template>
-  <my-dialog
-    :visible="visible"
-    :header="title"
-    :width="1200"
-    :closeOnOverlayClick="false"
-    @close="emit('update:visible', false)"
-  >
-    <div class="drill-search-box">
-      <div class="field">
-        <div class="label">服务单元:</div>
-        <div class="value">{{ data?.serviceName }}</div>
-      </div>
-      <div class="field" v-if="data?.supplierName">
-        <div class="label">供应商:</div>
-        <div class="value">{{ data?.supplierName }}</div>
-      </div>
-      <div class="field" v-if="data?.regionName">
-        <div class="label">大区:</div>
-        <div class="value">{{ data?.regionName }}</div>
-      </div>
-      <div class="field" v-if="data?.Influence_degree">
-        <div class="label">影响度:</div>
-        <div class="value">{{ data?.Influence_degree }}</div>
-      </div>
-      <div class="field" v-if="data?.reasonName">
-        <div class="label">问题原因类型:</div>
-        <div class="value">{{ data?.reasonName }}</div>
-      </div>
-    </div>
-    <t-table
-      ref="tableRef"
-      size="small"
-      row-key="key"
-      :columns="tableColumns"
-      :data="tableData"
-      bordered
-      :pagination="{
-        defaultCurrent: 1,
-        defaultPageSize: 10,
-        onChange,
-        showJumper: true,
-        showPageSize: false,
-        total: pagination.total,
-        current: pagination.pageNumber,
-      }"
-      v-loading="loading"
-    >
-      <template #custom-type="{ col, row }">
-        {{ customerTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #flow-status="{ col, row }">
-        {{ flowStatusFilter(row[col.colKey]) }}
-      </template>
-      <template #issues-type="{ col, row }">
-        {{ issuesTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #issues-reason="{ col, row }">
-        {{ issuesReasonTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #submit-time="{ col, row }">
-        {{ timestampFilter(row[col.colKey]) }}
-      </template>
-      <template #update-time="{ col, row }">
-        {{ timestampFilter(row[col.colKey]) }}
-      </template>
-    </t-table>
-  </my-dialog>
-</template>
-<script setup name="DegreeDrillDialog">
-import { ref, computed, reactive, watch } from 'vue';
-import { CUSTOMER_TYPE } from '@/config/constants';
-import { dateFormat } from '@/utils/tool';
-import useFetchTable from '@/hooks/useFetchTable';
-import { degreeDrill } from '@/api/report';
-import {
-  timestampFilter,
-  customerTypeFilter,
-  issuesReasonTypeFilter,
-  issuesTypeFilter,
-  flowStatusFilter,
-} from '@/utils/filter';
-const tableColumns = [
-  { colKey: 'problemNo', title: '质量问题编号', width: 200, fixed: 'left' },
-  { colKey: 'crmNo', title: '项目单号', width: 200 },
-  { colKey: 'customType', title: '客户类型', cell: 'custom-type', width: 120 },
-  { colKey: 'custom', title: '客户名称', width: 140 },
-  { colKey: 'sopTypeStr', title: '实施产品' },
-  { colKey: 'summary', title: '问题简要' },
-  { colKey: 'userNames', title: '责任人', width: 140 },
-  { colKey: 'type', title: '问题类型', cell: 'issues-type', width: 120 },
-  { colKey: 'reason', title: '问题归因', cell: 'issues-reason', width: 160 },
-  { colKey: 'influenceDegree', title: '影响度', width: 100 },
-  { colKey: 'submitter', title: '提交人', width: 140 },
-  {
-    colKey: 'submissionTime',
-    title: '提交时间',
-    cell: 'submit-time',
-    width: 180,
-  },
-  {
-    colKey: 'updateDateTime',
-    title: '更新时间',
-    cell: 'update-time',
-    width: 180,
-  },
-  { colKey: 'status', title: '流程状态', cell: 'flow-status', width: 120 },
-  { colKey: 'taskName', title: '当前节点', minWidth: 160 },
-  { colKey: 'pendApproveUsers', title: '当前负责人', width: 140 },
-];
-const props = defineProps({
-  visible: Boolean,
-  data: Object,
-  title: String,
-});
-const emit = defineEmits(['close']);
-
-watch(
-  () => props.visible,
-  (val) => {
-    val && search();
-  }
-);
-const transParams = computed(() => {
-  let data = props.data || {};
-  let params = {};
-  for (let key in data) {
-    if (!key.endsWith('Name')) {
-      params[key] = data[key];
-    }
-  }
-  return params;
-});
-
-const { loading, pagination, tableData, search, onChange } = useFetchTable(
-  degreeDrill,
-  {
-    params: transParams,
-  },
-  false
-);
-</script>

+ 248 - 0
src/views/report/quality-analysis/detail-drawer.vue

@@ -0,0 +1,248 @@
+<template>
+  <t-drawer
+    v-model:visible="visible"
+    :header="false"
+    size="80%"
+    :close-btn="true"
+    :cancelBtn="null"
+    confirmBtn="关闭"
+    :onConfirm="
+      () => {
+        visible = false;
+      }
+    "
+  >
+    <div class="drawer-body">
+      <SearchForm :params="params" :fields="fields" noBorder>
+        <template #crmUserId="{ item, params }">
+          <select-type-user
+            v-model="params[item.prop]"
+            type="ACCOUNT_MANAGER"
+          ></select-type-user>
+        </template>
+        <template #areaManagerId="{ item, params }">
+          <select-type-user
+            v-model="params[item.prop]"
+            type="REGION_MANAGER"
+          ></select-type-user>
+        </template>
+      </SearchForm>
+      <div class="table-wrap flex-1 overflow-auto">
+        <t-table
+          size="small"
+          :columns="columns"
+          row-key="problemNo"
+          :data="tableData"
+          bordered
+          :pagination="{
+            defaultCurrent: 1,
+            defaultPageSize: 10,
+            onChange,
+            showJumper: true,
+            showPageSize: false,
+            total: pagination.total,
+            current: pagination.pageNumber,
+          }"
+          :loading="loading"
+        >
+          <template #operate="{ row }">
+            <div class="table-operations" @click.stop>
+              <t-link
+                theme="primary"
+                hover="color"
+                @click="showSopDrawer(row.sopNo)"
+              >
+                查阅sop详情
+              </t-link>
+              <t-link
+                theme="primary"
+                hover="color"
+                @click="showQualityDetail(row)"
+              >
+                问题详情
+              </t-link>
+            </div>
+          </template>
+        </t-table>
+      </div>
+    </div>
+    <sop-step-dialog
+      v-model:visible="showSopStepDialog"
+      :sop="curSopData"
+      :type="curSopType"
+    ></sop-step-dialog>
+    <quality-issue-dialog
+      v-model:visible="showQualityDetailDialog"
+      :sop="curQualityData"
+      type="view"
+    ></quality-issue-dialog>
+  </t-drawer>
+</template>
+<script name="DetailDrawer" setup>
+import { useVModel } from '@vueuse/core';
+import { ref, reactive, watch, computed } from 'vue';
+import useFetchTable from '@/hooks/useFetchTable';
+import { qualityDetail, qualityExport } from '@/api/report';
+import {
+  PROJECT_PROCESS,
+  ISSUES_INFLUENCE_DEGREE,
+  ISSUES_REASON_TYPE,
+} from '@/config/constants';
+import { dictToOptionList } from '@/utils/tool';
+import { MessagePlugin } from 'tdesign-vue-next';
+import { getSopDataBySopNo } from '@/api/sop';
+import { omit } from 'lodash-es';
+import SopStepDialog from '@/views/sop/sop-manage/sop-step/sop-step-dialog.vue';
+import { issuesFeedbackListApi } from '@/api/project-quality';
+import QualityIssueDialog from '@/views/sop/sop-manage/quality-issue/quality-issue-dialog.vue';
+
+const emit = defineEmits(['update:modelValue']);
+const props = defineProps({
+  modelValue: { type: Boolean, default: false },
+});
+const curSopType = ref('');
+const curSopData = ref({});
+const showSopStepDialog = ref(false);
+const showSopDrawer = async (sopNo, type = 'view') => {
+  curSopType.value = type;
+  getSopDataBySopNo(sopNo).then((res) => {
+    if (res?.records?.length) {
+      curSopData.value = res.records[0];
+      showSopStepDialog.value = true;
+    }
+  });
+};
+const columns = computed(() => {
+  return [
+    { colKey: 'customName', title: '客户名称' },
+    { colKey: 'projectName', title: '项目名称' },
+    { colKey: 'courseName', title: '科目名称' },
+    { colKey: 'influenceDegree', title: '影响度' },
+    { colKey: 'areaManagerName', title: '大区经理' },
+    { colKey: 'dutyName', title: '责任人' },
+    { colKey: 'summary', title: '问题简要' },
+    { colKey: 'reasonStr', title: '问题原因' },
+    {
+      title: '操作',
+      colKey: 'operate',
+      fixed: 'right',
+      width: 240,
+    },
+  ];
+});
+const visible = useVModel(props, 'modelValue', emit);
+const params = reactive({
+  serviceId: '',
+  customName: '',
+  influenceDegree: '',
+  reason: '',
+  areaManagerId: '',
+});
+
+const fields = ref([
+  {
+    prop: 'customName',
+    label: '客户名称',
+    labelWidth: 80,
+    colSpan: 4.5,
+  },
+
+  {
+    prop: 'influenceDegree',
+    label: '影响度',
+    type: 'select',
+    labelWidth: 80,
+    colSpan: 4.5,
+    options: ISSUES_INFLUENCE_DEGREE.map((item) => {
+      return {
+        label: item,
+        value: item,
+      };
+    }),
+    attrs: {
+      clearable: true,
+    },
+  },
+  {
+    prop: 'reason',
+    label: '问题归因',
+    type: 'select',
+    labelWidth: 100,
+    colSpan: 4.5,
+    options: dictToOptionList(ISSUES_REASON_TYPE),
+    attrs: {
+      clearable: true,
+    },
+  },
+  {
+    prop: 'areaManagerId',
+    label: '大区经理',
+    labelWidth: 80,
+    colSpan: 4.5,
+    cell: 'areaManagerId',
+  },
+  {
+    type: 'buttons',
+    colSpan: 6,
+    children: [
+      {
+        type: 'button',
+        text: '搜索',
+        onClick: () => {
+          search();
+        },
+      },
+      {
+        type: 'button',
+        text: '导出',
+        onClick: () => {
+          qualityExport(params).then(() => {
+            MessagePlugin.success('导出成功');
+          });
+        },
+      },
+    ],
+  },
+]);
+
+const { pagination, tableData, fetchData, search, onChange, loading } =
+  useFetchTable(
+    qualityDetail,
+    {
+      params: params,
+    },
+    false
+  );
+
+const searchByOther = (obj) => {
+  Object.keys(obj).forEach((key) => {
+    params[key] = obj[key];
+  });
+  search();
+  visible.value = true;
+};
+defineExpose({ searchByOther });
+
+const showQualityDetailDialog = ref(false);
+const curQualityData = ref({});
+const showQualityDetail = (row) => {
+  issuesFeedbackListApi({
+    problemNo: row.problemNo,
+    pageNumber: 1,
+    pageSize: 1,
+  }).then((res) => {
+    curQualityData.value = res.records[0];
+    showQualityDetailDialog.value = true;
+  });
+};
+</script>
+<style lang="less" scoped>
+.table-search {
+  padding-top: 0 !important;
+}
+.drawer-body {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+</style>

+ 322 - 583
src/views/report/quality-analysis/index.vue

@@ -1,625 +1,364 @@
 <template>
   <div class="quality-analysis">
-    <report-header
-      title="质量监控分析"
-      v-model:dateRange="curTimeRange"
-      @timeChange="timeChange"
-    >
-      <t-select
-        style="width: 200px"
-        :options="serviceOptions"
+    <report-header title="质量监控分析" hideTimePicker>
+      <select-service-unit
         v-model="serviceId"
-        :keys="{ label: 'name', value: 'id' }"
-        filterable
-      ></t-select>
+        clearable
+        defaultSelect
+        style="width: 220px"
+      ></select-service-unit>
     </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
-                v-if="overallPieData"
-                :options="overallPieOptions"
-                @chartClick="chart0Click"
-              ></my-chart>
-            </div>
-            <div class="chart-part">
-              <my-chart
-                v-if="overallRadarData"
-                :options="overallRadarOptions"
-              ></my-chart>
-            </div>
-          </div>
+      <div class="scroll-content">
+        <div class="col1">
+          <div class="module-title">按大区质量问题统计</div>
+          <t-table
+            size="small"
+            row-key="areaManagerId"
+            :columns="columns1"
+            :data="data1 || []"
+            bordered
+            :max-height="tableMaxHeightHalf"
+            :loading="loading1"
+          >
+            <template #name="{ col, row }">
+              <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
+              <span v-else style="font-weight: bold">合计</span>
+            </template>
+            <template #sum="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    areaManagerId: row.areaManagerId,
+                    influenceDegree: '',
+                    supplierId: '',
+                  })
+                "
+                >{{ row.sum }}</t-link
+              >
+            </template>
+            <template #influenceDegreeA="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'A',
+                    areaManagerId: row.areaManagerId,
+                    supplierId: '',
+                  })
+                "
+                >{{ row.influenceDegreeA }}({{
+                  row.influenceDegreeARate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeB="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'B',
+                    areaManagerId: row.areaManagerId,
+                    supplierId: '',
+                  })
+                "
+                >{{ row.influenceDegreeB }}({{
+                  row.influenceDegreeBRate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeC="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'C',
+                    areaManagerId: row.areaManagerId,
+                    supplierId: '',
+                  })
+                "
+                >{{ row.influenceDegreeC }}({{
+                  row.influenceDegreeCRate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeD="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'D',
+                    areaManagerId: row.areaManagerId,
+                    supplierId: '',
+                  })
+                "
+                >{{ row.influenceDegreeD }}({{
+                  row.influenceDegreeDRate
+                }})</t-link
+              >
+            </template>
+          </t-table>
+          <div class="module-title m-t-15px">按人力商质量问题统计</div>
+          <t-table
+            size="small"
+            row-key="supplierId"
+            :columns="columns2"
+            :data="data2 || []"
+            bordered
+            :max-height="tableMaxHeightHalf"
+            :loading="loading2"
+          >
+            <template #name="{ col, row }">
+              <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
+              <span v-else style="font-weight: bold">合计</span>
+            </template>
+            <template #sum="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    supplierId: row.supplierId,
+                    influenceDegree: '',
+                    areaManagerId: '',
+                  })
+                "
+                >{{ row.sum }}</t-link
+              >
+            </template>
+            <template #influenceDegreeA="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'A',
+                    supplierId: row.supplierId,
+                    areaManagerId: '',
+                  })
+                "
+                >{{ row.influenceDegreeA }}({{
+                  row.influenceDegreeARate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeB="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'B',
+                    supplierId: row.supplierId,
+                    areaManagerId: '',
+                  })
+                "
+                >{{ row.influenceDegreeB }}({{
+                  row.influenceDegreeBRate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeC="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'C',
+                    supplierId: row.supplierId,
+                    areaManagerId: '',
+                  })
+                "
+                >{{ row.influenceDegreeC }}({{
+                  row.influenceDegreeCRate
+                }})</t-link
+              >
+            </template>
+            <template #influenceDegreeD="{ col, row }">
+              <t-link
+                theme="primary"
+                @click="
+                  searchByX({
+                    influenceDegree: 'D',
+                    supplierId: row.supplierId,
+                    areaManagerId: '',
+                  })
+                "
+                >{{ row.influenceDegreeD }}({{
+                  row.influenceDegreeDRate
+                }})</t-link
+              >
+            </template>
+          </t-table>
         </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="card">
-            <div class="title">
-              <span class="label">影响度供应商分布及对比</span>
-              <!-- <t-button variant="outline">
-                <template #icon><FullscreenIcon /></template>
-              </t-button> -->
-              <FullscreenIcon
-                class="cursor-pointer"
-                @click="chart11?.maximize"
-                color="#595959"
-              />
-            </div>
-            <div class="chart-wrap">
-              <my-chart
-                v-if="result11"
-                :options="options11"
-                ref="chart11"
-                @chartClick="chart11Click"
-              ></my-chart>
-            </div>
-          </div>
-          <div class="card">
-            <div class="title">
-              <span class="label">执行协调类归因供应商分布及对比</span>
-              <!-- <t-button variant="outline">
-                <template #icon><FullscreenIcon /></template>
-              </t-button> -->
-              <FullscreenIcon
-                class="cursor-pointer"
-                @click="chart12?.maximize"
-                color="#595959"
-              />
-            </div>
-            <div class="chart-wrap">
-              <my-chart
-                v-if="result12"
-                :options="options12"
-                ref="chart12"
-                @chartClick="chart12Click"
-              ></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> -->
-              <FullscreenIcon
-                class="cursor-pointer"
-                @click="chart21?.maximize"
-                color="#595959"
-              />
-            </div>
-            <div class="chart-wrap">
-              <my-chart
-                v-if="result21"
-                :options="options21"
-                ref="chart21"
-                @chartClick="chart21Click"
-              ></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> -->
-              <FullscreenIcon
-                class="cursor-pointer"
-                @click="chart22?.maximize"
-                color="#595959"
-              />
-            </div>
-            <div class="chart-wrap">
-              <my-chart
-                v-if="result22"
-                :options="options22"
-                ref="chart22"
-                @chartClick="chart22Click"
-              ></my-chart>
-            </div>
-          </div>
+        <div class="col2">
+          <div class="module-title">质量问题原因分类</div>
+
+          <t-table
+            size="small"
+            row-key="leadId"
+            :columns="columns3"
+            :data="data3 || []"
+            bordered
+            :max-height="tableMaxHeight"
+            :loading="loading3"
+          >
+            <template #name="{ col, row }">
+              <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
+              <span v-else style="font-weight: bold">合计</span>
+            </template>
+            <template #reasonRate="{ col, row }">
+              {{ row.reasonRate + '%' }}
+            </template>
+            <template #operate="{ row, col }">
+              <t-link theme="primary" @click="toDetail(row)">详情</t-link>
+            </template>
+          </t-table>
         </div>
       </div>
     </div>
-
-    <InventoryDrillDialog
-      v-model:visible="showInventoryDrillData"
-      :data="inventoryDrillData"
-      :footer="false"
-    ></InventoryDrillDialog>
-    <DegreeDrillDialog
-      v-model:visible="showDegreeDrillData"
-      :data="degreeDrillData"
-      :title="degreeDrillTitle"
-      :footer="false"
-    ></DegreeDrillDialog> </div
-></template>
+    <DetailDrawer v-model="showDetailDrawer" ref="DetailDrawerRef" />
+  </div>
+</template>
 
 <script setup name="QualityAnalysis">
-import { ref, computed, watch } from 'vue';
-import {
-  createRingPieOption,
-  createStackingBarOption,
-  createRadarOption,
-} from '@/utils/chart';
+import { ref, computed, watch, onMounted } from 'vue';
+import ChinaPointChart from '@/components/common/china-point-chart/index.vue';
 import { useRequest } from 'vue-request';
-import {
-  qualityServiceList,
-  qualityAnalysisPie,
-  qualityAnalysisRadar,
-  qualityAnalysisProgress,
-  qualityAnalysisInfluence,
-} from '@/api/report';
 import { FullscreenIcon } from 'tdesign-icons-vue-next';
-import { ISSUES_REASON_TYPE } from '@/config/constants';
-import InventoryDrillDialog from './inventory-drill-dialog.vue';
-import DegreeDrillDialog from './degree-drill-dialog.vue';
 import { cloneDeep } from 'lodash-es';
-const chart11 = ref();
-const chart12 = ref();
-const chart21 = ref();
-const chart22 = ref();
-const degreeDrillTitle = ref('');
-const inventoryDrillData = ref({
-  serviceUnitId: '',
-  degree: '',
-  serviceName: '',
-});
-const showInventoryDrillData = ref(false);
-const chart0Click = (params) => {
-  inventoryDrillData.value.serviceUnitId = serviceId.value;
-  inventoryDrillData.value.degree = params.name;
-  inventoryDrillData.value.serviceName = serviceOptions.value.find(
-    (item) => item.id == serviceId.value
-  )?.name;
-  showInventoryDrillData.value = true;
-};
-const degreeDrillData = ref({
-  Influence_degree: '',
-  region_id: '',
-  regionName: '',
-  reason: '',
-  supplierId: '',
-  supplierName: '',
-  serviceUnitId: '',
-  serviceName: '',
+import DetailDrawer from './detail-drawer.vue';
+import {
+  qualityByRegion,
+  qualityBySupplier,
+  qualityReasonTypes,
+} from '@/api/report';
+const tableMaxHeightHalf = ref(0);
+const tableMaxHeight = ref(0);
+onMounted(() => {
+  tableMaxHeight.value = parseInt(window.innerHeight) - 117;
+  tableMaxHeightHalf.value = parseInt((parseInt(window.innerHeight) - 147) / 2);
 });
 
-const showDegreeDrillData = ref(false);
+const showDetailDrawer = ref(false);
+const data1 = ref([]);
+const data2 = ref([]);
+const data3 = ref([]);
 
-const getReasonValue = (name) => {
-  for (let key in ISSUES_REASON_TYPE) {
-    if (ISSUES_REASON_TYPE[key] == name) {
-      return key;
-    }
-  }
-  return '';
-};
-const chart11Click = (params) => {
-  let map = getSupplierIdsMap(result11.value);
-  degreeDrillTitle.value = '影响度供应商分布';
-  degreeDrillData.value = {
-    serviceUnitId: serviceId.value,
-    serviceName: serviceOptions.value.find((item) => item.id == serviceId.value)
-      ?.name,
-    Influence_degree: params.name,
-    supplierId: map[params.seriesName],
-    supplierName: params.seriesName,
-  };
-  showDegreeDrillData.value = true;
-};
-const chart12Click = (params) => {
-  let map = getSupplierIdsMap(result12.value);
-  degreeDrillTitle.value = '执行协调类归因供应商分布';
-  degreeDrillData.value = {
-    serviceUnitId: serviceId.value,
-    serviceName: serviceOptions.value.find((item) => item.id == serviceId.value)
-      ?.name,
-    supplierId: map[params.seriesName],
-    supplierName: params.seriesName,
-    reason: getReasonValue(params.name),
-    reasonName: params.name,
-  };
-  showDegreeDrillData.value = true;
-};
-const chart21Click = (params) => {
-  let map = getRegionIdsMap(result21.value);
-  degreeDrillTitle.value = '影响度大区分布';
-  degreeDrillData.value = {
-    serviceUnitId: serviceId.value,
-    serviceName: serviceOptions.value.find((item) => item.id == serviceId.value)
-      ?.name,
-    region_id: map[params.name],
-    Influence_degree: params.seriesName,
-    regionName: params.name,
-  };
-  showDegreeDrillData.value = true;
-};
-const chart22Click = (params) => {
-  let map = getRegionIdsMap(result22.value);
-  degreeDrillTitle.value = '执行协调类归因大区分布';
-  degreeDrillData.value = {
-    serviceUnitId: serviceId.value,
-    serviceName: serviceOptions.value.find((item) => item.id == serviceId.value)
-      ?.name,
-    // region_id: map[params.name],
-    // reason: getReasonValue(params.seriesName),
-    // regionName: params.name,
-    // reasonName: params.seriesName,
-    region_id: map[params.name],
-    Influence_degree: params.seriesName,
-    regionName: params.name,
-  };
-  showDegreeDrillData.value = true;
-};
-const curTimeRange = ref([]);
-const timeParams = computed(() => {
-  return {
-    startTime: new Date(curTimeRange.value[0] + ' 00:00:00').getTime(),
-    endTime: new Date(curTimeRange.value[1] + ' 23:59:59').getTime(),
-  };
-});
-const groupOptions = ref([
-  { label: '按供应商', value: 'REASON_SUPPLIER' },
-  { label: '按大区', value: 'REASON_REGION' },
-]);
-let group = ref('REASON_SUPPLIER');
-const serviceOptions = ref([]);
+const loading1 = ref(false);
+const loading2 = ref(false);
+const loading3 = ref(false);
 const serviceId = ref('');
-const timeChange = (time) => {
-  qualityServiceList(timeParams.value).then((res) => {
-    serviceOptions.value = res || [];
-    res?.length && (serviceId.value = res[0].id);
+watch(serviceId, (serviceId) => {
+  loading1.value = true;
+  loading2.value = true;
+  loading3.value = true;
+  qualityByRegion({ serviceId }).then((res) => {
+    loading1.value = false;
+    data1.value = res?.records || [];
+    let push = {
+      name: '',
+      sum: res?.total, //总数
+      influenceDegreeA: res?.influenceDegreeATotal, //数量
+      influenceDegreeARate: res?.influenceDegreeARateTotal, //比例(直接+'%'号展示)
+      influenceDegreeB: res?.influenceDegreeBTotal,
+      influenceDegreeBRate: res?.influenceDegreeBRateTotal,
+      influenceDegreeC: res?.influenceDegreeCTotal,
+      influenceDegreeCRate: res?.influenceDegreeCRateTotal,
+      influenceDegreeD: res?.influenceDegreeDTotal,
+      influenceDegreeDRate: res?.influenceDegreeDRateTotal,
+    };
+    data1.value.push(push);
   });
-};
-// 总体盘点饼图
-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: 'REASON_SUPPLIER' });
-  run21({ ...param, group: 'INFLUENCE_REGION' });
-  run22({ ...param, group: 'REASON_REGION' });
-});
-watch(group, () => {
-  overallRadarRun({ serviceUnitId: serviceId.value, group: group.value });
-});
-
-const buildBarData1 = (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 buildBarData2 = (result = {}) => {
-  let xData = Object.keys(result);
-  let names = Object.values(result)
-    ? Object.keys(Object.values(result)[0] || {}).filter((key) => key !== 'id')
-    : [];
-  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 <script names.length; j++) {
-  //     data.push(result[xData[i]][names[j]] || 0);
-  //   }
-  //   sData.push({
-  //     name: xData[i] === 'all' ? '全部' : xData[i],
-  //     value: data,
-  //   });
-  // }
-
-  const sData = Object.entries(result).map((arr) => {
-    return {
-      name: arr[0],
-      data: Object.values(
-        Object.keys(ISSUES_REASON_TYPE).reduce((obj, key) => {
-          obj[key] = arr[1][key] || 0;
-          return obj;
-        }, {})
-      ),
+  qualityBySupplier({ serviceId }).then((res) => {
+    loading2.value = false;
+    data2.value = res?.records || [];
+    let push = {
+      name: '',
+      sum: res?.total,
+      influenceDegreeA: res?.influenceDegreeATotal,
+      influenceDegreeARate: res?.influenceDegreeARateTotal,
+      influenceDegreeB: res?.influenceDegreeBTotal,
+      influenceDegreeBRate: res?.influenceDegreeBRateTotal,
+      influenceDegreeC: res?.influenceDegreeCTotal,
+      influenceDegreeCRate: res?.influenceDegreeCRateTotal,
+      influenceDegreeD: res?.influenceDegreeDTotal,
+      influenceDegreeDRate: res?.influenceDegreeDRateTotal,
     };
+    data2.value.push(push);
   });
-  return createRadarOption({
-    names: Object.values(ISSUES_REASON_TYPE),
-    sData,
+  qualityReasonTypes({ serviceId }).then((res) => {
+    loading3.value = false;
+    data3.value = res?.records || [];
+    let push = {
+      name: '',
+      reasonSum: res?.total,
+      reasonRate: res?.reasonRateTotal,
+    };
+    data3.value.push(push);
   });
 });
-
-// 影响度/归因柱状图
-const barExtendOption = {
-  grid: {
-    top: 50,
-    left: 15,
+const baseColumns = [
+  { colKey: 'sum', title: '质量问题总数' },
+  { colKey: 'influenceDegreeA', title: 'A类' },
+  { colKey: 'influenceDegreeB', title: 'B类' },
+  { colKey: 'influenceDegreeC', title: 'C类' },
+  { colKey: 'influenceDegreeD', title: 'D类' },
+];
+const columns1 = [{ colKey: 'name', title: '大区经理' }, ...baseColumns];
+const columns2 = [{ colKey: 'name', title: '人力商' }, ...baseColumns];
+const columns3 = [
+  { colKey: 'name', title: '问题原因' },
+  { colKey: 'reasonSum', title: '数量' },
+  { colKey: 'reasonRate', title: '占比' },
+  {
+    title: '操作',
+    colKey: 'operate',
+    width: 100,
   },
-  yAxis: {
-    name: '件',
-    nameTextStyle: {
-      color: '#595959',
-    },
-    nameGap: 28,
-  },
-};
-const options11 = computed(() => {
-  return createStackingBarOption(
-    buildBarData1(result11.value || {}),
-    barExtendOption
-  );
-});
-const options12 = computed(() => {
-  let res = result12.value || {};
-  let obj = Object.keys(res).reduce((o, k) => {
-    if (ISSUES_REASON_TYPE[k]) {
-      o[ISSUES_REASON_TYPE[k]] = res[k];
-    } else {
-      o[k] = res[k];
-    }
-    return o;
-  }, {});
+];
 
-  return createStackingBarOption(buildBarData1(obj), barExtendOption);
-});
-const getSupplierIdsMap = (result) => {
-  let map = {};
-  let res = cloneDeep(result || {});
-  let objs = Object.values(res);
-  for (let i = 0; i < objs.length; i++) {
-    let obj = objs[i];
-    for (let k in obj) {
-      map[k] = obj[k].id;
-    }
-  }
-  return map;
+const DetailDrawerRef = ref();
+const searchByX = (obj) => {
+  DetailDrawerRef.value.searchByOther(obj);
 };
-const getRegionIdsMap = (result) => {
-  let map = {};
-  let res = cloneDeep(result || {});
-  for (let k in res) {
-    map[k] = res[k].id;
-  }
-  return map;
+
+const toDetail = (row) => {
+  searchByX({
+    influenceDegree: '',
+    supplierId: '',
+    areaManagerId: '',
+    reason: row.reason,
+  });
 };
-const options21 = computed(() => {
-  return createStackingBarOption(
-    buildBarData2(result21.value || {}),
-    barExtendOption
-  );
-});
-const options22 = computed(() => {
-  let res = result22.value || {};
-  for (let key in res) {
-    let oldObj = res[key];
-    let objNew = Object.keys(oldObj).reduce((o, k) => {
-      if (ISSUES_REASON_TYPE[k]) {
-        o[ISSUES_REASON_TYPE[k]] = oldObj[k];
-      } else {
-        o[k] = res[k];
-      }
-      return o;
-    }, {});
-    // res[key] = objNew;
-  }
-  return createStackingBarOption(buildBarData2(res), barExtendOption);
-});
 </script>
 
 <style lang="less" scoped>
 .quality-analysis {
   .page-main {
     height: calc(100vh - 57px);
-    min-width: 1000px;
-    min-height: 600px;
-    padding: 16px;
+    padding: 15px;
     overflow: auto;
-    display: flex;
-    justify-content: stretch;
-
-    .col1 {
-      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;
-      .col2-head {
-        height: 85px;
-        margin-bottom: 16px;
-      }
-      .col2-body {
-        height: calc(100% - 85px - 16px);
-        position: relative;
-        .card {
-          position: absolute;
-          width: calc(50% - 8px);
-          height: calc(50% - 8px);
-          &:nth-of-type(1) {
-            top: 0;
-            left: 0;
-          }
-          &:nth-of-type(2) {
-            top: 0;
-            right: 0;
-          }
-          &:nth-of-type(3) {
-            bottom: 0;
-            left: 0;
-          }
-          &:nth-of-type(4) {
-            bottom: 0;
-            right: 0;
-          }
-        }
-      }
-    }
-  }
-  .card {
-    padding: 16px;
-    background-color: #fff;
-    border: 1px solid #e5e5e5;
-    border-radius: 4px;
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-
-    .title {
+    .scroll-content {
+      height: 100%;
+      min-width: 1000px;
       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;
+      .col1,
+      .col2 {
+        width: calc(50% - 8px);
+        :deep(.t-table__body) {
+          font-size: 13px !important;
+          .t-link {
+            font-size: 13px !important;
+          }
+        }
+        .module-title {
+          height: 30px;
+          font-size: 16px;
+          font-weight: bold;
+          color: #000;
+          text-align: center;
         }
       }
     }
-    .chart-wrap {
-      flex-grow: 2;
-      overflow: hidden;
-    }
   }
 }
 </style>

+ 0 - 125
src/views/report/quality-analysis/inventory-drill-dialog.vue

@@ -1,125 +0,0 @@
-<template>
-  <my-dialog
-    :visible="visible"
-    :header="`质量问题盘点`"
-    :width="1200"
-    :closeOnOverlayClick="false"
-    @close="emit('update:visible', false)"
-  >
-    <div class="drill-search-box">
-      <div class="field">
-        <div class="label">服务单元:</div>
-        <div class="value">{{ props.data?.serviceName }}</div>
-      </div>
-      <div class="field">
-        <div class="label">影响度:</div>
-        <div class="value">{{ props.data?.degree }}</div>
-      </div>
-    </div>
-    <t-table
-      ref="tableRef"
-      size="small"
-      row-key="key"
-      :columns="tableColumns"
-      :data="tableData"
-      bordered
-      :pagination="{
-        defaultCurrent: 1,
-        defaultPageSize: 10,
-        onChange,
-        showJumper: true,
-        showPageSize: false,
-        total: pagination.total,
-        current: pagination.pageNumber,
-      }"
-      v-loading="loading"
-    >
-      <template #custom-type="{ col, row }">
-        {{ customerTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #flow-status="{ col, row }">
-        {{ flowStatusFilter(row[col.colKey]) }}
-      </template>
-      <template #issues-type="{ col, row }">
-        {{ issuesTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #issues-reason="{ col, row }">
-        {{ issuesReasonTypeFilter(row[col.colKey]) }}
-      </template>
-      <template #submit-time="{ col, row }">
-        {{ timestampFilter(row[col.colKey]) }}
-      </template>
-      <template #update-time="{ col, row }">
-        {{ timestampFilter(row[col.colKey]) }}
-      </template>
-    </t-table>
-  </my-dialog>
-</template>
-<script setup name="InventoryDrillDialog">
-import { ref, computed, reactive, watch } from 'vue';
-import { CUSTOMER_TYPE } from '@/config/constants';
-import { dateFormat } from '@/utils/tool';
-import useFetchTable from '@/hooks/useFetchTable';
-import { inventoryDrill } from '@/api/report';
-import {
-  timestampFilter,
-  customerTypeFilter,
-  issuesReasonTypeFilter,
-  issuesTypeFilter,
-  flowStatusFilter,
-} from '@/utils/filter';
-const tableColumns = [
-  { colKey: 'problemNo', title: '质量问题编号', width: 200, fixed: 'left' },
-  { colKey: 'crmNo', title: '项目单号', width: 200 },
-  { colKey: 'customType', title: '客户类型', cell: 'custom-type', width: 120 },
-  { colKey: 'custom', title: '客户名称', width: 140 },
-  { colKey: 'sopTypeStr', title: '实施产品' },
-  { colKey: 'summary', title: '问题简要' },
-  { colKey: 'userNames', title: '责任人', width: 140 },
-  { colKey: 'type', title: '问题类型', cell: 'issues-type', width: 120 },
-  { colKey: 'reason', title: '问题归因', cell: 'issues-reason', width: 160 },
-  { colKey: 'influenceDegree', title: '影响度', width: 100 },
-  { colKey: 'submitter', title: '提交人', width: 140 },
-  {
-    colKey: 'submissionTime',
-    title: '提交时间',
-    cell: 'submit-time',
-    width: 180,
-  },
-  {
-    colKey: 'updateDateTime',
-    title: '更新时间',
-    cell: 'update-time',
-    width: 180,
-  },
-  { colKey: 'status', title: '流程状态', cell: 'flow-status', width: 120 },
-  { colKey: 'taskName', title: '当前节点', minWidth: 160 },
-  { colKey: 'pendApproveUsers', title: '当前负责人', width: 140 },
-];
-const props = defineProps({
-  visible: Boolean,
-  data: Object,
-});
-const emit = defineEmits(['close']);
-
-watch(
-  () => props.visible,
-  (val) => {
-    val && search();
-  }
-);
-const transParams = computed(() => {
-  return {
-    serviceUnitId: props.data?.serviceUnitId,
-    degree: props.data?.degree,
-  };
-});
-
-const { loading, pagination, tableData, search, onChange } = useFetchTable(
-  inventoryDrill,
-  {
-    params: transParams,
-  },
-  false
-);
-</script>

+ 179 - 10
src/views/report/resource-analysis/index.vue

@@ -10,9 +10,40 @@
     </report-header>
     <div class="page-main">
       <div class="scroll-content">
-        <div class="col1"></div>
-        <div class="col2"></div>
-        <div class="col3"></div>
+        <div class="col1">
+          <div class="level1 card">
+            <div class="title"
+              >设备商供货数量分布
+              <span> (总数量:{{ getAllCount(data1) }})</span></div
+            >
+            <div class="chart-wrap"
+              ><my-chart :options="options1"></my-chart
+            ></div>
+          </div>
+          <div class="level2 card">
+            <div class="title"
+              >人力数量分布
+              <span> (总数量:{{ getAllCount(data2) }})</span></div
+            >
+            <div class="chart-wrap"
+              ><my-chart :options="options2"></my-chart
+            ></div>
+          </div>
+        </div>
+        <div class="col2">
+          <div class="level1 card">
+            <div class="title">大区考勤异常数统计</div>
+            <div class="chart-wrap">
+              <my-chart :options="options3"></my-chart>
+            </div>
+          </div>
+          <div class="level2 card">
+            <div class="title">人力供应商考勤异常数统计</div>
+            <div class="chart-wrap">
+              <my-chart :options="options4"></my-chart>
+            </div>
+          </div>
+        </div>
       </div>
     </div>
   </div>
@@ -23,9 +54,77 @@ import { ref, computed, watch } from 'vue';
 import { useRequest } from 'vue-request';
 import { FullscreenIcon } from 'tdesign-icons-vue-next';
 import { cloneDeep } from 'lodash-es';
-import {} from '@/api/report';
+import {
+  deviceSupplier,
+  humanSupplier,
+  leadDingException,
+  humanDingException,
+} from '@/api/report';
+import { createCakePieOption, createBarOption } from '@/utils/chart';
+
+const data1 = ref([]);
+const data2 = ref([]);
+const data3 = ref([]);
+const data4 = ref([]);
+
+const getAllCount = (data) => {
+  return (data || []).reduce((total, item) => {
+    return total + item.count;
+  }, 0);
+};
+const options1 = computed(() => {
+  return createCakePieOption({
+    data: data1.value.map((item) => ({
+      name: item.deviceSupplierName,
+      value: item.count,
+    })),
+    radius: [0, 85],
+    center: ['30%', '50%'],
+  });
+});
+const options2 = computed(() => {
+  return createCakePieOption({
+    data: data2.value.map((item) => ({
+      name: item.humanSupplierName,
+      value: item.count,
+    })),
+    radius: [0, 85],
+    center: ['30%', '50%'],
+  });
+});
+const barDataHandle = (data) => {
+  return {
+    xData: data?.map((item) => item.areaManagerName) || [],
+    seriesData: [
+      {
+        name: '大区考勤异常数统计',
+        data: data?.map((item) => item.count) || [],
+      },
+    ],
+  };
+};
+const options3 = computed(() => {
+  return createBarOption(barDataHandle(data3.value));
+});
+const options4 = computed(() => {
+  return createBarOption(barDataHandle(data4.value));
+});
 
 const serviceId = ref('');
+watch(serviceId, (serviceId) => {
+  deviceSupplier({ serviceId }).then((res) => {
+    data1.value = res?.records || [];
+  });
+  humanSupplier({ serviceId }).then((res) => {
+    data2.value = res?.records || [];
+  });
+  leadDingException({ serviceId }).then((res) => {
+    data3.value = res || [];
+  });
+  humanDingException({ serviceId }).then((res) => {
+    data4.value = res || [];
+  });
+});
 </script>
 
 <style lang="less" scoped>
@@ -38,15 +137,85 @@ const serviceId = ref('');
       height: 100%;
       display: flex;
       .col1,
-      .col2,
-      .col3 {
-        width: calc((100% - 45px) / 3);
-      }
-      .col1 {
+      .col2 {
+        width: calc((100% - 16px) / 2);
         height: 100%;
         .level1,
         .level2 {
-          height: calc((100% - 30px) / 2);
+          height: calc((100% - 16px) / 2);
+        }
+        .level2 {
+          margin-top: 15px;
+        }
+      }
+
+      .col2 {
+        margin-left: 15px;
+      }
+      .card {
+        padding: 4px 10px;
+        background-color: #fff;
+        border: 1px solid #e5e5e5;
+        border-radius: 4px;
+        .title {
+          height: 36px;
+          display: flex;
+          align-items: center;
+          span {
+            color: @brand-color;
+          }
+        }
+        .chart-wrap {
+          height: calc(100% - 36px);
+          padding: 2px;
+          &.service-box {
+            display: flex;
+            flex-wrap: wrap;
+            justify-content: space-between;
+            align-content: flex-start;
+          }
+          .service-item {
+            height: 54px;
+            width: calc(50% - 8px);
+            margin-bottom: 8px;
+            padding: 6px 8px;
+            background-color: #f7f7f7;
+            border-radius: 2px;
+            cursor: pointer;
+            &:hover {
+              box-shadow: 0 0 0 2px #4080ff;
+            }
+
+            .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%;
+                }
+              }
+            }
+          }
         }
       }
     }

+ 35 - 19
src/views/report/service-analysis/detail-drawer.vue

@@ -4,6 +4,13 @@
     :header="false"
     size="80%"
     :close-btn="true"
+    :cancelBtn="null"
+    confirmBtn="关闭"
+    :onConfirm="
+      () => {
+        visible = false;
+      }
+    "
   >
     <div class="drawer-body">
       <SearchForm :params="params" :fields="fields" noBorder>
@@ -36,6 +43,7 @@
             total: pagination.total,
             current: pagination.pageNumber,
           }"
+          :loading="loading"
         >
           <template #fieldObj="{ row, col }">
             {{ fieldObjMap[row[col.colKey]] || '-' }}
@@ -187,6 +195,9 @@ const fields = ref([
     colSpan: 4.5,
     type: 'select',
     options: dictToOptionList(PROJECT_PROCESS),
+    attrs: {
+      clearable: true,
+    },
   },
   {
     prop: 'leadId',
@@ -210,18 +221,18 @@ const fields = ref([
         type: 'button',
         text: '导出',
         onClick: () => {
-          let p = {};
-          if (otherParamActive.value) {
-            p = { ...params };
-          } else {
-            p = omit(params, [
-              'province',
-              'supplierId',
-              'coordinatorId',
-              'fieldObj',
-            ]);
-          }
-          exportByProcess(p).then(() => {
+          // let p = {};
+          // if (otherParamActive.value) {
+          //   p = { ...params };
+          // } else {
+          //   p = omit(params, [
+          //     'province',
+          //     'supplierId',
+          //     'coordinatorId',
+          //     'fieldObj',
+          //   ]);
+          // }
+          exportByProcess(params).then(() => {
             MessagePlugin.success('导出成功');
             otherParamActive.value = false;
           });
@@ -231,13 +242,14 @@ const fields = ref([
   },
 ]);
 
-const { pagination, tableData, fetchData, search, onChange } = useFetchTable(
-  getDetailByProcess,
-  {
-    params: params,
-  },
-  false
-);
+const { pagination, tableData, fetchData, search, onChange, loading } =
+  useFetchTable(
+    getDetailByProcess,
+    {
+      params: params,
+    },
+    false
+  );
 const data = computed(() => {
   return (tableData.value || []).map((item) => ({
     key: Math.random(),
@@ -247,6 +259,10 @@ const data = computed(() => {
 const mixSearch = () => {
   if (otherParamActive.value) {
     otherParamActive.value = false;
+    params.province = '';
+    params.supplierId = '';
+    params.coordinatorId = '';
+    params.fieldObj = '';
   }
   search();
 };

+ 13 - 3
src/views/report/service-analysis/index.vue

@@ -12,7 +12,7 @@
       <div class="scroll-content">
         <div class="col1">
           <div class="row1">
-            <div class="table">
+            <div class="table" v-loading="loading1">
               <div class="table-title">服务单元概况</div>
               <div class="tr">
                 <div class="td">累计派单数量</div>
@@ -37,7 +37,7 @@
             </div>
           </div>
           <div class="row2">
-            <div class="table table2">
+            <div class="table table2" v-loading="loading2">
               <div class="table-title">项目整体进度</div>
               <div class="tr">
                 <div class="td">所处阶段</div>
@@ -56,7 +56,7 @@
             </div>
           </div>
         </div>
-        <div class="col2">
+        <div class="col2" v-loading="loading3">
           <ChinaPointChart :data="mapData" @click="mapClick"></ChinaPointChart>
         </div>
       </div>
@@ -85,19 +85,29 @@ const overviewData = ref({});
 const table2data = ref([]);
 const mapData = ref([]);
 
+const loading1 = ref(false);
+const loading2 = ref(false);
 const _getOverviewData = (serviceUnitId) => {
+  loading1.value = true;
   getOverviewData({ serviceUnitId }).then((res) => {
     overviewData.value = res || {};
+    loading1.value = false;
   });
 };
 const _getProjectProgress = (serviceUnitId) => {
+  loading2.value = true;
   getProjectProgress({ serviceUnitId }).then((res) => {
+    loading2.value = false;
+
     table2data.value = res || [];
   });
 };
+const loading3 = ref(false);
 const _getChinaMapData = (serviceUnitId) => {
+  loading3.value = true;
   getChinaMapData({ serviceUnitId }).then((res) => {
     mapData.value = res || [];
+    loading3.value = false;
   });
 };
 watch(serviceId, (serviceUnitId) => {

+ 14 - 3
src/views/report/sop-analysis/index.vue

@@ -19,6 +19,7 @@
             :data="tableData1 || []"
             bordered
             :loading="loading1"
+            :max-height="tableMaxHeightHalf"
           >
             <template #leadName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -97,6 +98,7 @@
             :data="tableData2 || []"
             bordered
             :loading="loading2"
+            :max-height="tableMaxHeightHalf"
           >
             <template #supplierName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -178,6 +180,7 @@
             :data="tableData3 || []"
             bordered
             :loading="loading3"
+            :max-height="tableMaxHeight"
           >
             <template #coordinatorName="{ col, row }">
               <span v-if="row[col.colKey]">{{ row[col.colKey] }}</span>
@@ -258,12 +261,20 @@
 </template>
 
 <script setup name="SopAnalysis">
-import { ref, computed, watch } from 'vue';
+import { ref, computed, watch, onMounted } from 'vue';
 import { useRequest } from 'vue-request';
 import { FullscreenIcon } from 'tdesign-icons-vue-next';
 import { cloneDeep } from 'lodash-es';
 import DetailDrawer from '../service-analysis/detail-drawer.vue';
 import { warnByRegion, warnBySupplier, warnByCoordinator } from '@/api/report';
+
+const tableMaxHeightHalf = ref(0);
+const tableMaxHeight = ref(0);
+onMounted(() => {
+  tableMaxHeight.value = parseInt(window.innerHeight) - 117;
+  tableMaxHeightHalf.value = parseInt((parseInt(window.innerHeight) - 147) / 2);
+});
+
 const showDetailDrawer = ref(false);
 const curProcess = ref('');
 const {
@@ -322,9 +333,9 @@ const columns3 = [
       .col2 {
         width: calc(50% - 8px);
         :deep(.t-table__body) {
-          font-size: 12px !important;
+          font-size: 13px !important;
           .t-link {
-            font-size: 12px !important;
+            font-size: 13px !important;
           }
         }
         .module-title {

+ 1 - 0
src/views/sop/sop-manage/quality-issue/index.vue

@@ -563,6 +563,7 @@ const curStepData = computed(
   border: 1px solid #e5e5e5;
   border-radius: 4px;
   padding: 4px 15px;
+  min-height: 40px;
   .history-item {
     width: 100%;
     padding: 8px 0;