Browse Source

二期需求开发中...

刘洋 1 year ago
parent
commit
5ff451fbfa

+ 23 - 0
src/api/report.js

@@ -140,3 +140,26 @@ export const projectStageAnalysis = (params) =>
     url: '/api/sop/schedule/progress',
     params,
   });
+
+// -----------------------------------设备保障监控-----------------------------------------
+export const deviceStatusAnalysis = (params) =>
+  request({
+    url: '/api/sys/device/monitor/count',
+    params,
+  });
+export const deviceStatusTable = (params) =>
+  request({
+    url: '/api/sys/device/monitor/countByModel',
+    params,
+  });
+
+export const resourceWarningAnalysis = (params) =>
+  request({
+    url: '/api/sys/device/monitor/countByServiceUnit',
+    params,
+  });
+export const resourceWarningTable = (params) =>
+  request({
+    url: '/api/sys/device/monitor/countByServiceUnitDetail',
+    params,
+  });

+ 6 - 1
src/components/global/report-header/index.vue

@@ -4,6 +4,7 @@
       ><span style="margin-right: 10px">{{ props.title }}</span>
       <slot />
       <CustomDatePicker
+        v-if="!props.hideTimePicker"
         style="margin-left: 10px"
         v-model="range"
         @timeChange="timeChange"
@@ -30,7 +31,11 @@ const emit = defineEmits(['update:dateRange', 'timeChange']);
 const timeChange = (value) => {
   emit('timeChange', value);
 };
-const props = defineProps({ title: String, dateRange: Array });
+const props = defineProps({
+  title: String,
+  dateRange: Array,
+  hideTimePicker: Boolean,
+});
 const range = useVModel(props, 'dateRange', emit);
 const currentTime = ref(Date.now());
 const updateTime = () => {

+ 8 - 0
src/router/asyncRoutes.js

@@ -142,6 +142,14 @@ export const devPushMenuList = [
     sort: 4,
     type: 'MENU',
   },
+  {
+    id: '2005',
+    name: '设备保障监控',
+    parentId: '2000',
+    url: 'test5',
+    sort: 5,
+    type: 'MENU',
+  },
 ];
 
 export default asyncRoutes;

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

@@ -48,10 +48,21 @@ export default {
       component: () => import('@/views/report/project-analysis/index.vue'),
       meta: {
         title: '项目进度监控',
-        sort: 3,
+        sort: 4,
         alias: 'test4',
         icon: 'report-project',
       },
     },
+    {
+      name: 'DeviceAnalysis',
+      path: '/report/device-analysis',
+      component: () => import('@/views/report/device-analysis/index.vue'),
+      meta: {
+        title: '设备保障监控',
+        sort: 5,
+        alias: 'test5',
+        icon: 'report-project',
+      },
+    },
   ],
 };

+ 7 - 2
src/utils/chart.js

@@ -346,7 +346,12 @@ export const createCakePieOption = (
         },
         itemWidth: 10,
         itemHeight: 10,
-        // height: 150,
+        formatter: function (name) {
+          let item = data.find((item) => item.name == name);
+          let percent =
+            total == 0 ? 0 : ((item?.value / total) * 100).toFixed(2);
+          return `${name} ${item?.value} (${percent + '%'})`;
+        },
       },
       series: [
         {
@@ -581,6 +586,6 @@ export const createPercentBarOption = (
         data: item,
       })),
     },
-    {}
+    extend
   );
 };

+ 1 - 0
src/utils/tool.js

@@ -159,6 +159,7 @@ export const screen = (element) => {
 };
 /* 百分比值 */
 export const division = (a, b, fixed = 2) => {
+  if (b == 0) return '0.00';
   return ((a * 100) / b).toFixed(fixed);
 };
 /* 复制对象 */

+ 222 - 0
src/views/report/device-analysis/index.vue

@@ -0,0 +1,222 @@
+<template>
+  <div class="device-analysis">
+    <report-header title="设备保障监控" :hideTimePicker="true">
+      <t-select
+        style="width: 200px; margin-right: 10px"
+        :options="supplierList"
+        v-model="supplierId"
+        :keys="{ label: 'name', value: 'id' }"
+      ></t-select>
+      <t-select
+        style="width: 200px"
+        :options="modelList"
+        v-model="model"
+      ></t-select>
+    </report-header>
+    <div class="page-main">
+      <div class="scroll-content">
+        <div class="card">
+          <div class="title">库存监控</div>
+          <div class="chart-wrap">
+            <my-chart :options="options1"></my-chart>
+          </div>
+          <t-table
+            size="small"
+            row-key="index"
+            :columns="columns"
+            :data="tableData"
+            bordered
+            v-loading="loading2"
+          >
+          </t-table>
+        </div>
+        <div class="card m-t-10px">
+          <div class="title">资源预警监控</div>
+          <div class="detail">
+            总配额:{{ result3?.DEVICES }} | 总差额空闲比:{{
+              result3?.DEVICES - result3?.OUTS
+            }}/{{ result3?.ISIN }} | 总占用:{{ result3?.OUTS }} | 总满足率:{{
+              division(result3?.OUTS, result3?.DEVICES) + '%'
+            }}
+          </div>
+          <t-table
+            size="small"
+            row-key="index"
+            :columns="columns2"
+            :data="tableData2"
+            bordered
+            v-loading="loading4"
+          >
+          </t-table>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="DeviceAnalysis">
+import { ref, computed, onMounted, watch } from 'vue';
+import { useRequest } from 'vue-request';
+import { createCakePieOption } from '@/utils/chart';
+import {
+  deviceStatusAnalysis,
+  deviceStatusTable,
+  resourceWarningAnalysis,
+  resourceWarningTable,
+} from '@/api/report';
+import { supplierListApi, deviceBrandListApi } from '@/api/system';
+import { division } from '@/utils/tool';
+
+const supplierId = ref('');
+const supplierList = ref([]);
+const model = ref(null);
+const modelList = ref([]);
+const statusMap = {
+  ISIN: '空闲',
+  ISOUT: '占用',
+  BREAK_DOWN: '故障',
+};
+supplierListApi({ type: 'DEVICE' }).then((res) => {
+  supplierList.value = res || [];
+  res?.length && (supplierId.value = res[0].id);
+});
+deviceBrandListApi().then((res) => {
+  let mList = [];
+  let arr = res
+    .map((item) => {
+      return (item.list || []).map((v) => {
+        v.name = item.brand + ' - ' + v.model;
+        return v;
+      });
+    })
+    .flat();
+  for (let i = 0; i < arr.length; i++) {
+    if (!mList.find((item) => item.model == arr[i].model)) {
+      mList.push(arr[i]);
+    }
+  }
+  modelList.value = [
+    { label: '全部型号', value: '' },
+    ...mList.map((item) => ({ label: item.name, value: item.model })),
+  ];
+});
+const {
+  data: result1,
+  loading: loading1,
+  run: run1,
+} = useRequest(deviceStatusAnalysis);
+const {
+  data: result2,
+  loading: loading2,
+  run: run2,
+} = useRequest(deviceStatusTable);
+const { data: result3, run: run3 } = useRequest(resourceWarningAnalysis);
+const { data: result4, run: run4 } = useRequest(resourceWarningTable);
+onMounted(() => {
+  run3();
+  run4();
+});
+const tableData = computed(() => {
+  if (!model.value) {
+    return result2.value || [];
+  } else {
+    return result2.value
+      ?.filter((item) => item.MODEL == model.value)
+      .map((item, index) => {
+        item.index = index + '';
+        item.occupyRate =
+          item.TOTAL == (0 ? 0 : (item.ISOUT / item.TOTAL) * 100) + '%';
+        return item;
+      });
+  }
+});
+const tableData2 = computed(() => {
+  return result4?.value?.map((item) => {
+    item.difference = (item.devices || 0) - item.OUTS;
+    item.rate = division(item?.OUTS, item?.devices || 0) + '%';
+    return item;
+  });
+});
+const columns = [
+  { colKey: 'ISIN', title: '空闲' },
+  { colKey: 'BREAK_DOWN', title: '故障' },
+  { colKey: 'ISOUT', title: '占用' },
+  { colKey: 'occupyRate', title: '占用率' },
+  { colKey: 'TOTAL', title: '小计' },
+];
+const columns2 = [
+  { colKey: 'NAME', title: '服务单元' },
+  { colKey: 'devices', title: '配额' },
+  { colKey: 'OUTS', title: '占用' },
+  { colKey: 'difference', title: '差额' },
+  { colKey: 'rate', title: '占用率' },
+];
+watch(model, () => {
+  if (supplierId.value) {
+    run1({
+      supplierId: supplierId.value,
+      model: model.value,
+    });
+  }
+});
+watch(supplierId, () => {
+  model.value = '';
+  run2({
+    supplierId: supplierId.value,
+  });
+});
+
+const options1 = computed(() => {
+  return createCakePieOption({
+    data: Object.keys(statusMap).map((key) => {
+      return {
+        name: statusMap[key],
+        value: result1?.value?.[key],
+      };
+    }),
+    radius: [0, 65],
+  });
+});
+</script>
+
+<style lang="less" scoped>
+.device-analysis {
+  .page-main {
+    height: calc(100vh - 57px);
+    padding: 15px;
+    overflow: auto;
+    .scroll-content {
+      height: 100%;
+      min-height: 600px;
+      min-width: 1000px;
+    }
+
+    .card {
+      padding: 4px 10px 10px 10px;
+      background-color: #fff;
+      border: 1px solid #e5e5e5;
+      border-radius: 4px;
+      .detail {
+        color: #8c8c8c;
+        font-size: 12px;
+        margin-bottom: 10px;
+      }
+      .title {
+        height: 36px;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .label {
+          color: #262626;
+          font-size: 14px;
+          // font-weight: bold;
+        }
+      }
+      .chart-wrap {
+        height: 180px;
+        width: 300px;
+      }
+    }
+  }
+}
+</style>

+ 1 - 1
src/views/report/project-analysis/index.vue

@@ -48,7 +48,7 @@
   </div>
 </template>
 
-<script setup name="DispatchAnalysis">
+<script setup name="ProjectAnalysis">
 import { ref, computed, onMounted, watch } from 'vue';
 import { useRequest } from 'vue-request';
 import { createCakePieOption, createPercentBarOption } from '@/utils/chart';

+ 1 - 1
src/views/report/service-analysis/index.vue

@@ -161,7 +161,7 @@
   </div>
 </template>
 
-<script setup name="DispatchAnalysis">
+<script setup name="ServiceAnalysis">
 import { ref, computed, watch } from 'vue';
 import {
   createWaterBallOption,

+ 1 - 1
src/views/report/sop-analysis/index.vue

@@ -236,7 +236,7 @@
   </div>
 </template>
 
-<script setup name="DispatchAnalysis">
+<script setup name="SopAnalysis">
 import { ref, computed, watch, onMounted } from 'vue';
 import TableLoop from '@/components/common/table-loop/index.vue';
 import { useRequest } from 'vue-request';