iot-ui-vue/src/views/rule-engine/DashBoard/index.vue

609 lines
19 KiB
Vue

<template>
<page-container>
<div class="DashBoardBox">
<j-row :gutter="24">
<j-col :span="6">
<TopCard
title="今日告警"
:value="state.today"
:footer="currentMonAlarm"
>
<Charts :options="state.fifteenOptions"></Charts>
</TopCard>
</j-col>
<j-col :span="6">
<TopCard
title="告警配置"
:value="state.config"
:footer="alarmState"
:img="getImage('/device/device-number.png')"
></TopCard>
</j-col>
<j-col :span="12">
<NewAlarm :alarm-list="state.alarmList"></NewAlarm>
</j-col>
</j-row>
<j-row :gutter="24">
<j-col :span="24">
<div class="alarm-card">
<Guide>
<template #title>
<span style="margin-right: 24px">告警统计</span>
<j-select
style="width: 40%"
v-model:value="queryCodition.targetType"
:options="
isNoCommunity ? selectOpt1 : selectOpt2
"
@change="selectChange"
></j-select>
</template>
<template #extra>
<TimeSelect
key="flow-static"
:type="'week'"
:quickBtnList="[
{ label: '最近1小时', value: 'hour' },
{ label: '最近24小时', value: 'day' },
{ label: '近一周', value: 'week' },
]"
@change="initQueryTime"
/>
</template>
</Guide>
<div class="alarmBox">
<div class="alarmStatistics-chart">
<Charts
:options="alarmStatisticsOption"
></Charts>
</div>
<div class="alarmRank">
<h4>告警排名</h4>
<ul
v-if="state.ranking.length"
class="rankingList"
>
<li
v-for="(item, i) in state.ranking"
:key="item.targetId"
>
<img
:src="
getImage(
`/rule-engine/dashboard/ranking/${
i + 1
}.png`,
)
"
alt=""
/>
<span
class="rankingItemTitle"
:title="item.targetName"
>{{ item.targetName }}</span
>
<span class="rankingItemValue">{{
item.count
}}</span>
</li>
</ul>
<div v-else class="empty-body">
<j-empty
:image="Empty.PRESENTED_IMAGE_SIMPLE"
></j-empty>
</div>
</div>
</div>
</div>
</j-col>
</j-row>
</div>
</page-container>
</template>
<script lang="ts" setup>
import { Empty } from 'jetlinks-ui-components';
import { getImage } from '@/utils/comm';
import Charts from './components/Charts.vue';
import TopCard from './components/TopCard.vue';
import NewAlarm from './components/NewAlarm.vue';
import TimeSelect from './components/TimeSelect.vue';
import Guide from './components/Guide.vue';
import encodeQuery from '@/utils/encodeQuery';
import type { SelectTypes } from 'ant-design-vue/es/select';
import type { Footer } from '@/views/rule-engine/DashBoard/typings';
import { isNoCommunity } from '@/utils/utils';
import {
dashboard,
getAlarm,
getAlarmConfigCount,
getAlarmLevel,
} from '@/api/rule-engine/dashboard';
import dayjs from 'dayjs';
let currentMonAlarm = ref<Footer[]>([
{
title: '当月告警',
value: 0,
status: 'success',
},
]);
let alarmState = ref<Footer[]>([
{
title: '正常',
value: 0,
status: 'success',
},
{
title: '禁用',
value: 0,
status: 'error',
},
]);
const selectOpt1 = ref<Object[]>([
{ label: '设备', value: 'device' },
{ label: '产品', value: 'product' },
{ label: '组织', value: 'org' },
{ label: '其它', value: 'other' },
]);
const selectOpt2 = ref<SelectTypes['options']>([
{ label: '设备', value: 'device' },
{ label: '产品', value: 'product' },
{ label: '其它', value: 'other' },
]);
let queryCodition = reactive({
startTime: 0,
endTime: 0,
targetType: 'device',
});
let alarmStatisticsOption = ref<any>({});
type DashboardItem = {
group: string;
data: Record<string, any>;
};
let state = reactive<{
today: number;
thisMonth: number;
config: number;
enabledConfig: number;
disabledConfig: number;
alarmList: any[];
ranking: { targetId: string; targetName: string; count: number }[];
fifteenOptions: any;
}>({
today: 0,
thisMonth: 0,
config: 0,
enabledConfig: 0,
disabledConfig: 0,
alarmList: [],
ranking: [],
fifteenOptions: {},
});
// 今日告警
const today = {
dashboard: 'alarm',
object: 'record',
measurement: 'trend',
dimension: 'agg',
group: 'today',
params: {
time: '1d',
// targetType: 'device',
format: 'HH:mm:ss',
from: dayjs(new Date(new Date().setHours(0, 0, 0, 0))).format(
'YYYY-MM-DD HH:mm:ss',
),
to: 'now',
// limit: 24,
},
};
// 当月告警
const thisMonth = {
dashboard: 'alarm',
object: 'record',
measurement: 'trend',
dimension: 'agg',
group: 'thisMonth',
params: {
time: '1M',
// targetType: 'device',
format: 'yyyy-MM',
limit: 1,
from: 'now-1M',
},
};
const fifteen = {
dashboard: 'alarm',
object: 'record',
measurement: 'trend',
dimension: 'agg',
group: '15day',
params: {
time: '1d',
format: 'yyyy-MM-dd',
// targetType: 'product',
from: 'now-15d',
to: 'now',
limit: 15,
},
};
const getDashBoard = () => {
dashboard([today, thisMonth, fifteen]).then((res) => {
if (res.status == 200) {
const _data = res.result as DashboardItem[];
state.today = _data.find(
(item) => item.group === 'today',
)?.data.value;
state.thisMonth = _data.find(
(item) => item.group === 'thisMonth',
)?.data.value;
currentMonAlarm.value[0].value = state.thisMonth;
const fifteenData = _data
.filter((item) => item.group === '15day')
.map((item) => item.data)
.sort((a, b) => b.timestamp - a.timestamp);
state.fifteenOptions = {
xAxis: {
type: 'category',
data: fifteenData.map((item) => item.timeString),
show: false,
},
yAxis: {
type: 'value',
show: false,
},
grid: {
top: '2%',
left: 40,
bottom: 0,
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow',
},
},
series: [
{
name: '告警数',
data: fifteenData.map((item) => item.value),
type: 'line',
color: '#FF595E',
smooth: true,
symbolSize: 0,
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#FF595E', // 100% 处的颜色
},
{
offset: 1,
color: '#FFFFFF', // 0% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
],
};
}
});
};
getDashBoard();
const getAlarmConfig = async () => {
const countRes = await getAlarmConfigCount({});
const enabeldRes = await getAlarmConfigCount({
terms: [
{
column: 'state',
value: 'enabled',
},
],
});
const disableRes = await getAlarmConfigCount({
terms: [
{
column: 'state',
value: 'disabled',
},
],
});
if (countRes.status == 200) {
state.config = countRes.result;
}
if (enabeldRes.status == 200) {
state.enabledConfig = enabeldRes.result;
alarmState.value[0].value = state.enabledConfig;
}
if (disableRes.status == 200) {
state.disabledConfig = disableRes.result;
alarmState.value[1].value = state.disabledConfig;
}
};
getAlarmConfig();
const getCurrentAlarm = async () => {
const alarmLevel: any = await getAlarmLevel();
const sorts = { alarmTime: 'desc' };
const currentAlarm: any = await getAlarm(encodeQuery({ sorts }));
if (currentAlarm.status === 200) {
if (alarmLevel.status === 200) {
const levels = alarmLevel.result.levels;
state.alarmList = currentAlarm.result?.data
.filter((i: any) => i?.state?.value === 'warning')
.map((item: { level: any }) => ({
...item,
levelName: levels.find((l: any) => l.level === item.level)
?.title,
}));
} else {
state.alarmList = currentAlarm.result?.data.filter(
(item: any) => item?.state?.value === 'warning',
);
}
}
};
getCurrentAlarm();
//初始化查询条件
const initQueryTime = (data: any) => {
queryCodition.startTime = data.start;
queryCodition.endTime = data.end;
selectChange();
};
const selectChange = () => {
let time = '1m';
let format = 'M月dd日 HH:mm';
let limit = 12;
const dt = queryCodition.endTime - queryCodition.startTime;
const hour = 60 * 60 * 1000;
const day = hour * 24;
const month = day * 30;
const year = 365 * day;
if (dt <= (hour + 10)) {
limit = 60
format = 'HH:mm';
} else if (dt > hour && dt <= day) {
time = '1h'
limit = 24;
} else if (dt > day && dt < year) {
limit = Math.abs(Math.ceil(dt / day)) + 1;
time = '1d';
format = 'M月dd日 HH:mm:ss';
} else if (dt >= year) {
limit = Math.abs(Math.floor(dt / month));
time = '1M';
format = 'yyyy年-M月';
}
// 告警趋势
const chartData = {
dashboard: 'alarm',
object: 'record',
measurement: 'trend',
dimension: 'agg',
group: 'alarmTrend',
params: {
targetType: queryCodition.targetType, // product、device、org、other
format: format,
time: time,
// from: 'now-1y', // now-1d、now-1w、now-1M、now-1y
// to: 'now',
limit: limit, // 12
// time: params.time.type === 'today' ? '1h' : '1d',
from: dayjs(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
to: dayjs(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
// limit: 30,
},
};
// 告警排名
const order = {
dashboard: 'alarm',
object: 'record',
measurement: 'rank',
dimension: 'agg',
group: 'alarmRank',
params: {
// time: '1h',
time: time,
targetType: queryCodition.targetType,
from: dayjs(queryCodition.startTime).format('YYYY-MM-DD HH:mm:ss'),
to: dayjs(queryCodition.endTime).format('YYYY-MM-DD HH:mm:ss'),
limit: 9,
},
};
let tip = '其它';
if (queryCodition.targetType === 'device') {
tip = '设备';
} else if (queryCodition.targetType === 'product') {
tip = '产品';
} else if (queryCodition.targetType === 'org') {
tip = '组织';
}
// 网络请求
dashboard([chartData, order]).then((res) => {
if (res.status == 200) {
const xData: string[] = [];
const sData: number[] = [];
res.result
.filter((item: any) => item.group === 'alarmTrend')
.forEach((item: any) => {
if(time === '1d'){
item.data.timeString = item.data.timeString.split(' ')[0]
}
xData.push(item.data.timeString);
sData.push(item.data.value);
});
const data:any = JSON.parse(JSON.stringify(sData))
if (data && data.length > 0 ) {
const maxY = data.sort((a,b)=>{
return b-a
})[0]
alarmStatisticsOption.value = {
xAxis: {
type: 'category',
boundaryGap: false,
data: xData.reverse(),
},
yAxis: {
type: 'value',
},
tooltip: {
trigger: 'axis',
// axisPointer: {
// type: 'shadow',
// },
},
grid: {
top: '2%',
bottom: '5%',
left: maxY < 1000 ? 50 : maxY.toString().length * 10,
right: '48px',
},
series: [
{
name: tip,
data: sData.reverse(),
type: 'line',
smooth: true,
color: '#ADC6FF',
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: '#ADC6FF', // 100% 处的颜色
},
{
offset: 1,
color: '#FFFFFF', // 0% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
],
};
}else{
console.log('data is empty ')
}
state.ranking = res.result
?.filter(
(item: any) =>
item.group === 'alarmRank' &&
item.data?.value?.count !== 0,
)
.map((d: { data: { value: any } }) => d.data?.value)
.sort(
(a: { count: number }, b: { count: number }) =>
b.count - a.count,
);
}
});
};
</script>
<style scoped lang="less">
.alarm-card {
width: 100%;
background-color: white;
padding: 24px;
margin-top: 24px;
}
.alarmBox {
width: 100%;
display: flex;
.alarmStatistics-chart {
width: 70%;
height: 500px;
}
.alarmRank {
position: relative;
width: 30%;
padding-left: 48px;
}
}
.rankingList {
margin: 25px 0 0;
padding: 0;
list-style: none;
li {
display: flex;
align-items: center;
margin-top: 16px;
zoom: 1;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
span {
//color: red;
font-size: 14px;
line-height: 22px;
}
.rankingItemNumber {
display: inline-block;
width: 20px;
height: 20px;
margin-top: 1.5px;
margin-right: 16px;
font-weight: 600;
font-size: 12px;
line-height: 20px;
text-align: center;
background-color: #edf0f3;
border-radius: 20px;
&.active {
color: #fff;
background-color: #314659;
}
}
.rankingItemTitle {
flex: 1;
margin-right: 8px;
padding-left: 8px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.empty-body {
height: 490px;
display: flex;
flex-direction: column;
align-content: center;
justify-content: center;
width: 100%;
// height: 100%;
}
</style>