瀏覽代碼

feat: 增加告警统计页面,增加告警设备 top10 的绘图和接口对接,调试图形大小自适应,加载时的显示效果

yanglzh 10 月之前
父節點
當前提交
9af6edf539
共有 5 個文件被更改,包括 273 次插入1 次删除
  1. 1 1
      package.json
  2. 8 0
      pnpm-lock.yaml
  3. 3 0
      src/api/alarm/index.ts
  4. 102 0
      src/utils/dataUiOptions.ts
  5. 159 0
      src/views/iot/alarm/dashboard/index.vue

+ 1 - 1
package.json

@@ -52,7 +52,7 @@
     "vform3-builds": "3.0.8",
     "vue": "3.2.37",
     "vue-clipboard3": "1.0.1",
-    "vue-data-ui": "^2.3.98",
+    "vue-data-ui": "^2.4.17",
     "vue-grid-layout": "3.0.0-beta1",
     "vue-i18n": "9.1.10",
     "vue-router": "4.0.13",

+ 8 - 0
pnpm-lock.yaml

@@ -113,6 +113,9 @@ importers:
       vue-clipboard3:
         specifier: 1.0.1
         version: 1.0.1
+      vue-data-ui:
+        specifier: ^2.4.17
+        version: 2.4.17
       vue-grid-layout:
         specifier: 3.0.0-beta1
         version: 3.0.0-beta1(@interactjs/core@1.10.27(@interactjs/utils@1.10.27))(@interactjs/utils@1.10.27)
@@ -2073,6 +2076,9 @@ packages:
   vue-clipboard3@1.0.1:
     resolution: {integrity: sha512-iJ2vrizowfA73W3pcxMAKhYSvfekJrQ3FhbveVe9esS1Vfu+xW3Fgc0UKE8N4Q6DyRtcAoNlef8txmD8tK8dIg==}
 
+  vue-data-ui@2.4.17:
+    resolution: {integrity: sha512-8LWTj1BoMowqNkgolhhtKAKYlMaAdjxCfuDLnVpaNgo3TwRw9X2fp8qvRVVvT52iVjXFMmswNrk8vStNLf7m7A==}
+
   vue-demi@0.14.10:
     resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
     engines: {node: '>=12'}
@@ -4280,6 +4286,8 @@ snapshots:
     dependencies:
       clipboard: 2.0.11
 
+  vue-data-ui@2.4.17: {}
+
   vue-demi@0.14.10(vue@3.2.37):
     dependencies:
       vue: 3.2.37

+ 3 - 0
src/api/alarm/index.ts

@@ -21,4 +21,7 @@ export default {
     detail: (id: number) => get('/alarm/log/detail', { id }),
     handle: (data: object) => post('/alarm/log/handle', data),
   },
+  dashboard: {
+    getDeviceAlarmTop10: () => get('/alarm/log/getDeviceAlarmTop10'),
+  },
 }

+ 102 - 0
src/utils/dataUiOptions.ts

@@ -99,6 +99,108 @@ export function getLineData({ xAxis = [] as any[], datas = [] as number[][], leg
   return { config, dataset }
 }
 
+export function getBarData({ xAxis = [] as any[], datas = [] as number[][], legend = [] as string[], suffix = '', width = 1000, height = 400, responsive = false, zoom = false, modulo = 10, padding = 30, useArea = true }) {
+  const colors = ['#6376DD', '#FBBB04']
+
+  const config = {
+    theme: getTheme(),
+    responsive,
+    "chart": {
+      "fontFamily": "inherit",
+      "paddingTop": 0,
+      height,
+      width,
+      "zoom": {
+        "show": zoom
+      },
+      "padding": {
+        "top": 20,
+        "right": padding,
+        "bottom": 20,
+        "left": padding
+      },
+      "grid": {
+        "position": "middle",
+        "labels": {
+          "show": true,
+          "fontSize": 16,
+          "axis": {
+            "fontSize": 16
+          },
+          "yAxis": {
+            "commonScaleSteps": 10,
+          },
+          "xAxisLabels": {
+            "show": true,
+            "values": xAxis,
+            "fontSize": 16,
+            "showOnlyFirstAndLast": false,
+            "showOnlyAtModulo": true,
+            modulo: xAxis.length < modulo ? xAxis.length : modulo,
+            "yOffset": 2
+          }
+        }
+      },
+      "labels": {
+        "fontSize": 16,
+        "prefix": "",
+        suffix
+      },
+      "legend": {
+        "show": legend.length > 1,
+        "fontSize": 16
+      },
+      "title": {
+        "text": " ",
+        "show": false
+      },
+      "userOptions": {
+        "show": false
+      },
+    },
+    "bar": {
+      "borderRadius": 2,
+      "useGradient": true,
+      "periodGap": 0.5,
+      "border": {
+        "useSerieColor": false,
+        "strokeWidth": 1,
+        "stroke": "#FFFFFFff"
+      },
+      "labels": {
+        "show": true,
+        "offsetY": -5,
+        "rounding": 0,
+        "color": "#1A1A1Aff",
+        "formatter": null
+      },
+      "serieName": {
+        "show": false,
+        "offsetY": -6,
+        "useAbbreviation": true,
+        "abbreviationSize": 3,
+        "useSerieColor": true,
+        "color": "#1A1A1Aff",
+        "bold": false
+      }
+    },
+  }
+
+  const dataset = datas.map((data, i) => {
+    return {
+      "name": legend[i],
+      "series": data,
+      "color": colors[i],
+      "type": "bar",
+      scaleSteps: 10,
+      scaleLabel: "Blue circles"
+    }
+  })
+
+  return { config, dataset }
+}
+
+
 export function getLine2Data({ xAxis = [] as any[], datas = [] as number[][], legend = [] as string[], suffix = '', width = 500, height = 300, color = 'rgb(1, 191, 236)' }) {
 
   const max = 100

+ 159 - 0
src/views/iot/alarm/dashboard/index.vue

@@ -0,0 +1,159 @@
+<template>
+	<div class="page">
+		<div class="flex-row" style="gap: 12px">
+			<el-card shadow="nover">
+				<div class="title">今日告警</div>
+				<VueUiSkeleton class="flex1" :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">告警设备top10</div>
+				<VueUiXy v-if="dataset2?.length" :config="config2" :dataset="dataset2" />
+				<VueUiSkeleton v-else :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">最新告警</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+		</div>
+		<div class="flex-row" style="gap: 12px">
+			<el-card shadow="nover">
+				<div class="title">告警统计</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">告警增长趋势</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">部门告警分析</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+		</div>
+		<div class="flex-row" style="gap: 12px">
+			<el-card shadow="nover">
+				<div class="title">告警状态</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">告警等级</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+			<el-card shadow="nover">
+				<div class="title">告警类型</div>
+				<VueUiSkeleton :config="{ type: 'bar' }" />
+			</el-card>
+		</div>
+	</div>
+</template>
+
+<script lang="ts" setup>
+import { toRefs, reactive, onMounted, ref } from "vue";
+import { VueUiXy, VueUiDonut, VueUiSkeleton } from "vue-data-ui";
+import "vue-data-ui/style.css";
+import { getBarData, getLineData, getPieData } from "/@/utils/dataUiOptions";
+import api from "/@/api/alarm";
+import dayjs from "dayjs";
+import { useThemeChange } from "/@/hooks/useCommon";
+
+//#region 线图
+
+// 获取默认图形配置数据
+const chartData = getLineData({
+	xAxis: [],
+	legend: [" "],
+	datas: [[]],
+	responsive: true,
+});
+
+const config = ref<any>({});
+const config2 = ref<any>({});
+const dataset = ref<any[]>([]);
+const dataset2 = ref<any[]>([]);
+
+//#endregion
+
+//#region 饼图
+
+// 获取默认图形配置数据
+const pieData = getPieData({
+	legend: [" "],
+	datas: [[]],
+});
+
+const pieConfig = ref<any>(pieData.config);
+const pieDataset = ref<any[]>(pieData.dataset);
+
+// 监听暗黑模式变化,将 vue-data-ui 的 config 传进来,就能自动更新主题
+useThemeChange([config, pieConfig]);
+
+getData()
+
+function getData() {
+	// 告警设备top10
+	api.dashboard.getDeviceAlarmTop10().then((res: any) => {
+		const list = res || []
+		const chartData = getBarData({
+			xAxis: list.map((item: any) => item.deviceName),
+			legend: ['告警设备top10'],
+			datas: [list.map((item: any) => item.alarmCount)],
+			width: 300,
+			height: 300,
+			responsive: true
+		})
+		config2.value = chartData.config
+		dataset2.value = chartData.dataset
+	})
+}
+</script>
+
+<style scoped lang="scss">
+.page {
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+	gap: 12px;
+
+	.vue-ui-skeleton {
+		height: 100%;
+
+		& ::v-deep>svg {
+			height: 100%;
+		}
+	}
+
+	.title {
+		font-size: 14px;
+		color: #333;
+		font-weight: 500;
+		line-height: 1;
+	}
+
+	.flex-row {
+		flex: 1;
+
+		.el-card {
+			height: 100%;
+			flex: 1;
+
+			& ::v-deep .el-card__body {
+				padding: 1.5vh 1vw;
+				height: 100%;
+				gap: 10px;
+				display: flex;
+				flex-direction: column;
+				justify-content: space-between;
+
+				.vue-ui-xy,
+				.vue-ui-skeleton {
+					flex: 1;
+					height: 100%;
+				}
+
+				.vue-ui-skeleton {
+					overflow: hidden;
+				}
+			}
+		}
+	}
+}
+</style>