yanghao 4 päivää sitten
vanhempi
commit
5a84e3fb4f
2 muutettua tiedostoa jossa 214 lisäystä ja 0 poistoa
  1. 4 0
      src/api/pms/qhse/index.ts
  2. 210 0
      src/views/pms/qhse/index.vue

+ 4 - 0
src/api/pms/qhse/index.ts

@@ -70,6 +70,10 @@ export const IotInstrumentApi = {
   // 导出计量器具台账 Excel
   exportInstrument: async (params) => {
     return await request.download({ url: `/rq/iot-measure-book/export-excel`, params })
+  },
+  //统计
+  getInstrumentStatistics: async (id) => {
+    return await request.get({ url: `/rq/iot-measure-book/stat?deptId=${id}` })
   }
 }
 

+ 210 - 0
src/views/pms/qhse/index.vue

@@ -49,6 +49,20 @@
 
       <!-- 列表 -->
       <ContentWrap class="flex-1 overflow-hidden mt-15px" style="border: none">
+        <div class="stats-cards">
+          <div class="stats-card stats-card--expired">
+            <div class="stats-card__label">已过期</div>
+            <div class="stats-card__value text-[40px]! pt-10">{{ expired }}</div>
+          </div>
+          <div class="stats-card stats-card--warn">
+            <div class="stats-card__label">90天预警</div>
+            <div class="stats-card__value text-[40px]! pt-10">{{ warn }}</div>
+          </div>
+          <div class="stats-chart-card">
+            <div class="stats-card__label">分类统计</div>
+            <div ref="staticChartRef" class="stats-chart"></div>
+          </div>
+        </div>
         <zm-table
           :loading="loading"
           :data="list"
@@ -376,6 +390,7 @@
 </template>
 
 <script setup lang="ts">
+import * as echarts from 'echarts'
 import { IotInstrumentApi, IotMeasureDetectApi } from '@/api/pms/qhse/index'
 import DeptTree from '@/views/system/user/DeptTree2.vue'
 import { handleTree } from '@/utils/tree'
@@ -385,10 +400,13 @@ import { ElMessageBox } from 'element-plus'
 const deptList = ref<Tree[]>([]) // 树形结构
 const deptList2 = ref<Tree[]>([]) // 树形结构
 import { formatDate } from '@/utils/formatTime'
+import { useUserStore } from '@/store/modules/user'
 import { DICT_TYPE, getStrDictOptions, getBoolDictOptions } from '@/utils/dict'
 import { useTableComponents } from '@/components/ZmTable/useTableComponents'
 const { ZmTable, ZmTableColumn } = useTableComponents()
 
+const userStore = useUserStore()
+
 defineOptions({ name: 'IotQHSEMeasure' })
 
 const loading = ref(true) // 列表的加载中
@@ -400,8 +418,14 @@ const isLeftContentCollapsed = ref(false)
 
 const { t } = useI18n()
 
+type StaticItem = {
+  classify: string
+  count: number
+}
+
 const list = ref([]) // 列表的数据
 const total = ref(0) // 列表的总页数
+const staticChartRef = ref<HTMLDivElement>()
 const queryParams = reactive({
   pageNo: 1,
   pageSize: 10,
@@ -543,6 +567,7 @@ const handleExport = async () => {
 const handleDeptNodeClick = async (row) => {
   queryParams.deptId = row.id
   await getList()
+  await getStatic()
 }
 
 /** 搜索按钮操作 */
@@ -774,11 +799,134 @@ const handleDownload = async (url) => {
   }
 }
 
+let staticData = ref<StaticItem[]>([])
+let expired = ref(0)
+let warn = ref(0)
+let staticChart: echarts.ECharts | null = null
+
+function getStaticChartOption(): echarts.EChartsOption {
+  return {
+    tooltip: {
+      trigger: 'axis',
+      axisPointer: {
+        type: 'shadow'
+      }
+    },
+    grid: {
+      left: 16,
+      right: 16,
+      top: 24,
+      bottom: 16,
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      data: staticData.value.map((item) => item.classify),
+      axisTick: {
+        show: false
+      },
+      axisLine: {
+        lineStyle: {
+          color: '#d9e2ef'
+        }
+      },
+      axisLabel: {
+        color: '#6b7280',
+        fontSize: 12,
+        interval: 0
+      }
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: false
+      },
+      axisTick: {
+        show: false
+      },
+      axisLabel: {
+        color: '#94a3b8',
+        fontSize: 12
+      },
+      splitLine: {
+        lineStyle: {
+          color: '#edf2f7',
+          type: 'dashed'
+        }
+      }
+    },
+    series: [
+      {
+        type: 'bar',
+        barWidth: 32,
+        data: staticData.value.map((item) => item.count),
+        itemStyle: {
+          borderRadius: [8, 8, 0, 0],
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: '#79b8ff' },
+            { offset: 1, color: '#2f78f6' }
+          ])
+        },
+        label: {
+          show: true,
+          position: 'top',
+          color: '#2563eb',
+          fontSize: 12,
+          fontWeight: 700
+        }
+      }
+    ]
+  }
+}
+
+function renderStaticChart() {
+  if (!staticChartRef.value) return
+  if (!staticChart) {
+    staticChart = echarts.init(staticChartRef.value)
+  }
+  staticChart.setOption(getStaticChartOption(), true)
+}
+
+function resizeStaticChart() {
+  staticChart?.resize()
+}
+
+function destroyStaticChart() {
+  if (staticChart) {
+    staticChart.dispose()
+    staticChart = null
+  }
+}
+
+async function getStatic() {
+  if (queryParams.deptId) {
+    const res = await IotInstrumentApi.getInstrumentStatistics(queryParams.deptId)
+    staticData.value = res.classify
+    expired.value = res.expired
+    warn.value = res.warn
+  } else {
+    const res = await IotInstrumentApi.getInstrumentStatistics(userStore.user.deptId)
+    staticData.value = res.classify
+    expired.value = res.expired
+    warn.value = res.warn
+  }
+  nextTick(() => {
+    renderStaticChart()
+  })
+}
+
 onMounted(async () => {
   getList()
+  getStatic()
 
   deptList.value = handleTree(await DeptApi.getSimpleDeptList())
   deptList2.value = handleTree(await DeptApi.getSimpleDeptList())
+  window.addEventListener('resize', resizeStaticChart)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('resize', resizeStaticChart)
+  destroyStaticChart()
 })
 </script>
 
@@ -790,6 +938,68 @@ onMounted(async () => {
   height: 700px !important;
 }
 
+.stats-cards {
+  display: grid;
+  grid-template-columns: 180px 180px minmax(0, 1fr);
+  gap: 12px;
+  margin-bottom: 16px;
+  align-items: stretch;
+}
+
+.stats-card {
+  padding: 14px 16px;
+  background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
+  border: 1px solid #e4ecf7;
+  border-radius: 10px;
+  box-shadow: 0 4px 12px rgb(31 91 184 / 8%);
+}
+
+.stats-card__label {
+  font-size: 14px;
+  font-weight: 600;
+  color: #6b7280;
+  line-height: 1.4;
+}
+
+.stats-card__value {
+  margin-top: 8px;
+  font-size: 28px;
+  font-weight: 700;
+  line-height: 1;
+  color: #1f5bb8;
+}
+
+.stats-card--expired {
+  background: linear-gradient(180deg, #fff4f4 0%, #ffe8e8 100%);
+  border-color: #ffcfcf;
+}
+
+.stats-card--expired .stats-card__value {
+  color: #de3b3b;
+}
+
+.stats-card--warn {
+  background: linear-gradient(180deg, #fff8ef 0%, #ffeed9 100%);
+  border-color: #ffd7a1;
+}
+
+.stats-card--warn .stats-card__value {
+  color: #d97706;
+}
+
+.stats-chart-card {
+  padding: 14px 16px 10px;
+  background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
+  border: 1px solid #e4ecf7;
+  border-radius: 10px;
+  box-shadow: 0 4px 12px rgb(31 91 184 / 8%);
+}
+
+.stats-chart {
+  height: 130px;
+  margin-top: 6px;
+}
+
 /* 过期行的红色背景 - 基础状态 */
 :deep(.el-table__body tr.expired-row > td.el-table__cell) {
   background-color: #ffe6e6 !important;