Files
admin-govern/src/views/govern/device/planData/index.vue
2024-10-17 15:42:07 +08:00

855 lines
33 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-manage" :style="{ height: pageHeight.height }">
<!-- @node-change="nodeClick" -->
<schemeTree @node-change="nodeClick" @node-click="nodeClick" @init="nodeClick" ref="schemeTreeRef"></schemeTree>
<div class="device-manage-right" v-if="deviceData">
<el-descriptions title="方案信息" :column="2" border>
<template #extra>
<el-button type="primary" icon="el-icon-Plus" @click="handleOpen(0)">新增方案</el-button>
<el-button type="primary" icon="el-icon-Download" @click="handleExport">数据导出</el-button>
</template>
<el-descriptions-item label="方案名称" width="60">
{{ deviceData.itemName }}
</el-descriptions-item>
<el-descriptions-item label="方案描述" width="60">
{{ deviceData.describe ? deviceData.describe : '/' }}
</el-descriptions-item>
</el-descriptions>
<el-collapse v-model="activeColName" @change="handleChange">
<el-collapse-item title="测试项信息" name="0">
<div class="monitor_info" v-if="deviceData.records && deviceData.records.length != 0">
<!-- <div class="history_title">
<p>测试项信息</p>
</div> -->
<el-tabs v-model="activeName" type="border-card" @click="handleClickTabs">
<el-tab-pane v-for="(item, index) in deviceData.records" :label="item.itemName"
:name="item.id" :key="index">
<template #label>
<span class="custom-tabs-label">
<el-icon>
<TrendCharts />
</el-icon>
<span>{{ item.itemName }}</span>
</span>
</template>
<el-descriptions size="small" width="180" :column="4" border>
<el-descriptions-item label="测试项名称" width="160">
{{ item.itemName }}
</el-descriptions-item>
<el-descriptions-item label="测量间隔" width="160">
{{ item.statisticalInterval }}分钟
</el-descriptions-item>
<el-descriptions-item label="电压等级" width="160">
{{
voltageLevelList.find(vv => {
return vv.id == item.voltageLevel
})?.name
}}
</el-descriptions-item>
<el-descriptions-item label="接线方式" width="160">
{{
volConTypeList.find(vv => {
return vv.id == item.volConType
})?.name
}}
</el-descriptions-item>
<el-descriptions-item label="最小短路容量" width="160">
{{ item.capacitySscmin }}MVA
</el-descriptions-item>
<el-descriptions-item label="用户协议容量" width="160">
{{ item.capacitySi }}MVA
</el-descriptions-item>
<el-descriptions-item label="基准短路容量" width="160">
{{ item.capacitySscb }}MVA
</el-descriptions-item>
<el-descriptions-item label="供电设备容量" width="160">
{{ item.capacitySt }}MVA
</el-descriptions-item>
<el-descriptions-item label="PT变比" width="160">
{{ item.pt && item.pt1 ? item.pt / item.pt1 : item.pt }}
</el-descriptions-item>
<el-descriptions-item label="CT变比" width="160">
{{ item.ct && item.ct1 ? item.ct / item.ct1 : item.ct }}
</el-descriptions-item>
<el-descriptions-item label="起始时间" width="160">
<span style="width: 140px; overflow: hidden; display: block">
{{ item.startTime }}
</span>
</el-descriptions-item>
<el-descriptions-item label="结束时间" width="160">
<span style="width: 140px; overflow: hidden; display: block">
{{ item.endTime }}
</span>
</el-descriptions-item>
<el-descriptions-item label="监测位置" width="160">
{{ item.location }}
</el-descriptions-item>
<el-descriptions-item label="操作" width="160">
<el-button type="primary" icon="el-icon-Tools" @click="handleOpen(3)">
数据绑定
</el-button>
</el-descriptions-item>
</el-descriptions>
</el-tab-pane>
</el-tabs>
</div>
<div class="monitor_info" v-else>
<el-empty />
</div>
</el-collapse-item>
<!-- <el-collapse-item title="历史趋势" name="1" class="history_collapse"> -->
<!-- </el-collapse-item> -->
</el-collapse>
<h3 class="mt10 mb10">历史趋势</h3>
<div class="history_trend">
<div class="history_header" ref="headerRef">
<!-- <el-form :model="searchForm" class="history_select" id="history_select"> -->
<TableHeader :showSearch="false" ref="tableHeaderRef" :key="searchFormIndex"
@selectChange="selectChange">
<template v-slot:select>
<el-form-item for="-" label="统计指标">
<el-select collapse-tags collapse-tags-tooltip v-model="searchForm.index"
placeholder="请选择统计指标" multiple :multiple-limit="3">
<el-option v-for="item in indexOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item for="-" label="值类型">
<el-select style="width: 160px !important" v-model="searchForm.dataLevel">
<el-option value="Primary" label="一次值"></el-option>
<el-option value="Secondary" label="二次值"></el-option>
</el-select>
</el-form-item>
<div v-for="(item, index) in countData" :key="index">
<el-form-item for="-" :label="item.name + '谐波次数'" label-width="180px"
v-if="item.countOptions.length != 0">
<!-- multiple -->
<el-select v-model="item.count" collapse-tags collapse-tags-tooltip
placeholder="请选择谐波次数" style="width: 100px">
<el-option v-for="vv in item.countOptions" :key="vv" :label="vv"
:value="vv"></el-option>
</el-select>
</el-form-item>
</div>
<el-form-item for="-" label="统计类型" label-width="80px">
<el-select v-model="searchForm.type" placeholder="请选择值类型">
<el-option v-for="item in typeOptions" :key="item.id" :label="item.name"
:value="item.id"></el-option>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button type="primary" icon="el-icon-Search" @click="init(true)">查询</el-button>
</template>
</TableHeader>
<!-- </el-form> -->
<!-- <div class="history_searchBtn">
</div> -->
</div>
<div class="history_title">
<p>{{ chartTitle }}</p>
</div>
<div class="history_chart" v-loading="loading" :style="EcharHeight" :key="EcharHeight.height"
ref="chartRef">
<MyEchart ref="historyChart" v-if="echartsData" :isExport="true" :options="echartsData" />
</div>
</div>
</div>
<el-empty v-else description="请选择设备" class="device-manage-right" />
<popup ref="dialogRef" @onSubmit="refreshTree" />
</div>
</template>
<script lang="ts" setup>
import popup from './components/popup.vue'
import schemeTree from './components/schemeTree.vue'
import { mainHeight } from '@/utils/layout'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ref, onMounted, watch, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { getTestRecordInfo, getHistoryTrend } from '@/api/cs-device-boot/planData'
import { useDictData } from '@/stores/dictData'
import { queryStatistical } from '@/api/system-boot/csstatisticalset'
import { TrendCharts, Plus, Platform } from '@element-plus/icons-vue'
import { yMethod } from '@/utils/echartMethod'
import { color, gradeColor3 } from '@/components/echarts/color'
import TableHeader from '@/components/table/header/index.vue'
import { useConfig } from '@/stores/config'
const dictData = useDictData()
defineOptions({
name: 'govern/device/planData/index'
})
const config = useConfig()
color[0] = config.layout.elementUiPrimary[0]
//电压等级
const voltageLevelList = dictData.getBasicData('Dev_Voltage')
//接线方式
const volConTypeList = dictData.getBasicData('Dev_Connect')
//值类型
const pageHeight = mainHeight(20)
const EcharHeight = ref(mainHeight(510))
const loading = ref(false)
const searchForm: any = ref({})
const typeOptions = [
{
name: '平均值',
id: 'avg'
},
{
name: '最大值',
id: 'max'
},
{
name: '最小值',
id: 'min'
},
{
name: 'CP95值',
id: 'cp95'
}
]
searchForm.value = {
index: [],
dataLevel: 'Primary',
type: typeOptions[0].id,
count: []
}
//统计指标
const indexOptions: any = ref([])
//谐波次数
const countOptions: any = ref([])
// Harmonic_Type
// portable-harmonic
const legendDictList: any = ref([])
queryByCode('portable-harmonic').then(res => {
queryCsDictTree(res.data.id).then(item => {
indexOptions.value = item.data
searchForm.value.index[0] = indexOptions.value[0].id
// searchForm.value.index = indexOptions.value[0].id
})
queryStatistical(res.data.id).then(vv => {
legendDictList.value = vv.data
})
})
const activeName: any = ref()
const activeColName: any = ref('0')
const deviceData: any = ref([])
const schemeTreeRef = ref()
//历史趋势devId
const historyDevId: any = ref('')
const chartTitle: any = ref('')
//点击测试项切换树节点
const handleClickTabs = async () => {
searchForm.value.index = [indexOptions.value[0].id]
historyDevId.value = activeName.value
schemeTreeRef.value.setCheckedNode(activeName.value)
setTimeout(() => {
init(true)
}, 100);
}
const nodeClick = async (e: anyObj) => {
console.log("🚀 ~ nodeClick ~ e:", e)
loading.value = true
deviceData.value = []
historyDevId.value = e.children && e.children.length != 0 ? e.children[0].id : e.id
let id = e.pid ? e.pid : e.id
//查询测试项信息
await getTestRecordInfo(id)
.then(async (res) => {
deviceData.value = res.data
if (res.data.records.length == 1) {
activeName.value = res.data.records[0].id
} else {
res.data.records.map((item: any) => {
//多层
if (item.id == e.id) {
activeName.value = item.id
return
}
})
}
searchForm.value.index = [indexOptions.value[0].id]
schemeTreeRef.value.getPlanData(deviceData.value)
await setTimeout(() => {
init(true)
}, 100);
loading.value = false
})
.catch(e => {
loading.value = false
})
}
const dialogRef = ref()
const dailogForm = ref()
const handleOpen = (val: any) => {
deviceData.value.records.map((item: any) => {
if (item.id == activeName.value) {
dailogForm.value = item
}
})
dialogRef.value.details(dailogForm.value)
// deviceData.value.records[0].id
let ids = ''
//数据绑定
if (val == 3) {
ids = deviceData.value.records.find((item: any) => {
return item.id == activeName.value
})?.id
dialogRef.value.detailsType('table')
} else {
ids = ''
dialogRef.value.detailsType('')
}
dialogRef.value.open(val, ids)
}
const echartsData = ref<any>(null)
//加载echarts图表
//历史趋势数据
const historyDataList: any = ref([])
const refreshTree = () => {
schemeTreeRef.value.getTreeList()
}
const range = (start: any, end: any, step: any) => {
return Array.from({ length: (end - start) / step + 1 }, (_, i) => start + i * step)
}
//二维数组去除相同数据的数组
const init = (flag: boolean) => {
//调用子组件的方法切换的时候tree的节点也变化
let list: any = []
//颜色数组
if (historyDevId.value && legendDictList.value && legendDictList.value.selectedList) {
// 选择指标的时候切换legend内容和data数据
legendDictList.value?.selectedList?.map((item: any) => {
searchForm.value.index.map((vv: any) => {
if (item.dataType == vv) {
list.push(item.eleEpdPqdVOS)
}
})
})
//选择的指标使用方法处理
formatCountOptions(searchForm.value.index)
//查询历史趋势
historyDataList.value = []
let middleTitle = ''
if (
deviceData.value.records &&
deviceData.value.records.length != 0 &&
deviceData.value.records.find((item: any) => {
return item.id == activeName.value
})?.itemName
) {
middleTitle = deviceData.value.records.find((item: any) => {
return item.id == activeName.value
})?.itemName
} else {
middleTitle = ''
}
let indexList = []
indexList = searchForm.value.index
chartTitle.value = deviceData.value.itemName + '_' + middleTitle + '_'
indexList.map((item: any, indexs: any) => {
indexOptions.value.map((vv: any) => {
if (vv.id == item) {
chartTitle.value += indexs == indexList.length - 1 ? vv.name : vv.name + '/'
}
})
})
let lists: any = []
countData.value.map((item: any, index: any) => {
lists[index] = {
statisticalId: item.index,
frequencys: item.count && item.count.length != 0 ? [item.count] : []
}
})
let obj = {
devId: historyDevId.value,
list: lists,
dataLevel: searchForm.value.dataLevel,
valueType: searchForm.value.type
}
if (flag) {
loading.value = true
getHistoryTrend(obj)
.then((res: any) => {
if (res.code === 'A0000') {
historyDataList.value = res.data
let chartsList = JSON.parse(JSON.stringify(res.data))
echartsData.value = {}
//icon图标替换legend图例
// y轴单位数组
let unitList: any = []
let groupedData = chartsList.reduce((acc, item) => {
let key = item.anotherName;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {})
let result = Object.values(groupedData);
if (chartsList.length > 0) {
unitList = result.map((item: any) => {
return item[0].unit
})
}
echartsData.value = {
legend: {
itemWidth: 20,
itemHeight: 10,
itemGap: 15,
type: 'scroll', // 开启滚动分页
// orient: 'vertical', // 垂直排列
top: 5,
bottom: 30,
width: 400,
height: 50
},
tooltip: {
axisPointer: {
type: 'cross',
label: {
color: '#fff',
fontSize: 16
}
},
textStyle: {
color: '#fff',
fontStyle: 'normal',
opacity: 0.35,
fontSize: 14
},
backgroundColor: 'rgba(0,0,0,0.35)',
borderWidth: 0,
formatter(params) {
const xname = params[0].value[0];
let str = `${xname}<br>`;
params.forEach((el, index) => {
str += `${el.marker}${el.seriesName.split('(')[0]}${el.value[1] ? (el.value[1] + ' ' + el.value[2]) : '-'}<br>`;
});
return str;
},
},
color: ['#FFCC00', '#009900', '#CC0000', ...color],
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: [{
}],
options: {
series: [
]
}
}
if (chartsList.length > 0) {
echartsData.value.yAxis = []
unitList.forEach((item: any, index: any) => {
let right = {
position: 'right',
offset: (index - 1) * 80,
}
echartsData.value.yAxis.push({
name: item,
yAxisIndex: index,
splitNumber: 5,
minInterval: 1,
splitLine: {
show: false,
},
...(index > 0 ? right : null)
})
})
result.forEach((item: any, index: any) => {
let yMethodList: any = []
let ABCList = Object.values(item.reduce((acc, item) => {
let key = item.phase;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {}));
ABCList.forEach((kk: any,) => {
let seriesList: any = []
kk.forEach((cc: any) => {
if (cc.statisticalData) {
yMethodList.push(cc.statisticalData?.toFixed(2) - 0)
}
seriesList.push([cc.time, cc.statisticalData?.toFixed(2), cc.unit])
})
echartsData.value.options.series.push({
name: kk[0].phase + '相' + item[0].anotherName,
type: 'line',
smooth: true,
symbol: 'none',
data: seriesList,
yAxisIndex: index
})
})
let [min, max] = yMethod(yMethodList)
echartsData.value.yAxis[index].min = min
echartsData.value.yAxis[index].max = max
})
}
// console.log("🚀 ~ .1111 ~ echartsData.value :", echartsData.value)
loading.value = false
}
})
.catch(error => {
loading.value = false
})
}
}
}
//导出
const historyChart = ref()
const handleExport = async () => {
const planCsv = ref('')
const chartsCsv = ref('')
if (deviceData.value.records && deviceData.value.records.length != 0) {
let csv = '',
obj: any = {}
obj = deviceData.value.records.find((item: any) => {
return item.id == activeName.value
})
if (obj) {
//测试是否与变量名长度有关系
let cell1 = deviceData.value.itemName,
cell2 = deviceData.value.describe,
cell3 = obj?.itemName,
cell4 = obj?.statisticalInterval,
cell5 = voltageLevelList.find(vv => {
return vv.id == obj.voltageLevel
})?.name,
cell6 = volConTypeList.find(vv => {
return vv.id == obj.volConType
})?.name,
cell7 = obj.capacitySscmin,
cell8 = obj.capacitySi,
cell9 = obj.capacitySscb,
cell10 = obj.capacitySt,
cell11 = obj.pt && obj.pt1 ? obj.pt / obj.pt1 + '\b' : '/',
cell12 = obj.ct && obj.ct1 ? obj.ct / obj.ct1 + '\b' : '/',
cell13 = obj.startTime ? obj.startTime : '/',
cell14 = obj.endTime ? obj.endTime : '/',
cell15 = obj.location
csv = `方案测试项信息,
方案名称, ${cell1},
方案描述, ${cell2},
测试项名称, ${cell2},
测量间隔, ${cell4 + '分钟'},
电压等级, ${cell5},
接线方式, ${cell6},
最小短路容量, ${cell7 + 'MVA'},
用户协议容量, ${cell8 + 'MVA'},
基准短路容量, ${cell9 + 'MVA'},
供电设备容量, ${cell10 + 'MVA'},
PT变比, ${cell11},
CT变比, ${cell12},
起始时间, ${cell13},
结束时间, ${cell14},
监测位置, ${cell15}\n,
`
planCsv.value = csv
}
}
if (historyDataList.value.length != 0) {
let xAxis: any = []
let timeList: any = []
historyDataList.value.map((item: any) => {
timeList.push(item.time)
})
xAxis = timeList.sort((a: any, b: any) => {
new Date(a).getTime() - new Date(b).getTime()
})
xAxis = Array.from(new Set(xAxis))
// 使用这个函数转换数据为CSV格式
let csv: any = ''
const list = echartsData.value.options.series
csv = convertToCSV([], [])
chartsCsv.value = csv
// 如果你想提供下载链接
function convertToCSV(data: any, key: any) {
// 添加列头
let title = '统计时间,'
list.map((item: any, index: any) => {
index == list.length - 1 ? (title += `${item.name}\n`) : (title += `${item.name},`)
})
let csv = ''
csv = title
// 遍历数据并添加到CSV字符串中
list[0]?.data.map((vv: any, indexs: any) => {
let strs = '',
count = null
list.map((item: any, index: any) => {
if (index == 0) {
count = index
}
let itemList: any = list[index].data[indexs]
if (itemList && itemList.length != 0) {
index == list.length - 1 ? (strs += itemList[1]) : (strs += itemList[1] + ',')
} else {
index == list.length - 1 ? (strs += '/') : (strs += '/,')
}
})
if (count == 0 && xAxis[indexs]) {
csv += `${xAxis[indexs]},` + strs + '\n'
} else {
strs += '/,'
}
})
return csv
}
}
let csvs = ''
if (chartsCsv.value) {
csvs = planCsv.value + '历史趋势数据,\n\n' + chartsCsv.value
} else {
csvs = planCsv.value
}
// 如果你想提供下载链接
const blob = new Blob([csvs], { type: 'text/csv;charset=utf-8;' })
const link = document.createElement('a')
link.href = URL.createObjectURL(blob)
let obj = deviceData.value.records.find((item: any) => {
return item.id == activeName.value
})
const date = window.XEUtils.toDateString(new Date(), 'yyyyMMdd HHmmss').replace(' ', '_')
link.download = `${deviceData.value.itemName}_${obj?.itemName}_${date}.csv`
link.click()
return
}
const countData: any = ref([])
//根据选择的指标处理谐波次数
const formatCountOptions = (list: any) => {
if (list.length != 0) {
list.map((item: any, index: any) => {
if (!countData.value[index]) {
countData.value[index] = {
index: item,
countOptions: [],
count: [],
name: indexOptions.value.find((vv: any) => {
return vv.id == item
})?.name
}
}
legendDictList.value?.selectedList?.map((vv: any, vvs: any) => {
//查找相等的指标
if (item == vv.dataType) {
vv.eleEpdPqdVOS.map((kk: any, kks: any) => {
if (kk.harmStart && kk.harmEnd) {
range(0, 0, 0)
countData.value[index].countOptions = range(kk.harmStart, kk.harmEnd, 1)
if (!countData.value[index].count || countData.value[index].count.length == 0) {
countData.value[index].count = countData.value[index].countOptions[0]
}
}
})
}
})
})
}
}
const flag = ref(false)
const selectChange = (e: boolean) => {
flag.value = e
if (activeColName.value == '0') {
if (flag.value) {
EcharHeight.value = mainHeight(565)
} else {
EcharHeight.value = mainHeight(510)
}
} else {
if (flag.value) {
EcharHeight.value = mainHeight(365)
} else {
EcharHeight.value = mainHeight(310)
}
}
}
const handleChange = () => {
if (activeColName.value == '0') {
if (flag.value) {
EcharHeight.value = mainHeight(565)
} else {
EcharHeight.value = mainHeight(510)
}
} else {
if (flag.value) {
EcharHeight.value = mainHeight(365)
} else {
EcharHeight.value = mainHeight(310)
}
}
}
const tableHeaderRef = ref<any>()
const searchFormIndex = ref<any>()
watch(
() => searchForm.value.index,
(val: any, oldval: any) => {
if (val) {
let list = val
searchFormIndex.value = val
formatCountOptions(list)
countData.value.map((item: any, key: any) => {
if (
list.findIndex((vv: any) => {
return vv == item.index
}) == -1
) {
countData.value.splice(key, 1)
}
})
init(false)
}
},
{
deep: true,
immediate: true
}
)
onMounted(() => {
setTimeout(() => {
init(true)
}, 1500)
})
</script>
<style lang="scss" scoped>
.device-manage {
display: flex;
height: calc(100vh - 100px);
// overflow-y: auto;
&-right {
// overflow: auto;
flex: 1;
padding: 10px 10px 10px 0;
.el-descriptions__header {
height: 36px;
margin-bottom: 7px;
display: flex;
align-items: center;
}
}
.device-manage-right {
overflow: hidden;
flex: 1 !important;
height: calc(100vh - 135px);
padding: 10px 10px 10px 10px;
border: 2px solid #eeeeee;
display: flex;
flex-direction: column;
.el-descriptions__header {
height: 36px;
margin-bottom: 7px;
display: flex;
align-items: center;
}
}
}
.history_title {
width: 100%;
p {
height: 32px;
line-height: 32px;
font-size: 16px;
font-weight: bold;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
}
}
::v-deep .monitor_info {
width: 100%;
.el-tabs__content {
padding: 0 !important;
min-height: 130px !important;
}
}
.history_collapse {
height: calc(100vh - 300px);
width: 100%;
.history_trend {
width: 100%;
height: calc(100vh - 340px);
display: flex;
flex-direction: column;
.history_header {
width: 100%;
}
.history_chart {
margin: 10px 0;
}
}
}
::v-deep .el-select {
width: 120px !important;
}
::v-deep .el-collapse-item__header {
font-size: 16px !important;
font-weight: 800 !important;
}
</style>