|
@@ -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>
|