Files
admin-govern/src/views/govern/device/control/index.vue
2024-12-30 10:07:26 +08:00

1382 lines
56 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="default-main device-control" :style="{ height: pageHeight.height }" v-loading="loading"
style="position: relative">
<!-- @init="nodeClick" -->
<PointTree @node-click="nodeClick" @pointTypeChange="pointTypeChange"></PointTree>
<div class="device-control-right" v-if="deviceData">
<el-descriptions title="监测点信息" class="mb10" width="180" :column="3" border>
<template #extra>
<!-- <el-button v-if="deviceType == '1'" type="primary" @click="handleDownLoadTemplate">
模版下载
</el-button> -->
<!-- <el-button v-if="deviceType == '1'" type="primary" icon="el-icon-Connection" @click="handleImport">
离线补召
</el-button>
<el-button v-if="deviceType == '1'" type="primary" icon="el-icon-Monitor" @click="handleaddDevice">
在线补召
</el-button> -->
<el-button v-if="deviceType == '1'" type="primary" icon="el-icon-Tickets"
@click="handleAnalysisList">
补召
</el-button>
</template>
<el-descriptions-item label="名称">
{{ devData.name || '/' }}
</el-descriptions-item>
<el-descriptions-item label="测量间隔" width="160">
{{ devData.lineInterval }}分钟
</el-descriptions-item>
<el-descriptions-item label="电压等级" width="160">
{{ devData.volGrade }}kV
</el-descriptions-item>
<el-descriptions-item label="接线方式" width="160">
{{ devData.conType == 0 ? '星型接线' : devData.conType == 1 ? '角型接线' : devData.conType == 2 ? 'V型接线' :
'/' }}
</el-descriptions-item>
<!-- <el-descriptions-item label="安装位置" width="160">
{{ devData.position || '/' }}
</el-descriptions-item> -->
<el-descriptions-item label="PT变比" width="160">
{{ devData.ptRatio || '/' }}
</el-descriptions-item>
<el-descriptions-item label="CT变比" width="160">
{{ devData.ctRatio || '/' }}
</el-descriptions-item>
<!-- <el-descriptions-item label="名称">
{{ devData.name ? devData.name : '/' }}
</el-descriptions-item>
<el-descriptions-item label="类型">
{{ echoName(devData.devType, devTypeOptions) }}
</el-descriptions-item>
<el-descriptions-item label="接入方式">
{{ devData.devAccessMethod ? devData.devAccessMethod : '/' }}
</el-descriptions-item>
<el-descriptions-item label="网络设备ID">
{{ devData.ndid ? devData.ndid : '/' }}
</el-descriptions-item>
<el-descriptions-item label="型号">
{{ echoName(devData.devModel, devModelOptions) }}
</el-descriptions-item>
<el-descriptions-item label="接入日期">
{{ devData.time ? devData.time : '/' }}
</el-descriptions-item> -->
</el-descriptions>
<el-tabs v-model.trim="dataSet" type="border-card" class="device-control-box-card" @tab-click="handleClick">
<el-tab-pane lazy :label="item.name" :name="item.id" v-for="(item, index) in deviceData.dataSetList"
:key="index">
<template #label>
<span class="custom-tabs-label">
<el-icon>
<TrendCharts v-if="item.name == 'APF模块数据'" />
<DataLine v-if="item.name == '历史APF模块数据'" />
<DataAnalysis v-if="item.name.includes('趋势数据')" />
<Timer v-if="item.name.includes('实时数据')" />
<Monitor v-if="item.name.includes('暂态事件')" />
<Odometer v-if="
item.name != 'APF模块数据' &&
item.name != '历史APF模块数据' &&
!item.name.includes('历史监测') &&
!item.name.includes('趋势数据') &&
!item.name.includes('实时数据') &&
!item.name.includes('暂态事件')
" />
<Histogram v-if="
item.name != 'APF模块数据' &&
item.name != '历史APF模块数据' &&
item.name.includes('历史监测') &&
!item.name.includes('趋势数据') &&
!item.name.includes('实时数据') &&
!item.name.includes('暂态事件')
" />
</el-icon>
<span>{{ item.name }}</span>
</span>
</template>
</el-tab-pane>
<TableHeader :showSearch="false" v-if="
(dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1) ||
realTimeFlag
">
<template #select>
<el-form-item label="日期"
v-show="dataSet.includes('_items') || dataSet.indexOf('_history') != -1">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="数据类型" v-if="!dataSet.includes('_')">
<el-select v-model.trim="formInline.targetType" @change="handleTargetTypeChange">
<el-option v-for="item in queryList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item v-if="!dataSet.includes('_items')">
<!-- <el-select style="min-width: 120px !important" v-model.trim="formInline.dataLevel"
:disabled="dataLevel == 'Primary' && deviceType == '0'">
<el-option value="Primary" label="一次值"></el-option>
<el-option value="Secondary" label="二次值"></el-option>
</el-select> -->
<el-radio-group v-model.trim="formInline.dataLevel" :disabled="TrendList?.lineType != 1"
@change="handleClick">
<el-radio-button label="一次值" value="Primary" />
<el-radio-button label="二次值" value="Secondary" />
</el-radio-group>
</el-form-item>
<el-form-item label="谐波次数" v-show="oddAndEvenFlag && !dataSet.includes('_')">
<el-select v-model.trim="oddAndEven" style="min-width: 120px !important">
<el-option v-for="item in oddAndEvenList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="数据来源" v-if="dataSet.includes('_items')">
<el-select v-model.trim="formInline.dataSource" placeholder="请选择数据来源" clearable>
<el-option v-for="item in dataSourceList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="关键字" v-if="!dataSet.includes('_')">
<el-input maxlength="32" show-word-limit v-model.trim="searchValue" autocomplete="off"
clearable @input="handleSearch" placeholder="请输入关键词"
style="width: 180px !important"></el-input>
</el-form-item>
</template>
<template #operation>
<el-button type="primary"
:icon="dataSet.indexOf('_realtimedata') != -1 ? 'el-icon-Refresh' : 'el-icon-Search'"
@click="handleClick">{{ dataSet.indexOf('_realtimedata') != -1 ? '刷新' : '查询' }}</el-button>
<el-button type="primary" :disabled="tableLoading" v-if="realTimeFlag" :icon="DataLine"
@click="handleTrend">
谐波频谱
</el-button>
<el-button type="primary" v-if="realTimeFlag" :icon="TrendCharts"
@click="handleHarmonicSpectrum" :disabled="tableLoading">
实时趋势
</el-button>
</template>
</TableHeader>
<div class="data_time" :style="{
height: realTimeFlag ? '30px' : '40px',
alignItems: realTimeFlag ? 'flex-end' : 'center'
}" v-if="dataSet.includes('_realtimedata') && mqttMessage && mqttMessage.dataTime && sonTab != 2">
<p class="mb5">
<span v-if="trendDataTime && !realTimeFlag">数据时间:{{ trendDataTime }}</span>
<span v-if="realTimeFlag">数据时间:{{ mqttMessage.dataTime }}</span>
</p>
<el-button v-if="!realTimeFlag && dataSet.includes('_realtimedata')" style="float: right !important"
:icon="Back" @click="handleReturn" :loading="tableLoading">
返回
</el-button>
</div>
<!-- style="height: calc(100vh - 300px)" -->
<!-- <div
style="height: 100%;"
v-if="
dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1 &&
tableData.length == 0
"
></div> -->
<!-- v-loading="tableLoading" -->
<div :style="{ height: tableHeight }" v-loading="tableLoading" v-if="!dataSet.includes('_')">
<div style="overflow: auto" :style="{ height: tableHeight }" v-if="
dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1 &&
tableData.length != 0
">
<div class="mb5 mt5" v-if="dataSet.indexOf('_history') == -1"
style="font-weight: 800;font-size: 16px;text-align: center">
统计时间:{{ tableData[0]?.time || '' }}
</div>
<nearRealTimeData ref="nearRealTimeDataRef" v-if="dataSet.indexOf('_history') == -1"
:style="{ height: tableHeightBox, overflow: 'auto' }" />
<!-- 循环渲染的card 最新数据/历史数据显示 -->
<div class="content"
v-if="tableData.length != 0 && !tableLoading && dataSet.indexOf('_history') != -1"
:style="{ height: tableHeightBox }">
<el-card class="box-card" :class="dataSet.indexOf('_history') == -1 ? 'box-card-new' : ''"
v-for="(item, index) in tableData" :key="index">
<template #header>
<div class="clearfix">
<span style="flex: 1">{{ item.name }}</span>
<Icon name="el-icon-TrendCharts" class="ml10" @click="getDeviceDataTrend(item)"
style="font-size: 26px; cursor: pointer; color: #fff"></Icon>
</div>
</template>
<div class="box-card-content" v-if="dataSet.indexOf('_history') == -1">
<div class="box-card-div">
<div v-for="(child, childIndex) in item.children" :key="childIndex">
{{ child.anotherName }}:
{{ child.dataValue === 3.1415926 ? '暂无数据' : child.dataValue }}
</div>
</div>
</div>
<div v-else-if="item.children.length" class="box-card-div">
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="danger" style="width: 40px; text-align: center"
class="mr10">
MAX
</el-tag>
{{
item.children[0].maxValue === 3.1415956 ||
typeof item.children[0].maxValue != 'number'
? '暂无数据'
: item.children[0].maxValue
}}
</div>
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="success" style="width: 40px; text-align: center"
class="mr10">
AVG
</el-tag>
{{
item.children[0].avgValue === 3.1415956 ||
typeof item.children[0].avgValue != 'number'
? '暂无数据'
: item.children[0].avgValue
}}
</div>
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="warning" style="width: 40px; text-align: center"
class="mr10">
MIN
</el-tag>
{{
item.children[0].minValue === 3.1415956 ||
typeof item.children[0].minValue != 'number'
? '暂无数据'
: item.children[0].minValue
}}
</div>
</div>
</el-card>
<el-empty description="暂无数据" v-if="tableData.length === 0"></el-empty>
</div>
<!-- <div class="content" v-if="tableData.length != 0 && !tableLoading"
:style="{ height: tableHeightBox }">
<el-card class="box-card" :class="dataSet.indexOf('_history') == -1 ? 'box-card-new' : ''"
v-for="(item, index) in tableData" :key="index">
<template #header>
<div class="clearfix">
<span style="flex: 1">{{ item.name }}</span>
<Icon name="el-icon-TrendCharts" class="ml10" @click="getDeviceDataTrend(item)"
style="font-size: 26px; cursor: pointer; color: #fff"></Icon>
</div>
</template>
<div class="box-card-content" v-if="dataSet.indexOf('_history') == -1">
<div class="box-card-div">
<div v-for="(child, childIndex) in item.children" :key="childIndex">
{{ child.anotherName }}:
{{ child.dataValue === 3.1415926 ? '暂无数据' : child.dataValue }}
</div>
</div>
</div>
<div v-else-if="item.children.length" class="box-card-div">
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="danger" style="width: 40px; text-align: center"
class="mr10">
MAX
</el-tag>
{{
item.children[0].maxValue === 3.1415956 ||
typeof item.children[0].maxValue != 'number'
? '暂无数据'
: item.children[0].maxValue
}}
</div>
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="success" style="width: 40px; text-align: center"
class="mr10">
AVG
</el-tag>
{{
item.children[0].avgValue === 3.1415956 ||
typeof item.children[0].avgValue != 'number'
? '暂无数据'
: item.children[0].avgValue
}}
</div>
<div style="display: flex; align-items: center">
<el-tag effect="dark" type="warning" style="width: 40px; text-align: center"
class="mr10">
MIN
</el-tag>
{{
item.children[0].minValue === 3.1415956 ||
typeof item.children[0].minValue != 'number'
? '暂无数据'
: item.children[0].minValue
}}
</div>
</div>
</el-card>
<el-empty description="暂无数据" v-if="tableData.length === 0"></el-empty>
</div> -->
</div>
</div>
<!-- <el-pagination v-if="
tableData.length &&
dataSet.indexOf('_trenddata') == -1 &&
dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1
" background class="mr2 mt10" style="float: right" @size-change="handleSizeChange"
@current-change="pageChange" :current-page="formInline.pageNum" :page-sizes="[20, 30, 40, 50, 100]"
:page-size="formInline.pageSize" layout="total, sizes, prev, pager, next, jumper"
:total="formInline.total"></el-pagination> -->
<!-- 趋势数据 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_trenddata') != -1">
<Trend ref="trendRef" :TrendList="TrendList"></Trend>
</div>
<!-- 实时数据 -->
<div :style="`height: calc(100vh - (${sonTab == 1 ? '378px' : sonTab == 2 ? '340px' : '425px'}))`"
v-if="dataSet.indexOf('_realtimedata') != -1" v-loading="tableLoading">
<!-- <div class="view_top_btn" v-if="realTimeFlag">
<el-button type="primary" :icon="Platform" @click="handleRecordWaves">
实时录波
</el-button>
</div> -->
<div class="view_top_btn"
v-if="dataSet.includes('_realtimedata') && !realTimeFlag && mqttMessage.dataTime && sonTab == 2">
<el-button :icon="Back" @click="handleReturn" :loading="tableLoading">返回</el-button>
</div>
<!-- 实时数据主界面组件 -->
<realTime v-if="realTimeFlag" ref="realTimeRef"></realTime>
<!-- 实时数据-实时录波组件 -->
<recordWoves v-if="!realTimeFlag && sonTab == 0"></recordWoves>
<!-- 实时数据-实时趋势组件 -->
<realTrend v-if="!realTimeFlag && sonTab == 1" ref="realTrendRef"
@changeTrendType="changeTrendType">
</realTrend>
<!-- 实时数据-谐波频谱组件 -->
<harmonicSpectrum v-show="!realTimeFlag && sonTab == 2" ref="harmonicSpectrumRef">
</harmonicSpectrum>
</div>
<!-- 暂态事件 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_event') != -1"
v-loading="tableLoading">
<Event ref="eventRef"></Event>
</div>
<!-- 测试项记录 -->
<div style="height: calc(100vh - 395px)" v-if="dataSet.indexOf('_items') != -1"
v-loading="tableLoading">
<testItemRecords ref="testItemRecordsRef" @onSubmit="handleClick" />
</div>
<div v-if="!tableData" style="height: 42px"></div>
</el-tabs>
</div>
<el-empty v-else description="请添加设备" class="device-control-right" />
<Detail ref="detailRef" :detail="detail" @close="detail = null" :dataLevel="dataLevel" v-if="detail"></Detail>
<!-- 离线数据导入组件 -->
<!-- <offLineDataImport ref="offLineDataImportRef"></offLineDataImport> -->
<!-- 补召日志 -->
<analysisList ref="analysisListRef"></analysisList>
</div>
</template>
<script setup lang="ts">
import Detail from './detail.vue'
import PointTree from '@/components/tree/govern/pointTree.vue'
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryByid, queryCsDictTree } from '@/api/system-boot/dictTree'
import {
getDeviceData,
getBasicRealData,
getHarmRealData,
getOverLimitData,
queryDictType,
getById
} from '@/api/cs-device-boot/EquipmentDelivery'
import { deviceHisData, deviceRtData, realTimeData, getTestData } from '@/api/cs-device-boot/csGroup'
import { ref, reactive, onMounted, onUnmounted, inject, nextTick, onBeforeUnmount } from 'vue'
import { ElMessage } from 'element-plus'
import DatePicker from '@/components/form/datePicker/index.vue'
import Trend from './tabs/trend.vue' //趋势数据
import realTime from './tabs/realtime.vue' //实时数据-主界面
import realTrend from './tabs/components/realtrend.vue' //实时数据-实时趋势
import harmonicSpectrum from './tabs/components/harmonicSpectrum.vue' //实时数据-谐波频谱子页面
import recordWoves from './tabs/components/recordwoves.vue' //实时数据-实时录波子页面
import offLineDataImport from './offLineDataImport/index.vue'
import Event from './tabs/event.vue'
import nearRealTimeData from './nearRealTimeData.vue'
import testItemRecords from './testItemRecords.vue'
import { useDictData } from '@/stores/dictData'
import { useRouter } from 'vue-router'
import TableHeader from '@/components/table/header/index.vue'
import { useAdminInfo } from '@/stores/adminInfo'
import { Histogram, TrendCharts, DataLine, DataAnalysis, Odometer, Monitor, Timer, Back } from '@element-plus/icons-vue'
import analysisList from './analysisList/index.vue'
import mqtt from 'mqtt'
defineOptions({
// name: 'govern/device/control'
})
const adminInfo = useAdminInfo()
const pageHeight = mainHeight(20)
const loading = ref(true)
const tableLoading = ref(false)
const getGroupLoading = ref(false)
const deviceData = ref<any>(null)
const dataSet = ref('')
const testItemRecordsRef = ref()
const nearRealTimeDataRef = ref()
const devTypeOptions = ref([])
const devModelOptions = ref([])
const tableData = ref<any[]>([])
const tableHeight = mainHeight(290).height
const tableHeightBox = mainHeight(330).height
const searchValue = ref('')
const TrendList = ref({})
const oddAndEven = ref('1')
const datePickerRef = ref('1')
const oddAndEvenFlag = ref(false)
const formInline = reactive({
searchValue: '',
pageNum: 1,
pageSize: 20,
total: 0,
startTime: '',
endTime: '',
id: '',
lineId: '',
targetType: '',
dataSource: '',
dataLevel: 'Secondary'
})
const dataSourceList = [{
id: '0',
name: '补召'
}, {
id: '1',
name: '在线监测'
}]
const oddAndEvenList = [
{
value: '3',
label: '全部',
},
{
value: '1',
label: '奇次',
},
{
value: '2',
label: '偶次',
},
]
const detail = ref<any>(null)
//是否显示实时数据默认内容
const realTimeFlag = ref(true)
//实时数据子菜单
const sonTab = ref()
const dictData = useDictData()
//电压等级
const voltageLevelList = dictData.getBasicData('Dev_Voltage_Stand')
//接线方式
const volConTypeList = dictData.getBasicData('Dev_Connect')
//实时录波
// const handleRecordWaves = () => {
// realTimeFlag.value = false
// sonTab.value = 0
// }
//谐波频谱
const realTrendRef = ref()
const changeTrendType = (val: any) => {
trendDataTime.value = ''
activeTrendName.value = val * 1
handleTrend()
}
//判断是否显示数据时间
const activeTrendName: any = ref(0)
const trendTimer: any = ref()
const trendDataTime: any = ref()
//谐波频谱方法
const handleTrend = async () => {
realTimeFlag.value = false
sonTab.value = 1
if (realDataTimer.value) {
window.clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
window.clearInterval(trendTimer.value)
}
tableLoading.value = true
await getHarmRealData(lineId.value, activeTrendName.value)
.then((res: any) => {
if (res.code == 'A0000') {
trendDataTime.value = ''
ElMessage.success('装置应答成功')
//每隔30s调用一下接口通知后台推送mqtt消息
trendTimer.value = window.setInterval(() => {
getHarmRealData(lineId.value, activeTrendName.value).then((res: any) => {
// console.log(res, '获取谐波频谱数据')
})
}, 30000)
mqttRef.value.on('message', (topic: any, message: any) => {
let obj = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message)))) || {}
if ((obj.hasOwnProperty('data1') || obj.hasOwnProperty('data2')) && obj.dataTime) {
trendDataTime.value = obj.dataTime
realTrendRef.value && realTrendRef.value.setRealTrendData(obj)
tableLoading.value = false
// console.log(
// '谐波频谱---mqtt接收到消息',
// JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
// )
}
// else {
// trendDataTime.value = obj.dataTime
// }
})
} else {
ElMessage.success('装置应答失败')
}
})
.catch(e => {
realTrendRef.value && realTrendRef.value.setRealTrendData(false)
tableLoading.value = false
})
await getOverLimitData(lineId.value).then((res: any) => {
if (res.code == 'A0000') {
realTrendRef.value && realTrendRef.value.setOverLimitData(res.data)
}
})
realTrendRef.value &&
realTrendRef.value.open({ devId: deviceId.value, lineId: lineId.value, activeTrendName: activeTrendName.value })
}
//实时趋势
const harmonicSpectrumRef = ref()
//实时趋势
const handleHarmonicSpectrum = async () => {
// if (realDataTimer.value) {
// window.clearInterval(realDataTimer.value)
// }
// if (trendTimer.value) {
// window.clearInterval(trendTimer.value)
// }
realTimeFlag.value = false
sonTab.value = 2
harmonicSpectrumRef.value && harmonicSpectrumRef.value.resetData()
// getRealDataMqttMsg()
await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') {
// ElMessage.success('装置应答成功')
// mqttMessage.value = {}
realDataTimer.value = window.setInterval(() => {
getBasicRealData(lineId.value).then((res: any) => {
// console.log(res, '获取基础实时数据')
})
}, 30000)
}
})
}
//返回
const handleReturn = async () => {
if (realDataTimer.value) {
clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
clearInterval(trendTimer.value)
}
realTimeFlag.value = true
sonTab.value = null
activeTrendName.value = 0
mqttMessage.value = {}
tableLoading.value = true
await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') {
ElMessage.success('装置应答成功')
// mqttMessage.value = {}
realDataTimer.value = window.setInterval(() => {
getBasicRealData(lineId.value).then((res: any) => {
// console.log(res, '获取基础实时数据')
})
}, 30000)
}
})
// handleClick()
}
const handleSearch = () => {
let queryListName = queryList.value.filter((item: any) => item.id == formInline.targetType)
let list = tableData.value.filter((item: any) => {
if (item.otherName.includes(searchValue.value)) {
return item
}
})
if (oddAndEvenFlag.value) {
list = list.filter((item: any) => {
let str = item.otherName.split("次")[0]
queryListName[0].name == '间谐波电压含有率' ? str = str - 0.5 : ''
if (oddAndEven.value == '1') {
// 奇次
if (str % 2 != 0) {
return item
}
} else if (oddAndEven.value == '2') {
// 偶次
if (str % 2 == 0) {
return item
}
} else {
return item
}
})
}
nearRealTimeDataRef.value?.setData(list, queryListName)
}
const getDeviceDataTrend = (e: any) => {
detail.value = {
devId: deviceId.value,
lineId: lineId.value,
dataLevel: dataLevel.value,
...e
}
}
const pageChange = (e: number) => {
formInline.pageNum = e
handleClick()
}
const handleSizeChange = (val: number) => {
formInline.pageNum = 1
formInline.pageSize = val
handleClick()
}
const { push, options, currentRoute } = useRouter()
//设备补召
const handleaddDevice = () => {
push({
path: '/supplementaryRecruitment',
query: {
id: lineId.value,
ndid: deviceData.value.ndid
}
})
}
//树节点点击事件
const deviceId: any = ref('')
const devData: any = ref({})
const lineId: any = ref('')
const dataLevel: any = ref('')
const dataSource = ref([])
const nodeClick = async (e: anyObj) => {
// console.log("🚀 ~ nodeClick ~ e:", e)
if (e == undefined || e.level == 2) {
return loading.value = false
}
searchValue.value = ''
await queryDictType({
lineId: e?.id,
conType: e.conType
}).then(res => {
oddAndEvenFlag.value = false
queryList.value = res.data
formInline.targetType = res.data[0].id
})
deviceId.value = e?.pid
lineId.value = e?.id
TrendList.value = e
if (!e) {
loading.value = false
return
}
//选中设备名称后,点击标签页也能查询数据,要求点击设备名称后,点击标签页默认查询第一个监测点数据
if (e.level == 3 || e.level == 2) {
loading.value = true
formInline.lineId = e.level == 3 ? e.id : e.children[0].id
await getDeviceData(e.level == 3 ? e.pid : e.id, 'history', e.level == 3 ? e.id : e.children[0].id)
.then((res: any) => {
getById({ lineId: e.level == 3 ? e.id : e.children[0].id }).then((res: any) => {
devData.value = res.data
})
deviceData.value = res.data
formInline.dataLevel = res.data.dataLevel
dataLevel.value = res.data.dataLevel
if (dataLevel.value == 'Secondary') {
formInline.dataLevel = 'Primary'
}
if (!res.data.dataSetList) {
dataSet.value = ''
tableData.value = []
loading.value = false
} else {
res.data.dataSetList.forEach((item: any) => {
//历史
if (item.type === 'history') {
item.id = item.id + '_history'
}
//趋势数据
if (item.type === 'trenddata') {
item.id = item.id + '_trenddata'
}
//实时数据
if (item.type === 'realtimedata') {
item.id = item.id + '_realtimedata'
}
//暂态事件
if (item.type === 'event') {
item.id = item.id + '_event'
}
// 测试项日志
if (item.type === 'items') {
item.id = item.id + '_items'
}
})
res.data.dataSetList = res.data.dataSetList.filter((item: any) => item.name != '历史统计数据')
//便携式设备默认二次值
// if (deviceType.value == '1') {
// formInline.dataLevel = 'Primary'
// }
dataSet.value = res.data.dataSetList[0].id
handleClick()
}
loading.value = false
})
.catch(e => {
loading.value = false
})
} else {
loading.value = false
}
}
//治理设备和便携式设备切换判断
const deviceType = ref('0')
const pointTypeChange = (val: any, obj: any) => {
deviceType.value = val
nodeClick(obj)
}
const realTimeRef: any = ref()
//趋势数据组件
const trendRef: any = ref()
//暂态事件组件
const eventRef: any = ref()
const mqttRef = ref()
const url: any = window.localStorage.getItem('MQTTURL')
const connectMqtt = () => {
if (mqttRef.value) {
if (mqttRef.value.connected) {
return
}
}
const options: any = {
protocolId: 'MQTT',
qos: 2,
clean: true,
connectTimeout: 30 * 1000,
clientId: 'mqttjs' + Math.random(),
username: 't_user',
password: 'njcnpqs'
}
mqttRef.value = mqtt.connect(url, options)
}
const getRealDataMqttMsg = async () => {
tableLoading.value = true
if (realDataTimer.value) {
window.clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
window.clearInterval(trendTimer.value)
}
//新的实时数据
//1.调用接口 mqtt推送数据
await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') {
ElMessage.success('装置应答成功')
mqttMessage.value = {}
realDataTimer.value = window.setInterval(async () => {
await getBasicRealData(lineId.value).then((res: any) => {
// console.log(res, '获取基础实时数据')
})
}, 30000)
mqttRef.value.on('message', (topic: any, message: any) => {
// console.log(
// '实时数据&实时趋势---mqtt接收到消息',
// JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
// )
let obj = JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
// console.log("🚀 ~ mqttRef.value.on ~ obj:", obj)
if (lineId.value != obj.lineId && adminInfo.userIndex != obj.userId) return
// console.log("🚀 ~ mqttRef.value.on ~ obj:", obj)
// console.log("🚀 ~ mqttRef.value.on ~ formInline.dataLevel:", obj.dataLevel, formInline.dataLevel)
//处理mqtt数据 1转2除 2转1乘
//如果消息返回值是二次值,下拉框是二次值只需要单位换算 除以1000
//如果消息返回值是一次值,下拉框是一次值只需要单位换算 除以1000
if (obj.dataLevel == formInline.dataLevel) {
obj = {
...obj,
// 电压有效值
vRmsA: (obj.vRmsA) / 1000,
vRmsB: obj.vRmsB / 1000,
vRmsC: obj.vRmsC / 1000,
//基波电压幅值
v1A: obj.v1A / 1000,
v1B: obj.v1B / 1000,
v1C: obj.v1C / 1000,
//有功功率
pA: obj.pA / 1000,
pB: obj.pB / 1000,
pC: obj.pC / 1000,
pTot: obj.pTot / 1000,
//无功功率
qA: obj.qA / 1000,
qB: obj.qB / 1000,
qC: obj.qC / 1000,
qTot: obj.qTot / 1000,
//视在功率
sA: obj.sA / 1000,
sB: obj.sB / 1000,
sC: obj.sC / 1000,
sTot: obj.sTot / 1000
}
}
//如果消息返回值是二次值,下拉框是一次值需要单位换算 除以1000 并且乘以pt ct
if (obj.dataLevel == 'Secondary' && formInline.dataLevel == 'Primary') {
obj = {
...obj,
// 电压有效值
vRmsA: ((obj.vRmsA * obj.pt)) / 1000,
vRmsB: ((obj.vRmsB * obj.pt)) / 1000,
vRmsC: ((obj.vRmsC * obj.pt)) / 1000,
// 电流有效值
iRmsA: (obj.iRmsA * obj.ct),
iRmsB: (obj.iRmsB * obj.ct),
iRmsC: (obj.iRmsC * obj.ct),
//基波电压幅值
v1A: ((obj.v1A * obj.pt)) / 1000,
v1B: ((obj.v1B * obj.pt)) / 1000,
v1C: ((obj.v1C * obj.pt)) / 1000,
//基波电流幅值
i1A: (obj.i1A * obj.ct),
i1B: (obj.i1B * obj.ct),
i1C: (obj.i1C * obj.ct),
//有功功率
pA: ((obj.pA * obj.pt * obj.ct)) / 1000,
pB: ((obj.pB * obj.pt * obj.ct)) / 1000,
pC: ((obj.pC * obj.pt * obj.ct)) / 1000,
pTot: ((obj.pTot * obj.pt * obj.ct)) / 1000,
//无功功率
qA: ((obj.qA * obj.pt * obj.ct)) / 1000,
qB: ((obj.qB * obj.pt * obj.ct)) / 1000,
qC: ((obj.qC * obj.pt * obj.ct)) / 1000,
qTot: ((obj.qTot * obj.pt * obj.ct)) / 1000,
//视在功率
sA: ((obj.sA * obj.pt * obj.ct)) / 1000,
sB: ((obj.sB * obj.pt * obj.ct)) / 1000,
sC: ((obj.sC * obj.pt * obj.ct)) / 1000,
sTot: ((obj.sTot * obj.pt * obj.ct)) / 1000
}
}
//如果消息返回值是一次值,下拉框是二次值需要单位换算 乘以1000 并且除以pt ct
if (obj.dataLevel == 'Primary' && formInline.dataLevel == 'Secondary') {
obj = {
...obj,
// 电压有效值
vRmsA: (obj.vRmsA / obj.pt) * 1000,
vRmsB: (obj.vRmsB / obj.pt) * 1000,
vRmsC: (obj.vRmsC / obj.pt) * 1000,
// 电流有效值
iRmsA: obj.iRmsA / obj.ct,
iRmsB: obj.iRmsB / obj.ct,
iRmsC: obj.iRmsC / obj.ct,
//基波电压幅值
v1A: (obj.v1A / obj.pt) * 1000,
v1B: (obj.v1B / obj.pt) * 1000,
v1C: (obj.v1C / obj.pt) * 1000,
//基波电流幅值
i1A: obj.i1A / obj.ct,
i1B: obj.i1B / obj.ct,
i1C: obj.i1C / obj.ct,
//有功功率
pA: (obj.pA / obj.pt / obj.ct) * 1000,
pB: (obj.pB / obj.pt / obj.ct) * 1000,
pC: (obj.pC / obj.pt / obj.ct) * 1000,
pTot: (obj.pTot / obj.pt / obj.ct) * 1000,
//无功功率
qA: (obj.qA / obj.pt / obj.ct) * 1000,
qB: (obj.qB / obj.pt / obj.ct) * 1000,
qC: (obj.qC / obj.pt / obj.ct) * 1000,
qTot: (obj.qTot / obj.pt / obj.ct) * 1000,
//视在功率
sA: (obj.sA / obj.pt / obj.ct) * 1000,
sB: (obj.sB / obj.pt / obj.ct) * 1000,
sC: (obj.sC / obj.pt / obj.ct) * 1000,
sTot: (obj.sTot / obj.pt / obj.ct) * 1000
}
}
//保留两位小数
for (var i in obj) {
if (typeof obj[i] == 'number' && obj[i] != 0 && Math.abs(obj[i]) % 1 != 0) {
obj[i] = obj[i].toFixed(2)
}
}
if (obj.hasOwnProperty('pA') && obj.hasOwnProperty('pB')) {
mqttMessage.value = obj
//更新实时数据主页面值
realTimeFlag.value && realTimeRef.value && realTimeRef.value.setRealData(mqttMessage.value)
tableLoading.value = false
//更新实时趋势折线图数据
if (sonTab.value == 2) {
!realTimeFlag.value &&
sonTab.value == 2 &&
harmonicSpectrumRef.value &&
harmonicSpectrumRef.value.setHarmonicSpectrumData(mqttMessage.value)
}
}
//更新谐波频谱数据
// !realTimeFlag.value &&
// sonTab.value == 1 &&
// realTrendRef.value &&
// realTrendRef.value.setRealTrendData(obj)
})
//2.建立mqtt通讯
//每隔30s调用一下接口通知后台推送mqtt消息
mqttRef.value.on('error', (error: any) => {
console.log('mqtt连接失败...', error)
mqttRef.value.end()
})
mqttRef.value.on('close', function () {
console.log('mqtt客户端已断开连接.....')
})
} else {
ElMessage.success('装置应答失败')
tableLoading.value = false
}
}).catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 0)
})
}
//tab点击事件
const realDataTimer: any = ref()
const mqttMessage = ref<any>({})
const handleClick = async (tab?: any) => {
tableLoading.value = true
if (realDataTimer.value) {
clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
clearInterval(trendTimer.value)
}
sonTab.value = null
activeTrendName.value = 0
mqttMessage.value = {}
//点击tab时更新dataSet最新值
if (tab && tab.props && tab.props.name && dataSet.value != tab.props.name) {
dataSet.value = tab.props.name
}
//初始化点击tab隐藏实时录波、实时趋势、谐波频谱按钮
realTimeFlag.value = false
//初始化点击tab隐藏子页面
sonTab.value = null
// console.log(123, dataSet.value.includes('_history'));
//查询历史指标
if (dataSet.value.includes('_history')) {
await nextTick(() => {
formInline.startTime = datePickerRef.value && datePickerRef.value.timeValue[0]
formInline.endTime = datePickerRef.value && datePickerRef.value.timeValue[1]
formInline.id = dataSet.value.replace('_history', '')
deviceHisData(formInline)
.then((res: any) => {
tableData.value = res.data.records
formInline.total = res.data.total
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
})
}
//查询趋势数据
if (dataSet.value.includes('_trenddata')) {
let obj = {
devId: deviceId.value, //e.id
lineId: lineId.value, //e.pid
type: 1,
list: [
{
lineId: lineId.value,
devId: dataSet.value.replace('_trenddata', '')
}
]
// startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
// endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
setTimeout(() => {
trendRef.value && trendRef.value.getTrendRequest(obj)
tableLoading.value = false
}, 1500)
}
//查询实时数据
if (dataSet.value.includes('_realtimedata')) {
tableLoading.value = true
//查询实时数据显示实时录波、实时趋势、谐波频谱
realTimeFlag.value = true
connectMqtt()
mqttRef.value.on('connect', (e: any) => {
// ElMessage.success('连接mqtt服务器成功!')
mqttRef.value.subscribe('/Web/RealData/+')
})
getRealDataMqttMsg()
}
//查询暂态事件
if (dataSet.value.includes('_event')) {
let obj = {
devId: deviceId.value, //e.id
lineId: lineId.value, //e.pid
type: 3,
list: [
{
lineId: lineId.value,
devId: dataSet.value.replace('_event', '')
}
]
}
setTimeout(() => {
//暂态事件表格请求参数
eventRef.value && eventRef.value.getTableParams(obj)
tableLoading.value = false
}, 1500)
}
//测试项记录
if (dataSet.value.includes('_items')) {
setTimeout(() => {
formInline.startTime = datePickerRef.value && datePickerRef.value?.timeValue[0]
formInline.endTime = datePickerRef.value && datePickerRef.value?.timeValue[1]
formInline.id = dataSet.value
getTestData(formInline)
.then((res: any) => {
tableData.value = res.data
formInline.total = res.data.total
tableLoading.value = false
setTimeout(() => { //targetType
testItemRecordsRef.value?.setData(res.data,)
}, 500)
setTimeout(() => {
loading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
}, 100)
}
//查询当前指标
if (!dataSet.value.includes('_')) {
formInline.id = dataSet.value
// await deviceRtData(formInline)
await realTimeData(formInline)
.then((res: any) => {
tableData.value = res.data
formInline.total = res.data.total
let queryListName = queryList.value.filter((item: any) => item.id == formInline.targetType)
let list = tableData.value.filter((item: any) => {
if (item.otherName.includes(searchValue.value)) {
return item
}
})
if (oddAndEvenFlag.value) {
list = list.filter((item: any) => {
let str = item.otherName.split("次")[0]
queryListName[0].name == '间谐波电压含有率' ? str = str - 0.5 : ''
if (oddAndEven.value == '1') {
// 奇次
if (str % 2 != 0) {
return item
}
} else if (oddAndEven.value == '2') {
// 偶次
if (str % 2 == 0) {
return item
}
} else {
return item
}
})
}
setTimeout(() => { //targetType
nearRealTimeDataRef.value?.setData(list, queryListName)
tableLoading.value = false
}, 500)
setTimeout(() => {
loading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
}
if (!dataSet.value.includes('_realtimedata')) {
if (realDataTimer.value) {
window.clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
window.clearInterval(trendTimer.value)
}
if (mqttRef.value) {
mqttRef.value.end()
}
}
}
//模版下载
const handleDownLoadTemplate = () => { }
//补召日志
const analysisListRef = ref()
//打开补召日志
const handleAnalysisList = () => {
analysisListRef.value && analysisListRef.value.open({
lineId: lineId.value,
deviceData: deviceData.value,
deviceId: deviceId.value,
})
}
//离线数据导入
const offLineDataImportRef = ref()
const handleImport = () => {
//设备devId&监测点lineId带入组件
offLineDataImportRef.value && offLineDataImportRef.value.open(deviceId.value, lineId.value)
}
queryByCode('Device_Type').then(res => {
queryCsDictTree(res.data.id).then(res => {
devTypeOptions.value = res.data.map((item: any) => {
return {
value: item.id,
label: item.name,
...item
}
})
})
queryByid(res.data.id).then(res => {
devModelOptions.value = res.data.map((item: any) => {
return {
value: item.id,
label: item.name,
...item
}
})
})
})
const handleTargetTypeChange = () => {
if (queryList.value.filter((item: any) => item.id == formInline.targetType)[0].name != '基本数据') {
oddAndEvenFlag.value = true
} else {
oddAndEvenFlag.value = false
}
}
const queryList: any = ref([])
const echoName = (value: any, arr: any[]) => {
return value ? arr.find(item => item.value == value)?.label : '/'
}
onMounted(() => { })
onBeforeUnmount(() => {
clearInterval(realDataTimer.value)
clearInterval(trendTimer.value)
realDataTimer.value = 0
trendTimer.value = 0
if (mqttRef.value) {
mqttRef.value.end()
}
})
</script>
<style lang="scss">
.device-control {
display: flex;
&-left {
// width: 280px;
}
&-right {
overflow: hidden;
flex: 1;
padding: 10px 10px 10px 0;
.el-descriptions__header {
height: 36px;
margin-bottom: 7px;
display: flex;
align-items: center;
}
.content {
box-sizing: border-box;
overflow: auto;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(310px, 1fr));
grid-gap: 10px;
justify-content: center;
.box-card {
display: flex;
flex-direction: column;
justify-content: space-between;
color: var(--el-color-white);
min-height: 80px;
font-size: 13px;
.el-card__header {
padding: 0;
.clearfix {
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
height: 35px;
padding: 0 10px;
background: var(--el-color-primary);
}
}
.el-card__body {
flex: 1;
padding: 10px;
margin-bottom: 0;
background-image: linear-gradient(var(--el-color-primary), var(--el-color-primary-light-3));
.box-card-content {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
}
}
}
}
.box-card-div {
display: grid;
// grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(auto-fit, minmax(95px, 1fr));
}
}
.box-card-new {
min-height: 110px !important;
.el-card__body {
overflow-y: auto;
}
}
.view_top_btn {
width: 100%;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-end;
// padding-left: 20%;
}
.custom-tabs-label {
display: flex;
align-items: center;
justify-content: space-between;
.el-icon {
margin-right: 5px;
}
}
.el-form {
width: 100%;
height: auto;
display: flex;
flex-wrap: wrap;
}
.data_time {
width: 100%;
height: 40px;
text-align: center;
display: flex;
align-items: center;
p {
font-weight: 800;
font-size: 16px;
margin: 0 auto;
}
}
</style>