Files
admin-sjzx/src/views/LN/newEnergy/newEnergyAnalysis/test.vue
2025-12-12 09:26:56 +08:00

876 lines
33 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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" :style="height">
<splitpanes style="height: 100%" class="default-theme" id="navigation-splitpanes">
<pane :size="size">
<PointTree
:default-expand-all="false"
@node-click="handleNodeClick"
@init="handleNodeClick"
></PointTree>
</pane>
<pane :style="height" style="background: #fff">
<TableHeader ref="TableHeaderRef" datePicker>
<template #operation>
<el-button icon="el-icon-Download" type="primary" @click="exportData">导出区间数据</el-button>
</template>
</TableHeader>
<div class="container pd10">
<el-card class="cardBox">
<template #header>
<div class="card-header" style="display: flex; justify-content: space-between">
<span>功率区间</span>
<div>
<span style="font-size: 14px">稳态越限图例: </span>
<span style="color: red">越限 </span>
<span style="color: green">合格</span>
</div>
</div>
</template>
<el-card class="card-top mt5">
<div
v-for="(item, index) in powerList1"
class="pd10"
style="cursor: pointer"
:class="flag == item.label ? 'hoverBox' : ''"
@click="analyseList(item.label, index)"
>
<div style="font-size: 24px; font-weight: 700">
<span :style="{ color: item.crossTheLine > 0 ? 'red' : 'green' }">
{{ item.quantity }}
</span>
<span style="font-size: 14px; font-weight: 500"></span>
</div>
<div>
{{ item.label }}
</div>
</div>
</el-card>
<el-card class="card-top mt15">
<div
v-for="(item, index) in powerList2"
class="pd10"
style="cursor: pointer"
:class="flag == item.label ? 'hoverBox' : ''"
@click="analyseList(item.label, index + 5)"
>
<div style="font-size: 24px; font-weight: 700">
<span :style="{ color: item.crossTheLine > 0 ? 'red' : 'green' }">
{{ item.quantity }}
</span>
<span style="font-size: 14px; font-weight: 500"></span>
</div>
<div>
{{ item.label }}
</div>
</div>
</el-card>
</el-card>
<el-card class="echart">
<template #header>
<div class="card-header">
<span>有功率区间占比</span>
</div>
</template>
<my-echart :options="options" style="height: 212px" />
</el-card>
</div>
<div class="container" style="padding: 0px 10px 10px">
<el-card class="box">
<template #header>
<div class="card-header">
<span>功率区间稳态越限详情</span>
</div>
</template>
<!-- <h3>功率区间稳态越限详情</h3> -->
<div :style="`height:calc(${heightB.height} - 25px)`" style="overflow-y: auto">
<vxe-table
v-bind="defaultAttribute"
ref="vxeRef"
height="auto"
:data="tableData"
v-loading="loading"
>
<vxe-column field="time" title="时间" width="180px">
<template #default="{ row }">
<el-link
type="primary"
:underline="false"
class="percentage"
@click="timeClick(row)"
>
{{ row.time }}
</el-link>
</template>
</vxe-column>
<vxe-column field="voltageOffset" title="电压偏差(%)" min-width="110px">
<template #default="{ row }">
<el-link
:type="row.voltageOffset == '1' ? 'danger' : 'success'"
:class="row.voltageOffset == '1' ? 'percentage' : ''"
@click="
row.voltageOffset == '1'
? detailClick(row, '电压偏差(%)', 'voltageOffsetList')
: ''
"
>
{{ row.voltageOffset == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="vtimes" title="谐波电压(%)" min-width="110px">
<template #default="{ row }">
<el-link
:type="row.vtimes == '1' ? 'danger' : 'success'"
:class="row.vtimes == '1' ? 'percentage' : ''"
@click="
row.vtimes == '1' ? detailClick(row, '谐波电压(%)', 'vtimesList') : ''
"
>
{{ row.vtimes == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="itimes" title="谐波电流(A)" min-width="110px">
<template #default="{ row }">
<el-link
:type="row.itimes == '1' ? 'danger' : 'success'"
:class="row.itimes == '1' ? 'percentage' : ''"
@click="
row.itimes == '1' ? detailClick(row, '谐波电流(A)', 'itimestList') : ''
"
>
{{ row.itimes == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="ubalance" title="三相电压不平衡度(%)" min-width="150px">
<template #default="{ row }">
<el-link
:type="row.ubalance == '1' ? 'danger' : 'success'"
:class="row.ubalance == '1' ? 'percentage' : ''"
@click="
row.ubalance == '1'
? detailClick(row, '三相电压不平衡度(%)', 'ubalanceList')
: ''
"
>
{{ row.ubalance == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="voltageFluctuation" title="电压波动(%)" min-width="110px">
<template #default="{ row }">
<el-link
:type="row.voltageFluctuation == '1' ? 'danger' : 'success'"
:class="row.voltageFluctuation == '1' ? 'percentage' : ''"
@click="
row.voltageFluctuation == '1'
? detailClick(row, '电压波动(%)', 'voltageFluctuationList')
: ''
"
>
{{ row.voltageFluctuation == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="flicker" title="长时闪变" min-width="110px">
<template #default="{ row }">
<el-link
:type="row.flicker == '1' ? 'danger' : 'success'"
:class="row.flicker == '1' ? 'percentage' : ''"
@click="
row.flicker == '1' ? detailClick(row, '长时闪变', 'flickerList') : ''
"
>
{{ row.flicker == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="interHarmonic" title="间谐波电压含有率(%)" min-width="150px">
<template #default="{ row }">
<el-link
:type="row.interHarmonic == '1' ? 'danger' : 'success'"
:class="row.interHarmonic == '1' ? 'percentage' : ''"
@click="
row.interHarmonic == '1'
? detailClick(row, '间谐波电压含有率(%)', 'interHarmonicList')
: ''
"
>
{{ row.interHarmonic == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="sequenceCurrentUnbalance" title="电流不平衡度(%)" min-width="130px">
<template #default="{ row }">
<el-link
:type="row.sequenceCurrentUnbalance == '1' ? 'danger' : 'success'"
:class="row.sequenceCurrentUnbalance == '1' ? 'percentage' : ''"
@click="
row.sequenceCurrentUnbalance == '1'
? detailClick(
row,
'电流不平衡度(%)',
'sequenceCurrentUnbalanceList'
)
: ''
"
>
{{ row.sequenceCurrentUnbalance == '1' ? '越限' : '合格' }}
</el-link>
</template>
</vxe-column>
<vxe-column field="num3" fixed="right" title="操作" width="80">
<template #default="{ row }">
<el-button type="primary" link @click="addTo(row)">添加</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
</el-card>
<el-card class="box">
<template #header>
<div style="display: flex; justify-content: space-between">
<div class="card-header">
<span>报告参数</span>
</div>
<el-button icon="el-icon-Download" size="small" type="primary" @click="generateReports">
生成报表
</el-button>
</div>
</template>
<div :style="heightB" style="overflow-y: auto">
<div>
<el-tree style="max-width: 600px" :data="dataSource" node-key="id" default-expand-all>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span :style="data.level != 0 ? 'color: var(--el-color-primary)' : ''">
{{ data.time }}
</span>
<Close
v-if="data.level != 0"
style="margin-left: 5px; height: 14px"
@click="remove(node, data)"
/>
</span>
</template>
</el-tree>
</div>
</div>
</el-card>
</div>
</pane>
</splitpanes>
<!-- 时间弹框 -->
<timePopUpBox
v-if="timePopUpBox"
ref="timePopUpBoxRef"
:timePopUpBox="timePopUpBox"
@close="timePopUpBox = null"
/>
<!-- 越限详情 -->
<detail ref="detailRef" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import 'splitpanes/dist/splitpanes.css'
import { Splitpanes, Pane } from 'splitpanes'
import TableStore from '@/utils/tableStore'
import PointTree from '@/components/tree/pqs/pointTree.vue'
import TableHeader from '@/components/table/header/index.vue'
import { useDictData } from '@/stores/dictData'
import { mainHeight } from '@/utils/layout'
import { defaultAttribute } from '@/components/table/defaultAttribute'
import MyEchart from '@/components/echarts/MyEchart.vue'
import TimePopUpBox from './components/timePopUpBox.vue'
import { ElMessage } from 'element-plus'
import {
getDataByLineId,
getTargetLimitById,
exportExcelRangTemplate,
exportExcelListTemplate
} from '@/api/harmonic-boot/newEnergyAnalysis'
import detail from './components/detail.vue'
import { Close } from '@element-plus/icons-vue'
defineOptions({
name: 'newEnergy/newEnergyAnalysis'
})
const timePopUpBox = ref<anyObj | null>(null)
const flag = ref('0%~10%')
const value1 = ref('0%~10%')
const options1 = [
'0%~10%',
'10%~20%',
'20%~30%',
'30%~40%',
'40%~50%',
'50%~60%',
'60%~70%',
'70%~80%',
'80%~90%',
'90%~100%'
]
const height = mainHeight(20)
const heightB = mainHeight(445)
const size = ref(23)
const TableHeaderRef = ref()
const detailRef = ref()
const dotList: any = ref({})
const loading = ref(false)
const timePopUpBoxRef = ref()
const index = ref(0)
const treeList = ref([])
const powerList1: any = ref([
{
label: '0%~10%',
quantity: '0',
percentage: '10%',
crossTheLine: '越限',
value: '0%~10%'
},
{
label: '10%~20%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '10%~20%'
},
{
label: '20%~30%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '20%~30%'
},
{
label: '30%~40%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '30%~40%'
},
{
label: '40%~50%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '40%~50%'
}
])
const powerList2: any = ref([
{
label: '50%~60%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '50%~60%'
},
{
label: '60%~70%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '60%~70%'
},
{
label: '70%~80%',
quantity: '0',
percentage: '10%',
crossTheLine: '合格',
value: '70%~80%'
},
{
label: '80%~90%',
quantity: '0',
percentage: '10%',
crossTheLine: '越限',
value: '80%~90%'
},
{
label: '90%~100%',
quantity: '20',
percentage: '10%',
crossTheLine: '越限',
value: '90%~100%'
}
])
const tableData: any = ref([])
const dataSource: any = ref([
{
id: 1,
level: 0,
time: '0%~10%',
children: []
},
{
id: 2,
level: 0,
time: '10%~20%',
children: []
},
{
id: 3,
level: 0,
time: '20%~30%',
children: []
},
{
id: 4,
level: 0,
time: '30%~40%',
children: []
},
{
id: 5,
level: 0,
time: '40%~50%',
children: []
},
{
id: 6,
level: 0,
time: '50%~60%',
children: []
},
{
id: 7,
level: 0,
time: '60%~70%',
children: []
},
{
id: 8,
level: 0,
time: '70%~80%',
children: []
},
{
id: 9,
level: 0,
time: '80%~90%',
children: []
},
{
id: 10,
level: 0,
time: '90%~100%',
children: []
}
])
// 添加树
const addTo = (row: any) => {
// console.log("🚀 ~ addTo ~ uniqueObjectsByJSON([...dataSource.value[index.value].children, row]):", uniqueObjectsByJSON([...dataSource.value[index.value].children, row])[0].time)
let list = JSON.parse(JSON.stringify(dataSource.value[index.value].children))
dataSource.value[index.value].children = uniqueObjectsByJSON([...list, row])
}
// 树去重
const uniqueObjectsByJSON = (arr: any) => {
const seen = new Set()
return arr.filter((item: any) => {
const str = JSON.stringify(item)
if (!seen.has(str)) {
seen.add(str)
return true
}
return false
})
}
// 树删除
const remove = (node: any, data: any) => {
const parent = node.parent
const children = parent.data.children || parent.data
const index = children.findIndex(d => d.id === data.id)
children.splice(index, 1)
dataSource.value = [...dataSource.value]
}
const options = ref({})
const tableStore = new TableStore({
url: '/harmonic-boot/powerStatistics/getDataByLineId',
method: 'POST',
column: [],
beforeSearchFun: () => {
treeList.value = dotList.value
tableStore.table.params.lineId = dotList.value.id
tableStore.table.params.searchBeginTime = TableHeaderRef.value.datePickerRef.timeValue[0]
tableStore.table.params.searchEndTime = TableHeaderRef.value.datePickerRef.timeValue[1]
},
loadCallback: () => {
let res = tableStore.table.data
powerList1.value = [
{
label: '0%~10%',
quantity: res.minsNum0,
percentage: res.proportion0 + '%',
crossTheLine: res.isOrNot0,
value: '0%~10%'
},
{
label: '10%~20%',
quantity: res.minsNum1,
percentage: res.proportion1 + '%',
crossTheLine: res.isOrNot1,
value: '10%~20%'
},
{
label: '20%~30%',
quantity: res.minsNum2,
percentage: res.proportion2 + '%',
crossTheLine: res.isOrNot2,
value: '20%~30%'
},
{
label: '30%~40%',
quantity: res.minsNum3,
percentage: res.proportion3 + '%',
crossTheLine: res.isOrNot3,
value: '30%~40%'
},
{
label: '40%~50%',
quantity: res.minsNum4,
percentage: res.proportion4 + '%',
crossTheLine: res.isOrNot4,
value: '40%~50%'
}
]
powerList2.value = [
{
label: '50%~60%',
quantity: res.minsNum5,
percentage: res.proportion5 + '%',
crossTheLine: res.isOrNot5,
value: '50%~60%'
},
{
label: '60%~70%',
quantity: res.minsNum6,
percentage: res.proportion6 + '%',
crossTheLine: res.isOrNot6,
value: '60%~70%'
},
{
label: '70%~80%',
quantity: res.minsNum7,
percentage: res.proportion7 + '%',
crossTheLine: res.isOrNot7,
value: '70%~80%'
},
{
label: '80%~90%',
quantity: res.minsNum8,
percentage: res.proportion8 + '%',
crossTheLine: res.isOrNot8,
value: '80%~90%'
},
{
label: '90%~100%',
quantity: res.minsNum9,
percentage: res.proportion9 + '%',
crossTheLine: res.isOrNot9,
value: '90%~100%'
}
]
options.value = {
legend: {
show: false
},
tooltip: {},
xAxis: {
show: false
},
yAxis: {
show: false
},
dataZoom: { show: false },
options: {
series: [
{
type: 'pie',
center: ['50%', '52%'],
selectedOffset: 30,
label: {
show: true,
color: '#96A2B5',
lineHeight: 8,
minMargin: 10,
formatter: function (e) {
return '{name|' + e.name + '}{percent|' + e.data.percentage + '}'
},
rich: {
name: {
fontSize: 12
},
percent: {
fontSize: 14,
color: '#000',
padding: [0, 0, 0, 5]
}
}
},
data: [
...powerList1.value.map(item => {
return { value: item.quantity, name: item.label, percentage: item.percentage }
}),
...powerList2.value.map(item => {
return { value: item.quantity, name: item.label, percentage: item.percentage }
})
]
}
]
}
}
dataSource.value = [
{
id: 1,
level: 0,
time: '0%~10%',
children: []
},
{
id: 2,
level: 0,
time: '10%~20%',
children: []
},
{
id: 3,
level: 0,
time: '20%~30%',
children: []
},
{
id: 4,
level: 0,
time: '30%~40%',
children: []
},
{
id: 5,
level: 0,
time: '40%~50%',
children: []
},
{
id: 6,
level: 0,
time: '50%~60%',
children: []
},
{
id: 7,
level: 0,
time: '60%~70%',
children: []
},
{
id: 8,
level: 0,
time: '70%~80%',
children: []
},
{
id: 9,
level: 0,
time: '80%~90%',
children: []
},
{
id: 10,
level: 0,
time: '90%~100%',
children: []
}
]
analyseList(flag.value, index.value)
}
})
// 导出区间数据
const exportData = () => {
ElMessage('正在下载中,请稍等...')
exportExcelRangTemplate({
lineId: dotList.value.id,
searchValue: treeList.value?.name + ' 0%-100% 区间数据',
searchBeginTime: tableStore.table.params.searchBeginTime,
searchEndTime: tableStore.table.params.searchEndTime
}).then((res: any) => {
let blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = treeList.value?.name + ' 0%-100% 区间数据' // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
})
}
// 导出报告参数
const generateReports = () => {
let data: any = {
lineId: dotList.value.id,
searchValue: treeList.value?.name + ' 报告',
searchBeginTime: tableStore.table.params.searchBeginTime,
searchEndTime: tableStore.table.params.searchEndTime
}
dataSource.value.forEach((item: any, i: number) => {
// item.children
data[`time${i}`] = item.children.map((it: any) => it.time)
}),
exportExcelListTemplate(data).then((res: any) => {
let blob = new Blob([res], {
type: 'application/vnd.ms-excel'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
link.download = treeList.value?.name + ' 报告' // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
})
}
// 点击时间
const timeClick = (row: any) => {
let data = { ...row, field: index.value, lineId: dotList.value.id }
timePopUpBox.value = data
}
// 点击越限
const detailClick = (row: any, title: string, key: string) => {
console.log('🚀 ~ detailClick ~ row:', row)
detailRef.value.open({
row: row,
title: title,
key: key
})
}
// // 获取有功功率趋势分析数据
// const analyse = (e: any) => {
// getDataByLineId({
// lineId: e.id,
// searchBeginTime: tableStore.table.params.searchBeginTime,
// searchEndTime: tableStore.table.params.searchEndTime
// }).then(res => {})
// }
// 功率列表
const analyseList = (e: string, i: number) => {
flag.value = e
index.value = i
loading.value = true
getTargetLimitById({
lineId: dotList.value.id,
searchBeginTime: tableStore.table.params.searchBeginTime,
searchEndTime: tableStore.table.params.searchEndTime,
field: i
})
.then(res => {
loading.value = false
tableData.value = res.data
})
.catch(() => {
loading.value = false
})
}
provide('tableStore', tableStore)
onMounted(() => {
const dom = document.getElementById('navigation-splitpanes')
if (dom) {
size.value = Math.round((180 / dom.offsetHeight) * 120)
}
})
const handleNodeClick = (data: any, node: any) => {
console.log('🚀 ~ handleNodeClick ~ data:', data)
if (data.level == 6) {
dotList.value = data
// dotList.value.id = '6469e77fda42db12c7ca6620a092f03c1'
tableStore.index()
}
}
</script>
<style lang="scss">
.splitpanes.default-theme .splitpanes__pane {
background: #eaeef1;
}
.cardBox {
.el-card__body {
padding: 5px 10px 10px;
}
}
.box {
.el-card__body {
padding: 10px;
}
}
.container {
display: grid;
grid-template-columns: 5fr 2.5fr;
gap: 10px;
.card-top {
background-color: var(--el-color-primary-light-9);
// color: #fff;
.el-card__body {
display: grid; /* 使用 Grid 布局 */
grid-template-columns: repeat(5, 1fr); /* 每行5列每列占1份空间 */
text-align: center;
gap: 20px;
padding: 10px;
.img {
vertical-align: middle;
height: 16px;
}
}
}
.echart {
.el-card__body {
padding: 5px;
}
}
}
.el-card {
border-radius: 10px;
}
.hoverBox {
background-color: var(--el-color-primary-light-5);
border-radius: 10px;
}
.percentage {
text-decoration: underline;
text-underline-offset: 0.1em;
}
</style>
<style lang="scss" scoped>
:deep(.el-card__header) {
padding: 10px;
}
.card-header {
span {
font-weight: 600;
font-size: 16px;
}
}
</style>