Files
admin-govern/src/views/govern/device/control/index.vue
2026-06-09 19:51:31 +08:00

1517 lines
59 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 || 1 }} :
{{ devData.pt2Ratio || 1 }}
</el-descriptions-item>
<el-descriptions-item label="CT变比" width="160">
{{ devData.ctRatio || 1 }} :
{{ devData.ct2Ratio || 1 }}
</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" :disabled="tableLoading">
<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('_kilowattHour') == -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 ||
dataSet.indexOf('_moduleData') != -1 ||
dataSet.indexOf('_devRunTrend') != -1
">
<DatePicker ref="datePickerRef" :timeKeyList="['3', '4', '5']"></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> -->
<!-- _realtimedata
-->
<el-radio-group v-model.trim="formInline.dataLevel" v-if="
!dataSet.includes('_devRunTrend') &&
!dataSet.includes('_moduleData') &&
TrendList?.lineType == 1
" :disabled="TrendList?.lineType != 1"
@change="dataSet.includes('_realtimedata') ? realtimeChange() : 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" :loading="tableLoading">
{{ dataSet.indexOf('_realtimedata') != -1 ? '刷新' : '查询' }}
</el-button>
<el-button type="primary" v-if="dataSet.indexOf('_moduleData') != -1 && moduleFlag"
:icon="Refresh" @click="refreshTheState">
刷新状态
</el-button>
<el-button type="primary" :disabled="tableLoading" v-if="showButton" :icon="DataLine"
@click="handleTrend">
谐波频谱
</el-button>
<el-button type="primary" v-if="showButton" :icon="TrendCharts" @click="handleHarmonicSpectrum"
:disabled="tableLoading">
实时趋势
</el-button>
<el-button type="primary" v-if="showButton" :icon="Download" @click="downloadTxt"
:disabled="tableLoading">
下载数据
</el-button>
</template>
</TableHeader>
<div class="data_time" :style="{
alignItems: realTimeFlag ? 'flex-end' : 'center'
}" v-if="dataSet.includes('_realtimedata') && sonTab != 2 && !realTimeFlag">
<p class="mb10 mt10" >
<span>数据时间:{{ trendDataTime || '-' }}</span>
</p>
<el-button v-if="!realTimeFlag && dataSet.includes('_realtimedata')" style="float: right !important"
:icon="Back" @click="handleReturn" >
返回
</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('_kilowattHour') == -1 &&
dataSet.indexOf('_realtimedata') == -1 &&
dataSet.indexOf('_event') == -1 &&
tableData.length != 0
">
<div class="mb10 mt10" v-if="dataSet.indexOf('_history') == -1"
style="font-weight: 700; font-size: 13px; text-align: center">
最新数据时标:{{ latestTime || '-' }}
</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>
</div>
<!-- 趋势数据 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_trenddata') != -1">
<Trend ref="trendRef" :TrendList="TrendList"></Trend>
</div>
<!-- 电度数据 -->
<div style="height: calc(100vh - 340px)" v-if="dataSet.indexOf('_kilowattHour') != -1">
<electroplating ref="electroplatingRef" :TrendList="TrendList"></electroplating>
</div>
<!-- 实时数据 -->
<div :style="`height: calc(100vh - (${sonTab == 1 ? '346px' : sonTab == 2 ? '308px' : '393px'}))`"
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" :dataTime="mqttMessage.dataTime"></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">
<Event ref="eventRef" :deviceType="deviceType" @fileRecall="handleAnalysisList"></Event>
</div>
<!-- 测试项记录 -->
<div style="height: calc(100vh - 395px)" v-if="dataSet.indexOf('_items') != -1"
v-loading="tableLoading">
<testItemRecords ref="testItemRecordsRef" @onSubmit="handleClick" />
</div>
<!-- 模块数据 -->
<div style="height: calc(100vh - 395px)" v-if="dataSet.indexOf('_moduleData') != -1"
v-loading="tableLoading">
<moduleData ref="moduleDataRef" @onSubmit="handleClick" />
</div>
<!-- 运行趋势 -->
<div style="height: calc(100vh - 395px)" v-if="dataSet.indexOf('_devRunTrend') != -1"
v-loading="tableLoading">
<operatingTrend ref="operatingTrendRef" @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,
allModelData,
getRawData
} 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 electroplating from './tabs/electroplating.vue' //电度数据-主界面
import realTrend from './tabs/components/realtrend.vue' //实时数据-实时趋势
import operatingTrend from './tabs/operatingTrend.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 moduleData from './moduleData.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 {
Download,
TrendCharts,
DataLine,
DataAnalysis,
Odometer,
Monitor,
Timer,
Back,
Refresh
} from '@element-plus/icons-vue'
import analysisList from './analysisList/index.vue'
import mqtt from 'mqtt'
import { json } from 'node:stream/consumers'
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 moduleFlag = ref(false)
const testItemRecordsRef = ref()
const moduleDataRef = 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()
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 txtContent = ref('')
const operatingTrendRef = ref()
const electroplatingRef = 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 showButton = ref(false)
const latestTime=ref('')
const decodeMqttPayload = (message: any) => {
try {
return JSON.parse(JSON.stringify(JSON.parse(new TextDecoder().decode(message))))
} catch {
return {}
}
}
/** 谐波频谱 MQTT 消息(命名函数,便于 off 避免重复注册) */
const onMqttTrendMessage = (topic: any, message: any) => {
const obj = decodeMqttPayload(message) || {}
if ((obj.hasOwnProperty('data1') || obj.hasOwnProperty('data2')) && obj.dataTime) {
trendDataTime.value = obj.dataTime
realTrendRef.value?.setRealTrendData(obj)
tableLoading.value = false
}
}
//谐波频谱方法
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(() => {
if (!dataSet.value.includes('_realtimedata')) return
getHarmRealData(lineId.value, activeTrendName.value).then((res: any) => {
// console.log(res, '获取谐波频谱数据')
})
}, 30000)
bindMqttMessage(onMqttTrendMessage)
} else {
ElMessage.warning('设备应答失败')
}
})
.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(formInline.dataLevel)
// getRealDataMqttMsg()
await getBasicRealData(lineId.value).then((res: any) => {
if (res.code == 'A0000') {
// ElMessage.success('设备应答成功')
// mqttMessage.value = {}
showButton.value = true
realDataTimer.value = window.setInterval(() => {
if (!dataSet.value.includes('_realtimedata')) return
getBasicRealData(lineId.value).then((res: any) => {
console.log(res, '获取基础实时数据')
})
}, 30000)
}
})
}
//返回
const handleReturn = async () => {
if (realDataTimer.value) {
window.clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
window.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('设备应答成功')
showButton.value = true
// mqttMessage.value = {}
realDataTimer.value = window.setInterval(() => {
if (!dataSet.value.includes('_realtimedata')) return
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.name.includes(searchValue.value)) {
return item
}
})
if (oddAndEvenFlag.value) {
list = list.filter((item: any) => {
let str = item.name.split('次')[0]
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 engineeringName = ref('')
const nodeClick = async (e: anyObj, node?: any) => {
console.log("🚀 ~ nodeClick ~ node:", e)
if (e == undefined) {
return (loading.value = false)
}
if(e.pname?.includes('便携')){
deviceType.value = '1'
}else{
deviceType.value = '2'
}
searchValue.value = ''
deviceId.value = e?.pid
lineId.value = e?.id
TrendList.value = e
if (!e) {
loading.value = false
return
}
//选中设备名称后,点击标签页也能查询数据,要求点击设备名称后,点击标签页默认查询第一个监测点数据
if (e.level == 3) {
engineeringName.value = node?.parent.parent.data.name
await queryDictType({
lineId: e?.id,
conType: e.conType
}).then(res => {
oddAndEvenFlag.value = false
queryList.value = res.data
formInline.targetType = res.data[0].id
})
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 === 'kilowattHour') {
item.id = item.id + '_kilowattHour'
}
//实时数据
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'
}
// 模块数据
if (item.type === 'moduleData') {
item.id = item.id + '_moduleData'
}
// 模块数据
if (item.type === 'devRunTrend') {
item.id = item.id + '_devRunTrend'
}
})
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) => {
nodeClick(obj)
}
const realTimeRef: any = ref()
//趋势数据组件
const trendRef: any = ref()
//暂态事件组件
const eventRef: any = ref()
const mqttRef = ref()
const url: any = window.localStorage.getItem('MQTTURL')
/** 同一 handler 先 off 再 on避免重复 message 监听 */
const bindMqttMessage = (handler: (topic: any, message: any) => void) => {
if (!mqttRef.value) return
mqttRef.value.off('message', handler)
mqttRef.value.on('message', handler)
}
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 = {}
showButton.value = true
realDataTimer.value = window.setInterval(async () => {
if (!dataSet.value.includes('_realtimedata')) return
await getBasicRealData(lineId.value).then((res: any) => {
// console.log(res, '获取基础实时数据')
})
}, 30000)
bindMqttMessage(onMqttRealDataMessage)
//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客户端已断开连接.....')
})
setTimeout(() => {
tableLoading.value = false
}, 6000)
} else {
ElMessage.warning('设备应答失败')
tableLoading.value = false
}
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 0)
})
}
//tab点击事件
const realDataTimer: any = ref()
const mqttMessage = ref<any>({})
const realtimeMessage = ref<any>({})
const realtimeChange = () => {
onMqttRealDataMessage('', realtimeMessage.value)
}
/** 实时数据 / 实时趋势 MQTT 消息(命名函数,便于 off 避免重复注册) */
const onMqttRealDataMessage = (topic: any, message: any) => {
let obj = decodeMqttPayload(message)
if (lineId.value != obj.lineId || adminInfo.userIndex != obj.userId) return
realtimeMessage.value = message
//处理mqtt数据 1转2除 2转1乘
//如果消息返回值是二次值,下拉框是二次值只需要单位换算 除以1000
//如果消息返回值是一次值,下拉框是一次值只需要单位换算 除以1000
if (obj.dataLevel == formInline.dataLevel) {
obj = {
...obj,
// 电压有效值
vRmsA: obj.vRmsA,
vRmsB: obj.vRmsB,
vRmsC: obj.vRmsC,
//基波电压幅值
v1A: obj.v1A,
v1B: obj.v1B,
v1C: obj.v1C,
//有功功率
pA: obj.pA,
pB: obj.pB,
pC: obj.pC,
pTot: obj.pTot,
//无功功率
qA: obj.qA,
qB: obj.qB,
qC: obj.qC,
qTot: obj.qTot,
//视在功率
sA: obj.sA,
sB: obj.sB,
sC: obj.sC,
sTot: obj.sTot
}
}
//如果消息返回值是二次值,下拉框是一次值需要单位换算 除以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
txtContent.value = JSON.stringify(obj)
//更新实时数据主页面值
realTimeFlag.value &&
realTimeRef.value &&
realTimeRef.value.setRealData(mqttMessage.value, formInline.dataLevel)
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)
}
const handleClick = async (tab?: any) => {
tableLoading.value = true
showButton.value = false
if (realDataTimer.value) {
window.clearInterval(realDataTimer.value)
}
if (trendTimer.value) {
window.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
}, 0)
}
//电度数据
if (dataSet.value.includes('_kilowattHour')) {
let obj = {
devId: deviceId.value, //e.id
lineId: lineId.value, //e.pid
type: 4,
list: [
{
lineId: lineId.value,
devId: dataSet.value.replace('_kilowattHour', '')
}
]
// startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
// endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
setTimeout(() => {
electroplatingRef.value && electroplatingRef.value.getTrendRequest(obj)
tableLoading.value = false
}, 0)
}
//查询实时数据
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
engineeringName: engineeringName.value, //e.name
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('_moduleData')) {
setTimeout(async () => {
if (tab.props != undefined) await (datePickerRef.value && datePickerRef.value?.setInterval(5))
let obj = {
// devId: deviceId.value, //e.id
lineId: lineId.value, //e.pid
startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
await setTimeout(() => {
allModelData(obj)
.then((res: any) => {
tableData.value = res.data
formInline.total = res.data.total
tableLoading.value = false
setTimeout(() => {
//targetType
res.data.length > 0 ? (moduleFlag.value = true) : (moduleFlag.value = false)
moduleDataRef.value?.setData(res.data)
}, 500)
setTimeout(() => {
loading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
}, 0)
}, 0)
}
//运行趋势
if (dataSet.value.includes('_devRunTrend')) {
setTimeout(async () => {
if (tab.props != undefined) await (datePickerRef.value && datePickerRef.value?.setInterval(5))
let obj = {
// devId: deviceId.value, //e.id
lineId: [deviceId.value], //e.pid
startTime: datePickerRef.value && datePickerRef.value.timeValue[0],
endTime: datePickerRef.value && datePickerRef.value.timeValue[1]
}
await setTimeout(() => {
getRawData(obj)
.then((res: any) => {
tableLoading.value = false
setTimeout(() => {
operatingTrendRef.value?.setData(res.data)
}, 500)
setTimeout(() => {
loading.value = false
}, 1500)
})
.catch(e => {
setTimeout(() => {
tableLoading.value = false
}, 1500)
})
}, 0)
}, 0)
}
//查询当前指标
if (!dataSet.value.includes('_')) {
formInline.id = dataSet.value
latestTime.value=''
tableData.value=[]
// await deviceRtData(formInline)
await realTimeData(formInline)
.then((res: any) => {
tableData.value = res.data[0].children
latestTime.value=res.data[0].dataTime
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.name.includes(searchValue.value)) {
return item
}
})
if (oddAndEvenFlag.value) {
list = list.filter((item: any) => {
let str = item.name.split('次')[0]
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 => {
loading.value = false
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.off('message', onMqttTrendMessage)
mqttRef.value.off('message', onMqttRealDataMessage)
mqttRef.value.end()
}
}
}
// 刷新状态
const refreshTheState = () => {
moduleDataRef.value.getModule(deviceData.value.ndid)
}
//模版下载
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 = () => {
const targetItem = queryList.value.filter((item: any) => item.id == formInline.targetType)[0];
if (targetItem?.name !== '基本数据' && !targetItem?.name?.includes('间谐波')) {
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 : '/'
}
// 下载txt
const downloadTxt = () => {
const content = txtContent.value // 文件内容
const blob = new Blob([content], { type: 'text/plain' }) // 创建 Blob 对象
const url = URL.createObjectURL(blob) // 生成下载链接
// 创建 <a> 标签并触发下载
const link = document.createElement('a')
link.href = url
link.download = '实时数据.txt' // 文件名
link.click()
// 释放 URL 对象
URL.revokeObjectURL(url)
}
onMounted(() => { })
onBeforeUnmount(() => {
clearInterval(realDataTimer.value)
clearInterval(trendTimer.value)
realDataTimer.value = 0
trendTimer.value = 0
if (mqttRef.value) {
mqttRef.value.off('message', onMqttTrendMessage)
mqttRef.value.off('message', onMqttRealDataMessage)
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: 700;
font-size: 13px;
margin: 0 auto;
}
}
</style>