Files
admin-govern/src/components/cockpit/listOfMainMonitoringPoints/index.vue

630 lines
23 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="device-control">
<!--指标拟合图 -->
<div v-show="fullscreen">
<PointTree :height="flag ? 128 : 72" @node-click="nodeClick" @pointTypeChange="pointTypeChange"></PointTree>
</div>
<div>
<TableHeader datePicker @selectChange="selectChange" v-if="fullscreen" ref="TableHeaderRef"
:timeKeyList="prop.timeKey">
<template v-slot:select>
<!-- <el-form-item label="监测点">
<el-select filterable v-model="tableStore.table.params.lineId" placeholder="请选择监测点" clearable>
<el-option v-for="item in lineList" :key="item.lineId" :label="item.name"
:value="item.lineId" />
</el-select>
</el-form-item> -->
<el-form-item label="用户功率">
<el-select filterable v-model="tableStore.table.params.power" placeholder="请选择用户功率" clearable>
<el-option v-for="item in powerList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="统计类型">
<el-select style="min-width: 120px !important" placeholder="请选择"
v-model="tableStore.table.params.valueType" filterable>
<el-option value="max" label="最大值"></el-option>
<el-option value="min" label="最小值"></el-option>
<el-option value="avg" label="平均值"></el-option>
<el-option value="cp95" label="cp95"></el-option>
</el-select>
</el-form-item>
<el-form-item label="电能质量指标">
<el-select filterable v-model="tableStore.table.params.indicator" placeholder="请选择电能质量指标"
clearable>
<el-option v-for="item in indicatorList" :key="item.id" :label="item.name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item>
<div v-if="shouldShowHarmonicCount()"
style="display: flex; color: var(--el-text-color-regular)">
<span style="width: 160px">{{ getHarmonicTypeName() }}谐波次数</span>
<el-select v-model="tableStore.table.params.harmonicCount" placeholder="请选择谐波次数"
style="min-width: 80px !important" filterable>
<el-option v-for="num in harmonicCountOptions" :key="num" :label="num"
:value="num"></el-option>
</el-select>
</div>
</el-form-item>
</template>
</TableHeader>
<div v-loading="tableStore.table.loading">
<my-echart class="tall" v-if="lineShow" :options="echartList" :style="{
width: `calc(${prop.width} - ${fullscreen ? 290 : 0}px)`,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px )`
}" />
<el-empty v-else description="暂无监测点" :style="{
width: `calc(${prop.width} - ${fullscreen ? 290 : 0}px)`,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, provide, reactive, watch, h, computed, nextTick } from 'vue'
import TableStore from '@/utils/tableStore'
import TableHeader from '@/components/table/header/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { useConfig } from '@/stores/config'
import { cslineList, fittingData } from '@/api/harmonic-boot/cockpit/cockpit'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { ElMessage } from 'element-plus'
import { getTime } from '@/utils/formatTime'
import { exportSeriesCSV } from '@/utils/echartMethod'
import PointTree from '@/components/tree/govern/pointTree.vue'
const prop = defineProps({
w: { type: [String, Number] },
h: { type: [String, Number] },
width: { type: [String, Number] },
height: { type: [String, Number] },
timeKey: { type: Array as () => string[] },
timeValue: { type: Object },
interval: { type: Number },
flag: { type: Boolean }
})
const TableHeaderRef = ref()
const config = useConfig()
const lineList: any = ref()
const powerList: any = ref()
const chartsList = ref<any>([])
const lineShow = ref(true)
// 计算是否全屏展示
const fullscreen = computed(() => {
const w = Number(prop.w)
const h = Number(prop.h)
if (!isNaN(w) && !isNaN(h) && w === 12 && h === 6) {
// 执行相应逻辑
return true
} else {
return false
}
})
const exceedingTheLimitList: any = ref([
{
label: '越限',
value: '1'
},
{
label: '不越限',
value: '0'
}
])
const indicatorList = ref()
// const initLineList = async () => {
// cslineList({}).then(res => {
// setTime()
// if (res.data.length == 0) {
// lineShow.value = false
// return (tableStore.table.loading = false)
// }
// lineShow.value = true
// lineList.value = res.data
// tableStore.table.params.lineId = lineList.value[0].lineId
// initCode()
// })
// }
const exportSubjectName = ref('')
const nodeClick = (e: any) => {
if (e == undefined) {
}
if (e.level == 3) {
exportSubjectName.value = e.name || ''
tableStore.table.params.lineId = e.id
tableStore.exportName = { subject: exportSubjectName.value, feature: '主要监测点列表' }
initCode()
}
}
const pointTypeChange = (val: any, obj: any) => {
nodeClick(obj)
}
const echartList = ref()
const headerHeight = ref(57)
const selectChange = (showSelect: any, height: any, datePickerValue?: any) => {
headerHeight.value = height
if (datePickerValue && datePickerValue.timeValue) {
// 更新时间参数
tableStore.table.params.searchBeginTime = datePickerValue.timeValue[0]
tableStore.table.params.searchEndTime = datePickerValue.timeValue[1]
}
}
const formatExceedanceValue = (value: any) => {
if (value == 0) return '不越限'
if (value == 1) return '越限'
return value
}
const COLOR_NOT_EXCEED = '#2ab914'
const COLOR_EXCEED = '#e26257'
const mapExceedanceChartValue = (val: number) => {
if (val == 1) return 1
if (val == 0) return 10
return val
}
const getExceedanceChartText = (val: number | string) => {
if (val === 1 || val === '1') return '越限'
if (val === 10 || val === '10') return '不越限'
return formatExceedanceValue(val)
}
/** 越限阶梯线分段着色:不越限绿色,越限红色 */
const buildColoredExceedanceSeries = (data: any[], seriesName: string) => {
const points = data.filter(item => item[1] != null).map(item => [item[0], mapExceedanceChartValue(item[1])])
if (points.length === 0) return []
const series: any[] = []
let i = 0
while (i < points.length) {
const value = points[i][1]
const color = value === 1 ? COLOR_EXCEED : COLOR_NOT_EXCEED
const segment: any[] = []
if (i > 0 && points[i][1] !== points[i - 1][1]) {
segment.push([points[i][0], points[i - 1][1]])
}
segment.push(points[i])
let j = i + 1
while (j < points.length && points[j][1] === value) {
segment.push(points[j])
j++
}
if (j < points.length) {
segment.push([points[j][0], value])
}
series.push({
name: seriesName,
type: 'line',
step: 'end',
showSymbol: false,
clip: true,
data: segment,
yAxisIndex: 1,
lineStyle: { color, width: 2 }
})
i = j
}
return series
}
const qualityChartData = ref<any[]>([])
const getSeriesForCsvExport = () => {
const indicatorSeriesName = indicatorList.value?.find(
(item: any) => item.id === tableStore.table.params.indicator
)?.name
const powerName = powerList.value?.find((item: any) => item.id === tableStore.table.params.power)?.name
const powerSeries = echartList.value.options.series.find((item: any) => item.type === 'bar')
return [
{
name: powerSeries?.name || powerName,
data: powerSeries?.data || []
},
{
name: indicatorSeriesName,
data: qualityChartData.value.map(point => [point[0], formatExceedanceValue(point[1]), point[2]])
}
]
}
const getChartExportFileName = () => ({
subject: exportSubjectName.value,
feature: '负荷曲线拟合图',
date: tableStore.table.params.searchEndTime || tableStore.table.params.searchBeginTime
})
const setEchart = () => {
// 获取当前选择的功率和指标名称
const powerName = powerList.value?.find((item: any) => item.id === tableStore.table.params.power)?.name || '功率'
const indicatorName =
indicatorList.value?.find((item: any) => item.id === tableStore.table.params.indicator)?.name || '电能质量指标'
const chartTitle = `${indicatorName}${powerName}负荷曲线拟合图`
echartList.value = {
exportFileName: getChartExportFileName(),
title: {
text: chartTitle
},
toolbox: {
featureProps: {
myTool1: {
show: true,
title: '下载csv',
icon: 'path://M642 673.1H301.6c-9.9 0-17.9-8-17.9-17.9s8-17.9 17.9-17.9H642c9.9 0 17.9 8 17.9 17.9s-8 17.9-17.9 17.9zM642 511.8H301.6c-9.9 0-17.9-8-17.9-17.9 0-9.9 8-17.9 17.9-17.9H642c9.9 0 17.9 8 17.9 17.9 0 9.9-8 17.9-17.9 17.9zM480.7 350.6H301.6c-9.9 0-17.9-8-17.9-17.9s8-17.9 17.9-17.9h179.2c9.9 0 17.9 8 17.9 17.9s-8.1 17.9-18 17.9zM874.9 350.6H695.7c-49.4 0-89.6-40.2-89.6-89.6V81.9c0-9.9 8-17.9 17.9-17.9 9.9 0 17.9 8 17.9 17.9V261c0 29.6 24.1 53.7 53.7 53.7h179.2c9.9 0 17.9 8 17.9 17.9s-7.9 18-17.8 18zM794.3 959.7H221c-49.4 0-89.6-40.2-89.6-89.6V153.5c0-49.4 40.2-89.6 89.6-89.6h403.1c4.8 0 9.3 1.9 12.7 5.2L887.6 320c3.4 3.4 5.2 7.9 5.2 12.7v537.5c0 52.7-51.9 89.5-98.5 89.5zM221 99.8c-29.6 0-53.7 24.1-53.7 53.7v716.6c0 29.6 24.1 53.7 53.7 53.7h573.3c29 0 62.7-23.5 62.7-53.7v-530L616.7 99.8H221z',
onclick: () => {
exportSeriesCSV(getSeriesForCsvExport(), echartList.value.exportFileName)
}
}
}
},
tooltip: {
trigger: 'axis',
formatter: function (params: any) {
let result = params[0].axisValueLabel
const powerItem = params.find((item: any) => item.seriesName === powerName)
if (powerItem) {
result += `<br/>${powerItem.marker}${powerItem.seriesName}: ${powerItem.value[1]} ${powerItem.value[2] || ''}`
}
const indicatorItems = params.filter(
(item: any) => item.seriesName === indicatorName && item?.data?.[1] != null
)
if (indicatorItems.length) {
const item = indicatorItems[indicatorItems.length - 1]
result += `<br/>${item.marker}${indicatorName}: ${getExceedanceChartText(item.data[1])}`
}
return result
}
},
xAxis: {
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 100,
filterMode: 'filter'
},
{
start: 0,
height: 13,
bottom: '20px',
end: 100,
filterMode: 'filter'
}
],
yAxis: [
{},
indicatorName
? {
position: 'right',
min: 0,
max: 11,
interval: 1,
splitLine: {
lineStyle: {
color: ['#ccc'],
type: 'dashed',
opacity: 0
}
},
axisLabel: {
formatter: function (value: number) {
if (value === 1) return '越限'
if (value === 10) return '不越限'
return ''
}
}
}
: {}
],
// grid: {
// left: '10px',
// right: '30px',
// },
options: {
// dataZoom: [
// {
// type: 'inside',
// start: 0,
// end: 100,
// filterMode: 'filter'
// },
// {
// type: 'slider',
// start: 0,
// end: 100,
// height: 13,
// bottom: '20px',
// filterMode: 'filter'
// }
// ],
series: [
{
type: 'bar',
name: powerName,
data: [],
clip: true,
itemStyle: {
normal: {
color: function (params: any) {
if (params.value[1] == 0 || params.value[1] == 3.14159) {
return '#ccc'
} else {
return config.layout.elementUiPrimary[0]
}
}
}
},
yAxisIndex: 0
}
]
}
}
try {
// 用户功率数据和电能质量数据
let powerData: any[] = []
let qualityData: any[] = []
chartsList.value.forEach((item: any) => {
// 根据统计项ID判断是功率数据还是电能质量数据
if (item.statisticalIndex === tableStore.table.params.power) {
powerData.push(item)
} else if (item.statisticalIndex === tableStore.table.params.indicator) {
qualityData.push(item)
}
})
// 处理功率数据
const processedPowerData = powerData.map((item: any) => {
return [
item.time,
item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2))
: null,
item.unit
]
})
// 处理电能质量数据
const processedQualityData = qualityData.map((item: any) => {
return [
item.time,
item.statisticalData !== null && item.statisticalData !== undefined
? parseFloat(item.statisticalData.toFixed(2))
: null,
item.unit
]
})
// 检查是否有有效数据
const hasPowerData = processedPowerData.length > 0 && processedPowerData.some(item => item[1] !== null)
const hasQualityData = processedQualityData.length > 0 && processedQualityData.some(item => item[1] !== null)
qualityChartData.value = processedQualityData
echartList.value.options.series = [
echartList.value.options.series[0],
...buildColoredExceedanceSeries(processedQualityData, indicatorName)
]
echartList.value.options.series[0].data = processedPowerData
} catch (error) {
console.error('处理图表数据时出错:', error)
}
}
const initCode = () => {
queryByCode('steady_state_limit_fitting').then(res => {
queryCsDictTree(res.data.id).then(item => {
powerList.value = item.data.filter((item: any) => {
return item.name == '三相总无功功率' || item.name == '三相总有功功率'
})
indicatorList.value = item.data.filter((item: any) => {
return item.name != '三相总无功功率' && item.name != '三相总有功功率'
})
tableStore.table.params.power = powerList.value[0].id
tableStore.table.params.indicator = indicatorList.value[0].id
nextTick(() => {
// setTime()
tableStore.index()
})
})
})
}
const tableStore: any = new TableStore({
url: '/cs-device-boot/csGroup/fittingData',
method: 'POST',
showPage: false,
exportName: '主要监测点列表',
column: [],
beforeSearchFun: () => {
setTime()
// 只有当 lineList 已加载且有数据时才设置默认 lineId
if (!tableStore.table.params.lineId && lineList.value && lineList.value.length > 0) {
tableStore.table.params.lineId = lineList.value[0].lineId
}
// 构建请求参数 lists
let lists: any = []
// 处理用户功率指标
const selectedPower = powerList.value?.find((item: any) => item.id === tableStore.table.params.power)
if (selectedPower) {
lists.push({
statisticalId: tableStore.table.params.power,
valueType: tableStore.table.params.valueType
})
}
// 处理电能质量指标
const selectedIndicator = indicatorList.value?.find(
(item: any) => item.id === tableStore.table.params.indicator
)
if (selectedIndicator) {
let frequencys = ''
if (selectedIndicator.name.includes('谐波含有率')) {
frequencys = tableStore.table.params.harmonicCount
}
lists.push({
statisticalId: tableStore.table.params.indicator,
frequency: frequencys !== null && frequencys !== undefined ? String(frequencys) : ''
})
}
// 将 lists 添加到请求参数中
tableStore.table.params.list = lists
tableStore.table.params.dataLevel = 'Primary'
},
loadCallback: () => {
tableStore.table.height = `calc(${prop.height} - 80px)`
// 数据加载完成后的处理
if (tableStore.table.data) {
chartsList.value = JSON.parse(JSON.stringify(tableStore.table.data))
setEchart()
}
}
})
const tableRef = ref()
provide('tableRef', tableRef)
tableStore.table.params.indicator = '1'
tableStore.table.params.exceedingTheLimit = '1'
tableStore.table.params.valueType = 'avg'
tableStore.table.params.searchValue = ''
provide('tableStore', tableStore)
// 添加谐波次数选项2-50
const harmonicCountOptions = ref(Array.from({ length: 49 }, (_, i) => i + 2))
// 判断是否应该显示谐波次数选择框
const shouldShowHarmonicCount = () => {
if (!tableStore.table.params.indicator || !indicatorList.value) return false
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
return (
currentIndicator &&
(currentIndicator.name.includes('电压谐波含有率') || currentIndicator.name.includes('电流谐波含有率'))
)
}
// 获取谐波类型名称
const getHarmonicTypeName = () => {
const currentIndicator = indicatorList.value.find((item: any) => item.id === tableStore.table.params.indicator)
if (currentIndicator) {
if (currentIndicator.name.includes('电压谐波含有率')) {
return '电压'
} else if (currentIndicator.name.includes('电流谐波含有率')) {
return '电流'
}
}
return ''
}
// 监听指标变化,当指标变化时重置谐波次数
watch(
() => tableStore.table.params.indicator,
newVal => {
if (shouldShowHarmonicCount()) {
// 如果之前没有设置过谐波次数则默认设置为2
if (!tableStore.table.params.harmonicCount) {
tableStore.table.params.harmonicCount = 2
}
} else {
// 如果不是谐波含有率指标,则清除谐波次数设置
tableStore.table.params.harmonicCount = ''
}
}
)
onMounted(async () => {
// await initCode()
})
watch(
() => prop.timeKey,
val => {
tableStore.index()
}
)
const setTime = () => {
const time = getTime(
(TableHeaderRef.value?.datePickerRef.interval || prop.interval) ?? 0,
prop.timeKey,
fullscreen.value
? [tableStore.table.params.searchBeginTime, tableStore.table.params.searchEndTime]
: prop.timeValue
)
if (Array.isArray(time)) {
tableStore.table.params.searchBeginTime = time[0]
tableStore.table.params.searchEndTime = time[1]
TableHeaderRef.value?.setInterval(time[2] - 0)
TableHeaderRef.value?.setTimeInterval([time[0], time[1]])
} else {
console.warn('获取时间失败time 不是一个有效数组')
}
}
watch(
() => prop.timeValue,
(newVal, oldVal) => {
tableStore.index()
},
{
deep: true
}
)
</script>
<style lang="scss" scoped>
// :deep(.el-select) {
// min-width: 80px;
// }
.device-control {
display: flex;
}
:deep(.cn-tree) {
padding: 0 10px 0 0 !important;
}
</style>