浏览代码

coding...

刘洋 1 年之前
父节点
当前提交
68a4ea8f4f

+ 1 - 1
package.json

@@ -40,7 +40,7 @@
     "qs": "^6.11.2",
     "spark-md5": "^3.0.2",
     "swiper": "^8.4.7",
-    "tdesign-vue-next": "^1.5.1",
+    "tdesign-vue-next": "^1.6.5",
     "tinymce": "^6.7.0",
     "tvision-color": "^1.5.0",
     "unplugin-vue-setup-extend-plus": "^1.0.0",

+ 21 - 0
src/api/report.js

@@ -34,6 +34,13 @@ export const dispatchStatisticsAnalysis = (params) =>
     params,
   });
 
+//服务单元分析-按时间查询服务单元列表
+export const serviceServiceList = (params) =>
+  request({
+    url: '/api/service/analyse/list',
+    params,
+  });
+
 //服务单元概览
 export const serviceOverviewAnalysis = (params) =>
   request({
@@ -93,3 +100,17 @@ export const personNumAnalysis = (params) =>
     url: '/api/service/analyse/region/personnel',
     params,
   });
+
+// -----------------------------------SOP预警监控-----------------------------------------
+//sop-按时间查询服务单元列表
+export const sopServiceList = (params) =>
+  request({
+    url: '/api/sop/analyse/list',
+    params,
+  });
+//预警top10
+export const warningTop = (params) =>
+  request({
+    url: '/api/sop/analyse/processing',
+    params,
+  });

+ 31 - 7
src/components/common/table-loop/index.vue

@@ -1,11 +1,20 @@
 <template>
   <div class="table-loop" ref="tableLoop">
     <div class="thead">
-      <div class="td col1">排名</div>
+      <!-- <div class="td col1">排名</div>
       <div class="td col2">城市</div>
       <div class="td col3">总数</div>
       <div class="td col4">百分比</div>
-      <div class="td col5">增减</div>
+      <div class="td col5">增减</div> -->
+      <div class="td index">排名</div>
+      <div
+        class="td"
+        v-for="(item, index) in columns"
+        :key="index"
+        :style="item.styles || {}"
+      >
+        {{ item.label }}
+      </div>
     </div>
     <swiper
       :key="swiperKey"
@@ -22,15 +31,27 @@
         v-for="(item, index) in data || []"
         :key="index"
       >
-        <div class="td col1">{{ index + 1 }}</div>
-        <div class="td col2">{{ item.name }}</div>
+        <div class="td index">{{ index + 1 }}</div>
+        <!-- <div class="td col2">{{ item.name }}</div>
         <div class="td col3"> {{ item.count }} </div>
         <div class="td col4">
           <div class="process-box">
             <div class="process" :style="{ width: item.rate }"></div>
           </div>
         </div>
-        <div class="td col5"></div>
+        <div class="td col5"></div> -->
+
+        <div
+          class="td"
+          v-for="(v, i) in columns"
+          :key="i"
+          :style="item.styles || {}"
+        >
+          <span v-if="v.prop !== 'rate'">{{ item[v.prop] }}</span>
+          <div class="process-box" v-else>
+            <div class="process" :style="{ width: item.rate }"></div>
+          </div>
+        </div>
       </swiper-slide>
     </swiper>
   </div>
@@ -42,7 +63,7 @@ import { Navigation, Mousewheel, Autoplay } from 'swiper';
 import { Swiper, SwiperSlide } from 'vue-awesome-swiper';
 import 'swiper/css';
 import 'swiper/css/autoplay';
-const props = defineProps({ data: Object });
+const props = defineProps({ data: Object, columns: Array });
 const modules = ref([Navigation, Mousewheel, Autoplay]);
 const swiperKey = ref(Date.now() + '');
 const tableLoop = ref();
@@ -66,11 +87,11 @@ onMounted(() => {
   .thead {
     height: 36px;
     background-color: #f7f7f7;
-    color: #8c8c8c;
     font-size: 12px;
     .td {
       text-align: center;
       line-height: 36px;
+      color: #8c8c8c !important;
     }
   }
   .slide {
@@ -100,6 +121,9 @@ onMounted(() => {
     justify-content: center;
     align-items: center;
     height: 100%;
+    &.index {
+      width: 34px;
+    }
     .process-box {
       background-color: #d9d9d9;
       height: 6px;

+ 11 - 2
src/components/global/custom-date-picker/index.vue

@@ -3,6 +3,7 @@
     style="width: 240px"
     v-model="range"
     :presets="presets"
+    @pick="timeChange"
   />
 </template>
 <script setup name="CustomDatePicker">
@@ -12,8 +13,12 @@ import { useVModel } from '@vueuse/core';
 const props = defineProps({
   modelValue: Array,
 });
-const emit = defineEmits(['update:modelValue']);
-
+const emit = defineEmits(['update:modelValue', 'timeChange']);
+const timeChange = (e, obj) => {
+  if (obj.partial === 'end') {
+    emit('timeChange', range.value);
+  }
+};
 const range = useVModel(props, 'modelValue', emit);
 
 onMounted(() => {
@@ -22,6 +27,10 @@ onMounted(() => {
       dayjs().startOf('year').format('YYYY-MM-DD'),
       dayjs().endOf('year').format('YYYY-MM-DD'),
     ];
+    emit('timeChange', [
+      dayjs().startOf('year').format('YYYY-MM-DD'),
+      dayjs().endOf('year').format('YYYY-MM-DD'),
+    ]);
   }
 });
 

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

@@ -3,7 +3,10 @@
     <div class="title flex items-center"
       ><span class="m-r-10px">{{ props.title }}</span>
       <slot />
-      <CustomDatePicker v-model="range"></CustomDatePicker
+      <CustomDatePicker
+        v-model="range"
+        @timeChange="timeChange"
+      ></CustomDatePicker
     ></div>
     <div class="right-box flex h-full items-center">
       <img class="time-icon" src="../../../assets//imgs/time.png" />
@@ -17,7 +20,10 @@ import { useIntervalFn } from '@vueuse/core';
 import { dateFormat } from '@/utils/tool';
 import { useVModel } from '@vueuse/core';
 import CustomDatePicker from '../custom-date-picker';
-const emit = defineEmits(['update:dateRange']);
+const emit = defineEmits(['update:dateRange', 'timeChange']);
+const timeChange = (value) => {
+  emit('timeChange', value);
+};
 const props = defineProps({ title: String, dateRange: Array });
 const range = useVModel(props, 'dateRange', emit);
 const currentTime = ref(Date.now());

+ 8 - 0
src/router/asyncRoutes.js

@@ -126,6 +126,14 @@ export const devPushMenuList = [
     sort: 2,
     type: 'MENU',
   },
+  {
+    id: '2003',
+    name: 'SOP预警监控',
+    parentId: '2000',
+    url: 'test3',
+    sort: 3,
+    type: 'MENU',
+  },
 ];
 
 export default asyncRoutes;

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

@@ -31,5 +31,16 @@ export default {
         icon: 'notice',
       },
     },
+    {
+      name: 'SopAnalysis',
+      path: '/report/sop-analysis',
+      component: () => import('@/views/report/sop-analysis/index.vue'),
+      meta: {
+        title: 'SOP预警监控',
+        sort: 3,
+        alias: 'test3',
+        icon: 'notice',
+      },
+    },
   ],
 };

+ 29 - 4
src/views/report/dispatch-analysis/index.vue

@@ -39,7 +39,10 @@
             <div class="card chart5-box">
               <div class="title">研究生城市在执行派单排名</div>
               <div class="chart-wrap">
-                <table-loop :data="tableDataHandle(result5)"></table-loop>
+                <table-loop
+                  :data="tableDataHandle(result5)"
+                  :columns="tableColumns"
+                ></table-loop>
               </div>
             </div>
           </div>
@@ -84,7 +87,10 @@
             <div class="card chart8-box">
               <div class="title">教务处城市在执行派单排名</div>
               <div class="chart-wrap">
-                <table-loop :data="tableDataHandle(result8)"></table-loop>
+                <table-loop
+                  :data="tableDataHandle(result8)"
+                  :columns="tableColumns"
+                ></table-loop>
               </div>
             </div>
           </div>
@@ -198,6 +204,25 @@ const barDataHandle = (result) => {
     };
   }
 };
+
+const tableColumns = [
+  {
+    prop: 'name',
+    label: '城市',
+    styles: { width: 'calc((100% - 84px) / 2)', color: '#262626' },
+  },
+  {
+    prop: 'count',
+    label: '总数',
+    styles: { width: 'calc((100% - 84px) / 4)', color: '#262626' },
+  },
+  {
+    prop: 'name',
+    label: '百分比',
+    styles: { width: '50px', color: '#262626' },
+  },
+  { prop: 'compare', label: '增减' },
+];
 const tableDataHandle = (data) => {
   if (!data) {
     return [];
@@ -322,10 +347,10 @@ const options4 = computed(() =>
         color: #262626;
         font-size: 14px;
         font-weight: bold;
-        height: 20px;
+        height: 36px;
       }
       .chart-wrap {
-        height: calc(100% - 20px);
+        height: calc(100% - 36px);
         &.service-box {
           display: flex;
           flex-wrap: wrap;

+ 20 - 8
src/views/report/service-analysis/index.vue

@@ -1,12 +1,16 @@
 <template>
   <div class="dispatch-analysis">
-    <report-header title="服务单元分析" v-model:dateRange="curTimeRange">
-      <select-service-unit
-        style="width: auto; margin-right: 10px"
+    <report-header
+      title="服务单元分析"
+      v-model:dateRange="curTimeRange"
+      @timeChange="timeChange"
+    >
+      <t-select
+        style="width: 200px; margin-right: 10px"
+        :options="serviceOptions"
         v-model="serviceId"
-        clearable
-        defaultSelect
-      ></select-service-unit>
+        :keys="{ label: 'name', value: 'id' }"
+      ></t-select>
     </report-header>
     <div class="page-main">
       <div class="scroll-content">
@@ -159,7 +163,6 @@
 
 <script setup name="DispatchAnalysis">
 import { ref, computed, watch } from 'vue';
-import SelectServiceUnit from '@/components/common/select-service-unit/index.vue';
 import {
   createWaterBallOption,
   createRingPieOption,
@@ -177,8 +180,10 @@ import {
   getRegionList,
   deviceUseAnalysis,
   personNumAnalysis,
+  serviceServiceList,
 } from '@/api/report';
 const curTimeRange = ref([]);
+const serviceOptions = ref([]);
 const timeParams = computed(() => {
   return {
     startTime: new Date(curTimeRange.value[0]).getTime(),
@@ -237,6 +242,13 @@ const {
   run: run8,
 } = useRequest(personNumAnalysis); //大区在服务人数分布及对比
 
+const timeChange = (time) => {
+  serviceServiceList(timeParams.value).then((res) => {
+    serviceOptions.value = res || [];
+    res?.length && (serviceId.value = res[0].id);
+  });
+};
+
 watch(serviceId, (serviceUnitId) => {
   run1({ serviceUnitId });
   run2({ serviceUnitId }).then((res) => {
@@ -432,7 +444,7 @@ const options8 = computed(() => {
         width: 268px;
         height: 100%;
         .card {
-          height: calc((100% - 16px) / 2);
+          height: calc((100% - 15px) / 2);
           &:last-child {
             margin-top: 15px;
           }

+ 200 - 0
src/views/report/sop-analysis/index.vue

@@ -0,0 +1,200 @@
+<template>
+  <div class="sop-analysis">
+    <report-header
+      title="SOP预警监控"
+      v-model:dateRange="curTimeRange"
+      @timeChange="timeChange"
+    >
+      <t-select
+        style="width: 200px; margin-right: 10px"
+        :options="serviceOptions"
+        v-model="serviceId"
+        :keys="{ label: 'name', value: 'id' }"
+      ></t-select>
+    </report-header>
+    <div class="page-main">
+      <div class="scroll-content">
+        <div class="col1">
+          <div class="card">
+            <div class="title">
+              <t-select v-model="sort1" style="width: calc(100% - 50px)">
+                <t-option
+                  value="PENDING"
+                  label="项目预警待处理TOP10"
+                ></t-option>
+                <t-option
+                  value="SLOWEST"
+                  label="项目预警处理最慢TOP10"
+                ></t-option>
+                <t-option
+                  value="FASTEST"
+                  label="项目预警处理最快TOP10"
+                ></t-option>
+              </t-select>
+            </div>
+            <div class="chart-wrap">
+              <table-loop :data="tableDataHandle(result1)"></table-loop>
+            </div>
+          </div>
+          <div class="card">
+            <div class="title">
+              <t-select v-model="sort2" style="width: calc(100% - 50px)">
+                <t-option
+                  value="PENDING"
+                  label="大区预警待处理TOP10"
+                ></t-option>
+                <t-option
+                  value="SLOWEST"
+                  label="大区预警处理最慢TOP10"
+                ></t-option>
+                <t-option
+                  value="FASTEST"
+                  label="大区预警处理最快TOP10"
+                ></t-option>
+              </t-select>
+            </div>
+          </div>
+          <div class="card">
+            <div class="title">
+              <t-select v-model="sort3" style="width: calc(100% - 50px)">
+                <t-option
+                  value="PENDING"
+                  label="供应商预警待处理TOP10"
+                ></t-option>
+                <t-option
+                  value="SLOWEST"
+                  label="供应商预警处理最慢TOP10"
+                ></t-option>
+                <t-option
+                  value="FASTEST"
+                  label="供应商预警处理最快TOP10"
+                ></t-option>
+              </t-select>
+            </div>
+          </div>
+        </div>
+        <div class="col2">
+          <div class="card"></div>
+          <div class="card"></div>
+        </div>
+        <div class="col3">
+          <div class="card"></div>
+          <div class="card"></div>
+          <div class="card"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="DispatchAnalysis">
+import { ref, computed, watch } from 'vue';
+import SelectServiceUnit from '@/components/common/select-service-unit/index.vue';
+import TableLoop from '@/components/common/table-loop/index.vue';
+import { useRequest } from 'vue-request';
+import { sopServiceList, warningTop } from '@/api/report';
+const curTimeRange = ref([]);
+const serviceOptions = ref([]);
+
+const timeParams = computed(() => {
+  return {
+    startTime: new Date(curTimeRange.value[0]).getTime(),
+    endTime: new Date(curTimeRange.value[1]).getTime(),
+  };
+});
+const serviceId = ref('');
+const timeChange = (time) => {
+  sopServiceList(timeParams.value).then((res) => {
+    serviceOptions.value = res || [];
+    res?.length && (serviceId.value = res[0].id);
+  });
+};
+const sort1 = ref('PENDING');
+const sort2 = ref('PENDING');
+const sort3 = ref('PENDING');
+
+const { data: result1, loading: loading1, run: run1 } = useRequest(warningTop); //服务单元概览
+const tableDataHandle = (data) => {
+  return data || [];
+};
+watch(serviceId, (serviceId) => {
+  sort1.value = sort2.value = sort3.value = 'PENDING';
+  run1({
+    group: 'CRM',
+    serviceId,
+    sort: sort1.value,
+  });
+  run1({
+    group: 'REGION',
+    serviceId,
+    sort: sort1.value,
+  });
+  run1({
+    group: 'SUPPLIER',
+    serviceId,
+    sort: sort1.value,
+  });
+});
+</script>
+
+<style lang="less" scoped>
+.sop-analysis {
+  .page-main {
+    height: calc(100vh - 57px);
+    padding: 15px;
+    overflow: auto;
+    .scroll-content {
+      height: 100%;
+      min-height: 600px;
+      min-width: 1000px;
+      display: flex;
+      .title {
+        height: 36px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .label {
+          color: #262626;
+          font-size: 14px;
+          font-weight: bold;
+        }
+      }
+      .chart-wrap {
+        height: calc(100% - 36px);
+      }
+      .col1,
+      .col3 {
+        width: 268px;
+        height: 100%;
+        .card {
+          height: calc((100% - 30px) / 3);
+          &:not(:first-child) {
+            margin-top: 15px;
+          }
+        }
+      }
+      .col2 {
+        width: calc(100% - 566px);
+        margin-left: 15px;
+        margin-right: 15px;
+        .card {
+          &:first-child {
+            height: calc((100% - 30px) * 2 / 3 + 15px);
+          }
+          &:last-child {
+            height: calc((100% - 30px) / 3);
+            margin-top: 15px;
+          }
+        }
+      }
+    }
+
+    .card {
+      padding: 4px 10px 10px 10px;
+      background-color: #fff;
+      border: 1px solid #e5e5e5;
+      border-radius: 4px;
+    }
+  }
+}
+</style>