feat: 增加设备运行状态和区域温度

This commit is contained in:
huanghao1412 2024-07-06 16:14:20 +08:00
parent 124240e552
commit 1bd97da028
12 changed files with 589 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,29 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { DeviceRunningStateConfig } from './index'
import cloneDeep from 'lodash/cloneDeep'
// import logo from '@/assets/logo.png'
export const option = {}
export const customData = {
title: '设备运行状态',
config: JSON.stringify([
{"id":84,"label":"动力设备","code":"DL","remark":" 高压柜、低压柜、UPS、蓄电池等关于电的动力设备","icon":" ","father_id":0,"complete_id":"84","complete_name":"动力设备","sort":0,"is_leaf_node":false,"father_type":null,"need_data_num":0},
{"id":87,"label":"环境设备","code":"HJ","remark":"温湿度、空调、漏水、气体等监测或影响环境因素的设备 ","icon":" ","father_id":0,"complete_id":"87","complete_name":"环境设备","sort":0,"is_leaf_node":false,"father_type":null,"need_data_num":0},
{"id":90,"label":"安防设备","code":"AF","remark":" 视频、门禁、红外、消防及其他安防类设备","icon":" ","father_id":0,"complete_id":"90","complete_name":"安防设备","sort":0,"is_leaf_node":false,"father_type":null,"need_data_num":0}
]),
showInterval: true,
}
export default class Config extends PublicConfigClass implements CreateComponentType {
constructor() {
super();
this.attr.w = 450
this.attr.h = 300
this.request.requestInterval = 15
}
public key = DeviceRunningStateConfig.key
public chartConfig = cloneDeep(DeviceRunningStateConfig)
public option = cloneDeep(option)
public customData = cloneDeep(customData)
}

View File

@ -0,0 +1,23 @@
<template>
</template>
<script setup lang="ts">
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import {computed, PropType} from "vue";
import {GlobalThemeJsonType} from "@/settings/chartThemes";
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const config = computed(() => {
return props.optionData
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,17 @@
<template>
<setting-item-box name="标题" :alone="true">
<n-input v-model:value="props.customData.title" size="small" placeholder="请输入"/>
</setting-item-box>
<setting-item-box name="设备类型" :alone="true">
<n-input v-model:value="props.customData.config" size="small" placeholder="请输入"/>
</setting-item-box>
</template>
<script lang="ts" setup>
import { SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
const props = defineProps(['customData', 'request'])
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,16 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/CustomComponents/index.d'
export const DeviceRunningStateConfig: ConfigType = {
key: 'DeviceRunningState',
chartKey: 'VDeviceRunningState',
conKey: 'VCDeviceRunningState',
// VCD开头
conDataKey: 'VCDDeviceRunningState',
title: '设备运行状态',
category: ChatCategoryEnum.CUSTOMCOMPONENTS,
categoryName: ChatCategoryEnumName.CUSTOMCOMPONENTS,
package: PackagesCategoryEnum.CUSTOMCOMPONENTS,
chartFrame: ChartFrameEnum.COMMON,
image: 'DeviceRunningState.png'
}

View File

@ -0,0 +1,246 @@
<template>
<div style="overflow: visible;">
<BorderBox :title="customData?.title">
<div class="box">
<div class="legend">
<div class="rect green"></div>
<div class="label">在线</div>
<div class="rect red"></div>
<div class="label">告警</div>
<div class="rect"></div>
<div class="label">离线</div>
</div>
<div class="bottomBox">
<div @click.stop="openDialog(it)" class="item" v-for="(it, i) in systemDatas" :key="i">
<div class="row1">{{it.label}}</div>
<div class="row2">
<div class="col">{{it.online_num}}</div>
<div class="col">{{it.alarm_num}}</div>
<div class="col">{{it.offline_num}}</div>
</div>
</div>
</div>
</div>
</BorderBox>
</div>
</template>
<script setup lang="ts">
import BorderBox from '../components/BorderBox.vue'
import {computed, PropType, Ref, onMounted, ref, watch, onUnmounted} from "vue";
import { customData as customDataConfig } from './config'
import { CreateComponentType } from '@/packages/index.d'
import { publicInterface } from '@/api/path/business.api'
import {isPreview, postMessageToParent} from '@/utils'
import {selectTimeOptions} from "@/views/chart/ContentConfigurations/components/ChartData/index.d";
import {useOriginStore} from "@/store/modules/originStore/originStore";
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const customData: Ref<typeof customDataConfig> = computed(() => {
return props.chartConfig.customData as typeof customDataConfig
})
const deepGetAllDeviceCodes = (treeData: any):any => {
//
let device_codes = []
if (treeData?.data?.code) {
device_codes.push(treeData.data.code)
}
if (treeData.children) {
for (let i = 0; i < treeData.children.length; i++) {
device_codes = device_codes.concat(deepGetAllDeviceCodes(treeData.children[i]))
}
}
return device_codes
}
let systemDatas:Ref<any> = ref([])
const originStore = useOriginStore()
const systemConfig = originStore.getOriginStore.user.systemConfig
const getData = async() => {
try {
const res:any = await publicInterface('/dcim/dems/device_type', 'tree', {})
if (res.data && res.data.length) {
systemDatas.value = res.data.map((e: any) => {
const device_codes = deepGetAllDeviceCodes(e)
return {
...e,
device_code: e.data.code,
device_codes,
all: 0,
online_num: 0,
alarm_num: 0,
offline_num: 0
}
})
if (systemConfig.dglt_device_status_type_config && systemConfig.dglt_device_status_type_config !== '[]') {
//
systemDatas.value = systemDatas.value.filter((e: any) => {
const dglt_device_status_type_config = JSON.parse(systemConfig.dglt_device_status_type_config)
return dglt_device_status_type_config.find((v: any) => v.code === e.device_code)
})
}
const params = {
levels: [1, 2, 3],
// confirm_statuss: ['not'],
recovery_statuss: ['not'],
space_complete_id: '',
device_codes: systemDatas.value.map((e: any) => e.device_code)
}
publicInterface('/dcim/dems/device', 'count_num_and_alarm_num_by_type', params).then((res: any) => {
if (res.data && res.data.length) {
for (const i in res.data) {
const item = systemDatas.value.find((e: any) => e.device_code === res.data[i].device_code)
if (item) {
item.all = res.data[i].all
item.online_num = res.data[i].all - res.data[i].alarm_num - res.data[i].offline_num
item.alarm_num = res.data[i].alarm_num
item.offline_num = res.data[i].offline_num
}
}
}
})
}
} catch (error) {
console.log(error)
}
}
const openDialog = (item: any) => {
postMessageToParent({
type: 'openDeviceStatusDialog',
data: item
})
}
let timer:unknown
watch(() => [props.chartConfig.request.requestInterval, props.chartConfig.request.requestIntervalUnit].join('&&'), v => {
if(!isPreview()) return
if(props.chartConfig.request.requestInterval) {
if(timer) clearInterval(timer as number)
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || {unit: 0}
const unit = obj.unit
const number = unit * props.chartConfig.request.requestInterval
timer = setInterval(() => {
getData()
}, number)
}
})
onMounted(() => {
getData()
if(!isPreview()) return
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || {unit: 0}
const unit = obj.unit
const number = unit * props.chartConfig.request.requestInterval!
timer = setInterval(() => {
getData()
}, number)
})
onUnmounted(() => {
if(timer) clearInterval(timer as number)
})
</script>
<style lang="scss" scoped>
.box{
width: 100%;
height: 100%;
overflow: visible;
position: relative;
.legend{
position: absolute;
right: 0;
top: -34px;
display: flex;
align-items: center;
.rect{
margin-left: 10px;
margin-right: 5px;
width: 10px;
height: 10px;
background: #989898;
&.green{
background: #4ac95b;
}
&.red{
background: #f33b41;
}
}
.label{
font-size: 12px;
height: 16px;
line-height: 16px;
color: #ccc;
}
}
.bottomBox{
display: flex;
flex-wrap: wrap;
height: calc(100% - 20px);
align-content: space-around;
padding: 10px;
.item{
margin-top: 10px;
width: calc(50% - 10px);
height: calc(50% - 10px);
background: #142b42;
cursor: pointer;
&:nth-child(1), &:nth-child(2) {
margin-top: 0;
}
&:nth-child(odd) {
margin-right: 20px;
}
.row1{
height: 50%;
width: 100%;
font-size: 12px;
line-height: 30px;
color: #fff;
padding-left: 10px;
display: flex;
align-items: center;
}
.row2{
height: 50%;
width: 100%;
display: flex;
.col{
flex: none;
display: flex;
align-items: center;
justify-content: center;
width: 33.3%;
height: 100%;
font-size: 14px;
color: #989898;
border-bottom: 2px solid #989898;
background-image: linear-gradient(to bottom, #98989800, #98989840);
text-align: center;
&:nth-child(1) {
color: #4ac95b;
border-bottom: 2px solid #4ac95b;
background-image: linear-gradient(to bottom, #4ac95b00, #4ac95b40);
}
&:nth-child(2) {
color: #f33b41;
border-bottom: 2px solid #f33b41;
background-image: linear-gradient(to bottom, #f33b4100, #f33b4140);
}
}
}
}
}
}
</style>

View File

@ -0,0 +1,25 @@
import { PublicConfigClass } from '@/packages/public'
import { CreateComponentType } from '@/packages/index.d'
import { TemperatureTop10Config } from './index'
import cloneDeep from 'lodash/cloneDeep'
// import logo from '@/assets/logo.png'
export const option = {}
export const customData = {
title: '区域温度TOP10',
ids: '',
showInterval: true,
}
export default class Config extends PublicConfigClass implements CreateComponentType {
constructor() {
super();
this.attr.w = 450
this.attr.h = 300
this.request.requestInterval = 15
}
public key = TemperatureTop10Config.key
public chartConfig = cloneDeep(TemperatureTop10Config)
public option = cloneDeep(option)
public customData = cloneDeep(customData)
}

View File

@ -0,0 +1,23 @@
<template>
</template>
<script setup lang="ts">
import { CollapseItem, SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
import {computed, PropType} from "vue";
import {GlobalThemeJsonType} from "@/settings/chartThemes";
const props = defineProps({
optionData: {
type: Object as PropType<GlobalThemeJsonType>,
required: true
}
})
const config = computed(() => {
return props.optionData
});
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,17 @@
<template>
<setting-item-box name="标题" :alone="true">
<n-input v-model:value="props.customData.title" size="small" placeholder="请输入"/>
</setting-item-box>
<setting-item-box name="区域温度测点(英文逗号隔开)" :alone="true">
<n-input v-model:value="props.customData.ids" size="small" placeholder="请输入"/>
</setting-item-box>
</template>
<script lang="ts" setup>
import { SettingItemBox, SettingItem } from '@/components/Pages/ChartItemSetting'
const props = defineProps(['customData', 'request'])
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,16 @@
import { ConfigType, PackagesCategoryEnum, ChartFrameEnum } from '@/packages/index.d'
import { ChatCategoryEnum, ChatCategoryEnumName } from '@/packages/components/CustomComponents/index.d'
export const TemperatureTop10Config: ConfigType = {
key: 'TemperatureTop10',
chartKey: 'VTemperatureTop10',
conKey: 'VCTemperatureTop10',
// VCD开头
conDataKey: 'VCDTemperatureTop10',
title: '区域温度TOP10',
category: ChatCategoryEnum.CUSTOMCOMPONENTS,
categoryName: ChatCategoryEnumName.CUSTOMCOMPONENTS,
package: PackagesCategoryEnum.CUSTOMCOMPONENTS,
chartFrame: ChartFrameEnum.COMMON,
image: 'TemperatureTop10.png'
}

View File

@ -0,0 +1,173 @@
<template>
<div style="overflow: visible;">
<BorderBox :title="customData?.title">
<div class="contentBox">
<div class="row">
<div class="col">排序</div>
<div class="col">区域设备</div>
<div class="col">实时温度</div>
</div>
<div class="row" v-for="(it, i) in data" :key="i">
<div class="col col1">{{i + 1}}</div>
<div class="col col1" :title="`${it.space_complete_name}/${it.node_name}`">
{{ getAreaName(it.space_complete_name) }}<span v-if="getAreaName(it.space_complete_name)">/</span>{{ it.node_name }}
</div>
<div class="col col1">
<div class="value">{{it.dems_device_point.node_value}}</div>
<LocationIcon @click.stop="jumpToMachineRoom(it)" class="icon" style="margin-left: 5px;cursor: pointer;width: 16px;height: 16px;color: #4196ff;"/>
</div>
</div>
</div>
</BorderBox>
</div>
</template>
<script setup lang="ts">
import BorderBox from '../components/BorderBox.vue'
import {computed, PropType, Ref, onMounted, ref, watch, onUnmounted} from "vue";
import { customData as customDataConfig } from './config'
import { CreateComponentType } from '@/packages/index.d'
import { publicInterface } from '@/api/path/business.api'
import {isPreview, postMessageToParent} from '@/utils'
import {selectTimeOptions} from "@/views/chart/ContentConfigurations/components/ChartData/index.d";
import {useOriginStore} from "@/store/modules/originStore/originStore";
import {icon} from "@/plugins";
const { LocationIcon } = icon.carbon
const props = defineProps({
chartConfig: {
type: Object as PropType<CreateComponentType>,
required: true
}
})
const customData: Ref<typeof customDataConfig> = computed(() => {
return props.chartConfig.customData as typeof customDataConfig
})
let data:Ref<any> = ref([])
const getAreaName = (name: any) => {
name = name.split('/')
if (name.length) {
name = name.pop()
}
return name
}
const jumpToMachineRoom = (row: any) => {
if (row.space && row.space.space_type !== 'device') {
publicInterface('/dcim/space_page', 'get', { space_id: row.space_id, order: 'sort,id asc' }).then((res: any) => {
if (res.data.length) {
postMessageToParent({
type: 'changeRouterV1',
url: `/dynamicRing/schematicDiagram/${res.data[0].id}`
})
} else {
window['$message'].warning('所选节点没有配置页面')
}
})
} else {
window['$message'].warning('所选节点没有配置页面')
}
}
const getData = () => {
const params = {
signal_ids: customData.value.ids.split(',')
}
publicInterface('/dcim/dems/device_point', 'temp_list_dashboard', params).then((res: any) => {
if (res.data && res.data.length) {
data.value = res.data.slice(0, 10)
console.log(data.value, 777)
}
})
}
let timer:unknown
watch(() => [props.chartConfig.request.requestInterval, props.chartConfig.request.requestIntervalUnit].join('&&'), v => {
if(!isPreview()) return
if(props.chartConfig.request.requestInterval) {
if(timer) clearInterval(timer as number)
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || {unit: 0}
const unit = obj.unit
const number = unit * props.chartConfig.request.requestInterval
timer = setInterval(() => {
getData()
}, number)
}
})
onMounted(() => {
getData()
if(!isPreview()) return
const obj = selectTimeOptions.find(_ => _.value === props.chartConfig.request.requestIntervalUnit) || {unit: 0}
const unit = obj.unit
const number = unit * props.chartConfig.request.requestInterval!
timer = setInterval(() => {
getData()
}, number)
})
onUnmounted(() => {
if(timer) clearInterval(timer as number)
})
</script>
<style lang="scss" scoped>
.box{
width: 100%;
height: 100%;
overflow: visible;
position: relative;
.contentBox{
width: 100%;
height: 100%;
padding: 0 5px;
overflow: auto;
.row{
display: flex;
background: rgba(65, 150, 255, 0.08);
color: #999;
height: 20px;
line-height: 20px;
font-size: 12px;
padding-left: 10px;
margin-bottom: 5px;
&:nth-last-child(1) {
margin-bottom: 0;
}
.col{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&:nth-child(1) {
flex: none;
width: 40px;
}
&:nth-child(2) {
flex: 1;
min-width: 100px;
}
&:nth-child(3) {
flex: none;
width: 80px;
}
}
.col1{
color: #fff;
display: flex;
align-items: center;
&:nth-child(3) {
color: rgb(0, 255, 255);
.value{
width: 49px;
}
}
}
}
}
}
</style>

View File

@ -14,6 +14,8 @@ import { AirConditioningTableConfig } from './AirConditioningTable'
import { SiteStatisticsConfig } from './SiteStatistics'
import { PowerCapacityConfig } from './PowerCapacity'
import { ElectricityConsumptionConfig } from './ElectricityConsumption'
import { DeviceRunningStateConfig } from './DeviceRunningState'
import { TemperatureTop10Config } from './TemperatureTop10'
export default [
// Theme1Config,
@ -32,4 +34,6 @@ export default [
SiteStatisticsConfig,
PowerCapacityConfig,
ElectricityConsumptionConfig,
DeviceRunningStateConfig,
TemperatureTop10Config,
]