Files
admin-govern/src/views/govern/analyze/steadyState/components/chart.vue
2026-06-12 11:02:46 +08:00

412 lines
16 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="analyze-steadyState" v-loading="loading">
<analyzeTree ref="treeRef" :showCheckbox="true" :default-checked-keys="checkedKeyIds" @check="onCheck"
:height="40" :engineering="true" />
<div class="analyze-steadyState-right">
<div ref="headerRef">
<TableHeader :showSearch="false" ref="tableHeaderRef" @selectChange="selectChange">
<template v-slot:select>
<el-form-item label="时间:">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
<el-form-item label="统计指标:">
<el-select style="width: 200px" v-model.trim="formInline.statisticalId" filterable
@change="frequencyFlag" placeholder="请选择">
<el-option v-for="item in zblist" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="谐波次数:" v-show="frequencyShow">
<el-select v-model.trim="formInline.frequency" filterable placeholder="请选择"
style="width: 100px">
<el-option v-for="item in 49" :key="item + 1" :label="item + 1"
:value="item + 1"></el-option>
</el-select>
</el-form-item>
<el-form-item label="值类型:">
<el-select v-model.trim="formInline.valueType" filterable placeholder="请选择">
<el-option v-for="item in typelist" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
</template>
<template v-slot:operation>
<el-button type="primary" @click="search" icon="el-icon-Search">查询</el-button>
</template>
</TableHeader>
</div>
<el-empty description="暂无数据" v-if="!echartsData" style="flex: 1;margin-top: 10%"></el-empty>
<template v-else>
<div :style="echartHeight">
<MyEchart :options="echartsData" />
</div>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
import { ElMessage } from 'element-plus'
import { mainHeight } from '@/utils/layout'
import analyzeTree from '@/components/tree/govern/analyzeTree.vue'
import { queryByCode, queryCsDictTree } from '@/api/system-boot/dictTree'
import { queryCommonStatisticalByTime } from '@/api/cs-harmonic-boot/stable'
import DatePicker from '@/components/form/datePicker/index.vue'
import MyEchart from '@/components/echarts/MyEchart.vue'
import { yMethod, exportSeriesCSV, completeTimeSeries } from '@/utils/echartMethod'
import TableHeader from '@/components/table/header/index.vue'
const MAX_CHECK = 2
const defaultCheckedKeys = ref<any[]>([])
const checkedKeyIds = computed(() => defaultCheckedKeys.value.map(item => item.id))
const treeRef = ref()
const isSyncing = ref(false)
const tableHeaderRef = ref()
const headerRef = ref()
const echartHeight = ref(mainHeight(80))
const loading = ref(false)
const echartsData = ref<any>(null)
const datePickerRef = ref()
const timeControl = ref(false)
const timeFlag = ref(true)
const frequencyShow = ref(false)
const dataLists = ref<any[]>([])
const zblist = ref<any[]>([])
const formInline = reactive({
statisticalId: '',
valueType: '',
startTime: '',
endTime: '',
frequency: '' as string | number
})
const typelist = [
{ label: '平均值', value: 'avg' },
{ label: '最大值', value: 'max' },
{ label: '最小值', value: 'min' },
{ label: 'CP95值', value: 'cp95' }
]
const lineStyle = [
{ type: 'solid', width: 3 },
{ type: 'dotted', width: 3 },
{ type: 'dashed', width: 3 }
]
const getEngineeringTree = () => treeRef.value?.treRef?.treeRef4
const init = () => {
return queryByCode('portable-harmonic-jx').then(res => {
return queryCsDictTree(res.data?.id).then(res => {
zblist.value = res.data.map((item: any) => ({
value: item.id,
label: item.name,
...item
}))
formInline.statisticalId = zblist.value[0]?.value
formInline.valueType = typelist[0].value
})
})
}
onMounted(() => {
init()
})
const onCheck = (_data: any, { checkedNodes }: { checkedNodes: any[] }) => {
if (isSyncing.value) return
const tree = getEngineeringTree()
if (!tree) return
if (checkedNodes.length <= MAX_CHECK) {
defaultCheckedKeys.value = checkedNodes.map(node => ({ ...node }))
if (checkedNodes.length === 0) {
echartsData.value = null
dataLists.value = []
}
return
}
const allowedIds = new Set(defaultCheckedKeys.value.map(item => item.id))
const extras = checkedNodes.filter(node => !allowedIds.has(node.id))
if (defaultCheckedKeys.value.length >= MAX_CHECK) {
isSyncing.value = true
extras.forEach(node => tree.setChecked(node.id, false, false))
nextTick(() => {
isSyncing.value = false
})
ElMessage.warning(`最多只能选择${MAX_CHECK}`)
return
}
const room = MAX_CHECK - defaultCheckedKeys.value.length
extras.slice(0, room).forEach(node => {
if (!defaultCheckedKeys.value.some(item => item.id === node.id)) {
defaultCheckedKeys.value.push({ ...node })
}
})
const rejected = extras.slice(room)
if (rejected.length) {
isSyncing.value = true
rejected.forEach(node => tree.setChecked(node.id, false, false))
nextTick(() => {
isSyncing.value = false
})
ElMessage.warning(`最多只能选择${MAX_CHECK}`)
}
}
const search = async () => {
if (defaultCheckedKeys.value.length === 0) {
ElMessage.warning('请选择设备')
return
}
if (zblist.value.length === 0) {
await init()
}
if (timeFlag.value) {
datePickerRef.value?.setInterval(5)
timeFlag.value = false
}
loading.value = true
formInline.startTime = datePickerRef.value.timeValue[0]
formInline.endTime = datePickerRef.value.timeValue[1]
if (!frequencyShow.value) {
formInline.frequency = ''
}
try {
const results = await Promise.all(
defaultCheckedKeys.value.map(device =>
queryCommonStatisticalByTime({
...formInline,
devId: device.id
}).then(({ data }: { data: any[] }) => ({
deviceName: device.name,
data
}))
)
)
dataLists.value = results.flatMap(({ deviceName, data }) =>
data.map((item: any) => ({
...item,
anotherName:
defaultCheckedKeys.value.length > 1 ? `${deviceName}_${item.anotherName}` : item.anotherName
}))
)
setEchart()
} catch {
loading.value = false
}
}
const setEchart = () => {
loading.value = true
const data = JSON.parse(JSON.stringify(dataLists.value))
if (data.length) {
const list = processingOfData(data, 'unit')
echartsData.value = {}
const legend: any[] = []
const xAxis: any[] = []
const yAxis: any[] = []
const series: any[] = []
const color: any[] = []
data.forEach((item: any) => {
if (!xAxis.includes(item.time)) {
xAxis.push(item.time)
}
})
const units = Object.keys(list)
for (const unit in list) {
const [min, max] = yMethod(list[unit].map((item: any) => item.statisticalData))
yAxis.push({
name: unit == 'null' ? '' : unit,
type: 'value',
min,
max,
axisLine: {
show: true,
lineStyle: { color: '#333' }
}
})
const anotherList = processingOfData(list[unit], 'anotherName')
for (const k in anotherList) {
const lineName = lineStyle[Object.keys(anotherList).indexOf(k)]
const phaseList = processingOfData(anotherList[k], 'phase')
for (const j in phaseList) {
color.push(j == 'A' ? '#DAA520' : j == 'B' ? '#2E8B57' : j == 'C' ? '#A52a2a' : '#0000CC')
legend.push(
j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j
)
series.push({
name: j == 'T' ? k : j == 'A' ? `A相_${k}` : j == 'B' ? `B相_${k}` : j == 'C' ? `C相_${k}` : j,
symbol: 'none',
smooth: true,
type: 'line',
data: timeControl.value
? completeTimeSeries(
phaseList[j].map((item: any) => [
item.time,
Math.floor(item.statisticalData * 100) / 100,
unit,
lineName.type
])
)
: phaseList[j].map((item: any) => [
item.time,
Math.floor(item.statisticalData * 100) / 100,
unit,
lineName.type
]),
lineStyle: lineName,
yAxisIndex: units.indexOf(unit)
})
}
}
}
echartsData.value = {
title: {
text: zblist.value.filter(item => item.id == formInline.statisticalId)[0]?.name
},
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.55)',
borderWidth: 0,
formatter(params: any) {
const xname = params[0].value[0]
let str = `${xname}<br>`
params.forEach((el: any) => {
let marker = ''
if (el.value[3] == 'dashed') {
for (let i = 0; i < 3; i++) {
marker += `<span style="display:inline-block;border: 2px ${el.color} solid;margin-right:5px;width:10px;height:0px;background-color:#ffffff00;"></span>`
}
} else {
marker = `<span style="display:inline-block;border: 2px ${el.color} ${el.value[3]};margin-right:5px;width:40px;height:0px;background-color:#ffffff00;"></span>`
}
str += `${marker}${el.seriesName.split('(')[0]}${el.value[1] != null ? el.value[1] + ' ' + (el.value[2] == 'null' ? '' : el.value[2]) : '-'
}<br>`
})
return str
}
},
legend: {
itemWidth: 20,
itemHeight: 20,
itemStyle: { opacity: 0 },
type: 'scroll',
},
grid: {
left: '20px',
right: '40px',
bottom: '50px',
top: '80px',
containLabel: true
},
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: () => {
const chartTitle =
zblist.value.filter(item => item.id == formInline.statisticalId)[0]?.name || '稳态数据'
exportSeriesCSV(echartsData.value.options.series, `${chartTitle}.csv`)
}
},
myTool2: {
show: true,
title: timeControl.value ? '关闭缺失数据' : '缺失数据',
icon: 'path://M832 512l-192-192v128H128v128h512v128l192-192zM192 512l192 192v-128h512v-128H384V320L192 512z',
iconStyle: timeControl.value ? { borderColor: '#409EFF' } : {},
onclick: () => {
setTimeControl()
}
}
}
},
color,
xAxis: {
name: '',
type: 'time',
axisLabel: {
formatter: { day: '{MM}-{dd}', month: '{MM}', year: '{yyyy}' }
}
},
yAxis,
options: { series }
}
} else {
echartsData.value = null
}
loading.value = false
}
const setTimeControl = () => {
timeControl.value = !timeControl.value
setEchart()
}
const processingOfData = (data: any, type: string) => {
const groupedData: any = {}
data.forEach((item: any) => {
if (!groupedData[item[type]]) {
groupedData[item[type]] = []
}
groupedData[item[type]].push(item)
})
return groupedData
}
const frequencyFlag = () => {
const name = zblist.value.filter(item => item.id == formInline.statisticalId)[0]?.name
if (name?.includes('含有率') || name?.includes('幅值')) {
frequencyShow.value = true
formInline.frequency = 2
} else {
frequencyShow.value = false
}
tableHeaderRef.value?.computedSearchRow()
}
const selectChange = () => {
setTimeout(() => {
echartHeight.value = mainHeight(23 + headerRef.value.offsetHeight)
}, 100)
}
</script>
<style lang="scss">
.analyze-steadyState {
display: flex;
height: 100%;
min-height: 0;
&-right {
height: 100%;
overflow: hidden;
flex: 1;
padding: 10px 10px 10px 0;
display: flex;
flex-direction: column;
}
}
</style>
<style lang="scss" scoped>
.el-select {
min-width: 100px;
}
</style>