Files
admin-sjzx/src/views/pqs/bearingCapacity/evaluationList/components/photovoltaic.vue
2025-09-26 10:59:37 +08:00

902 lines
32 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>
<splitpanes :style="{ height: height }" class="default-theme" id="navigation-splitpanes">
<pane :size="size">
<bearingTree
ref="TreeRef"
:default-expand-all="false"
:default-expanded-keys="monitoringPoint.state.lineId ? [monitoringPoint.state.lineId] : []"
:current-node-key="monitoringPoint.state.lineId"
@node-click="handleNodeClick"
@init="handleNodeClick"
></bearingTree>
</pane>
<pane style="background: #fff">
<TableHeader :showSearch="false" v-show="props.rowList.id == undefined">
<template #select>
<el-form-item label="时间">
<DatePicker ref="datePickerRef"></DatePicker>
</el-form-item>
</template>
<template #operation>
<el-button type="primary" icon="el-icon-Search" @click="onSubmit">查询</el-button>
<el-button type="primary" icon="el-icon-Download" @click="exportTemplate">导出模板</el-button>
<el-upload
action=""
accept=".xlsx"
:show-file-list="false"
:auto-upload="false"
:on-change="choose"
>
<el-button type="primary" class="ml10" icon="el-icon-Upload">离线导入</el-button>
</el-upload>
</template>
</TableHeader>
<div v-loading="loading" class="pt5">
<el-divider content-position="left">基础数据</el-divider>
<el-descriptions class="mb10" :column="4" size="" border>
<el-descriptions-item label="用户名称" label-align="center" width="20%">
<el-select
v-model="user"
placeholder="请选择用户名称"
size="small"
filterable
v-if="props.rowList.id == undefined"
style="width: 100%"
value-key="userId"
@change="userChange"
>
<el-option v-for="item in userList" :key="item" :label="item.userName" :value="item" />
</el-select>
<span v-else>{{ props.rowList.userName }}</span>
</el-descriptions-item>
<el-descriptions-item label="拟接入光伏容量" label-align="center" width="10%">
{{ userData?.protocolCapacity }}MVA
</el-descriptions-item>
<el-descriptions-item label="电压等级" label-align="center" width="10%">
{{ voltageLevelArr.filter(item => item.id === userData?.voltage)[0]?.name }}
</el-descriptions-item>
<el-descriptions-item label="额定容量" label-align="center" width="10%">
{{ lineList?.standardCapacity }}MVA
</el-descriptions-item>
</el-descriptions>
<div :style="`height: calc(${tabsHeight} - ${props.rowList.id == undefined ? '256px' : '188px'})`">
<el-tabs
v-model="activeName"
class="mb10"
v-if="showTabs"
@tab-change="tabChange"
:style="`height: calc(${tabsHeight} - ${
props.rowList.id == undefined ? '256px' : '188px'
})`"
>
<el-tab-pane label="有功功率" name="1" class="mt10">
<MyEChart :options="options1" />
</el-tab-pane>
<el-tab-pane label="无功功率" name="2" class="mt10">
<MyEChart :options="options" />
</el-tab-pane>
<el-tab-pane label="谐波电流幅值" name="3" class="mt10" style="position: relative">
<div style="position: absolute; z-index: 99; top: 10px; right: 200px; width: 80px">
<el-select
v-model="harmonicValue"
placeholder="请选择谐波"
@change="amplitudeQuery"
>
<el-option
v-for="item in harmonic"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</div>
<MyEChart :options="options" v-loading="currentLod" />
</el-tab-pane>
<el-tab-pane label="首端电压模型参数" name="4">
<vxe-table height="auto" v-bind="defaultAttribute" :data="voltageList">
<vxe-colgroup field="group0" title="模型参数">
<vxe-column field="name" width="180" title="相别"></vxe-column>
</vxe-colgroup>
<vxe-column field="a" title="a"></vxe-column>
<vxe-column field="b" title="b"></vxe-column>
<vxe-column field="c" title="c"></vxe-column>
</vxe-table>
</el-tab-pane>
</el-tabs>
</div>
<div class="btnBox mt10 mr10">
<el-button
type="primary"
icon="el-icon-VideoPlay"
@click="assess"
v-if="showBtn && props.rowList.id == undefined && !showAssess"
>
启动评估
</el-button>
<el-button type="primary" icon="el-icon-Download" @click="onExport" v-if="showAssess">
导出结果
</el-button>
</div>
<div class="bottomBox pt1" id="evaluateTheResults">
<el-divider content-position="left" v-show="showBtn">评估结果</el-divider>
<div class="evaluateTheResults" v-if="showAssess">
<div style="height: 250px; flex: 1">
<vxe-table
style="flex: 1.5"
v-bind="defaultAttribute"
height="auto"
ref="xTable"
:data="tableData"
>
<vxe-colgroup field="group0" title="等级">
<vxe-column field="name" width="180" title="结果"></vxe-column>
</vxe-colgroup>
<vxe-column field="level1" title="安全">
<template #default="{ row }">
<Select v-if="row.level1 == 1" class="SelectIcon" style="color: #2e7d32" />
<span v-else>{{ row.level1 }}</span>
</template>
</vxe-column>
<vxe-column field="level2" title="III级预警">
<template #default="{ row }">
<Select v-if="row.level2 == 1" class="SelectIcon" style="color: #0288d1" />
<span v-else>{{ row.level2 }}</span>
</template>
</vxe-column>
<vxe-column field="level3" title="II级预警">
<template #default="{ row }">
<Select v-if="row.level3 == 1" class="SelectIcon" style="color: #f57c00" />
<span v-else>{{ row.level3 }}</span>
</template>
</vxe-column>
<vxe-column field="level4" title="I级预警">
<template #default="{ row }">
<Select v-if="row.level4 == 1" class="SelectIcon" style="color: #c62828" />
<span v-else>{{ row.level4 }}</span>
</template>
</vxe-column>
</vxe-table>
</div>
<div style="height: 250px; width: 350px">
<MyEChart style="flex: 1" :options="pieCharts" />
</div>
</div>
</div>
</div>
</pane>
</splitpanes>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, provide } from 'vue'
import 'splitpanes/dist/splitpanes.css'
import { Splitpanes, Pane } from 'splitpanes'
import bearingTree from '@/components/tree/pqs/bearingTree.vue'
import DatePicker from '@/components/form/datePicker/index.vue'
import { mainHeight } from '@/utils/layout'
import TableHeader from '@/components/table/header/index.vue'
import MyEChart from '@/components/echarts/MyEchart.vue'
import { useMonitoringPoint } from '@/stores/monitoringPoint'
import { defaultAttribute } from '@/components/table/defaultAttribute'
import { harmonicOptions } from '@/utils/dictionary'
import { Select } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import {
queryCarryCapacityData,
uploadExcel,
queyDetailUser,
queryCarryCapacityQData,
queryCarryCapacityIData,
carryCapacityCal,
getExcelTemplate,
getLineDetailData,
modelTraining,
queryResultbyCondition
} from '@/api/advance-boot/bearingCapacity'
import html2canvas from 'html2canvas'
import { yMethod } from '@/utils/echartMethod'
import { useDictData } from '@/stores/dictData'
const props = defineProps(['rowList'])
const harmonic = harmonicOptions.filter(item => item.value < 26)
const currentLod = ref(false)
const monitoringPoint = useMonitoringPoint()
const size = ref(0)
const dictData = useDictData()
const datePickerRef = ref()
const height = mainHeight(80).height
const tabsHeight = mainHeight(300).height
const voltageLevelArr = dictData.getBasicData('Dev_Voltage_Stand')
const tableData: any = ref([
{
name: '配变首端电压',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '配变功率因素',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '等效负载率最小值',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '各次谐波电流幅值',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
}
])
const harmonicValue = ref(2)
const TreeRef = ref()
const voltageList: any = ref([])
const showAssess = ref(false)
const showBtn = ref(false)
const flag = ref(true)
const loading = ref(false)
const user = ref('')
const userList: any = ref([])
const lineList: any = ref([])
const options = ref({})
const options1 = ref({})
const options2 = ref({})
const options3 = ref({})
const pieCharts = ref({})
const showTabs = ref(false)
const activeName = ref('1')
const userData: any = ref({})
const stringMap: any = ref({})
const p_βminMap: any = ref({})
const i_βmax: any = ref({})
const q_βminMap: any = ref({})
const dotList: any = ref({
name: monitoringPoint.state.lineName.split('>')[3],
id: monitoringPoint.state.lineId,
level: 6
})
onMounted(() => {
const dom = document.getElementById('navigation-splitpanes')
if (dom) {
size.value = Math.round((180 / (dom.offsetHeight + 60)) * 100)
}
datePickerRef.value.setTimeOptions([{ label: '周', value: 4 }])
datePickerRef.value.setTheDate(4)
info()
})
const info = () => {
queyDetailUser({
pageNum: 1,
pageSize: 1000,
userType: dictData.getBasicData('CARRY_CAPCITY_USER_TYPE', [
'Electrified_Rail_Users',
'Charging_Station_Users',
'Electric_Heating_Load_Users'
])[0]?.id
}).then(res => {
userList.value = res.data.records
if (props.rowList.id != undefined) {
user.value = userList.value.filter(item => item.userId == props.rowList.userId)[0]
userChange(user.value)
} else {
user.value = userList.value[0] || {}
userData.value = res.data.records[0] || {}
}
})
}
// 查询
const onSubmit = async () => {
activeName.value = '1'
if (dotList.value.level != 6) {
return ElMessage.warning('请选择线路')
}
showAssess.value = false
showTabs.value = false
loading.value = true
let form = {
endTime: props.rowList.endTime || datePickerRef.value.timeValue[1],
lineId: props.rowList.lineId || dotList.value.id,
startTime: props.rowList.startTime || datePickerRef.value.timeValue[0],
userId: dictData.state.area[0].id,
time: harmonicValue.value
}
initEchart(1, '有功功率')
initEchart(2, '无功功率')
initEchart(3, '谐波电流幅值')
voltageList.value = []
showBtn.value = false
if (props.rowList.id != undefined) {
// user.value = props.rowList
rendering(props.rowList)
}
try {
// 显示加载状态
// 获取有功功率数据
const res1 = await queryCarryCapacityData(form)
stringMap.value = res1.data.stringMap
p_βminMap.value = res1.data.p_βminMap
// 处理电压列表数据
voltageList.value = Object.entries(res1.data.stringMap).map(([k, values]) => ({
name: k,
c: values[0],
a: values[1],
b: values[2]
}))
// 设置有功功率图表
setEChart(1, res1.data.data, '有功功率', 'W')
// 获取无功功率数据并设置图表
const res2 = await queryCarryCapacityQData(form)
q_βminMap.value = res2.data.q_βminMap
setEChart(2, res2.data.data, '无功功率', 'Var')
// 获取谐波电流数据并设置图表
const res3 = await queryCarryCapacityIData(form)
i_βmax.value = res3.data.i_βmax
setEChart(3, res3.data.data, '谐波电流幅值', 'A')
// 显示结果和按钮
showTabs.value = true
showBtn.value = true
currentLod.value = false
// 如果没有rowList.id查询结果
if (props.rowList.id === undefined) {
const res4 = await queryResultbyCondition({ ...form, userId: user.value.userId })
rendering(res4.data)
}
} catch (err) {
console.error('请求出错:', err)
} finally {
// 无论成功失败,都关闭加载状态
loading.value = false
}
// Promise.all([queryCarryCapacityData(form), queryCarryCapacityQData(form), queryCarryCapacityIData(form)])
// .then(res => {
// // 有功功率
// stringMap.value = res[0].data.stringMap
// p_βminMap.value = res[0].data.p_βminMap
// q_βminMap.value = res[1].data.q_βminMap
// i_βmax.value = res[2].data.i_βmax
// for (let k in res[0].data.stringMap) {
// voltageList.value.push({
// name: k,
// c: res[0].data.stringMap[k][0],
// a: res[0].data.stringMap[k][1],
// b: res[0].data.stringMap[k][2]
// })
// }
// setEChart(1, res[0].data.data, '有功功率', 'w')
// setEChart(2, res[1].data.data, '无功功率', 'Var')
// setEChart(3, res[2].data.data, '谐波电流幅值', 'A')
// showBtn.value = true
// loading.value = false
// // props.rowList.id != undefined
// })
// .catch(err => {
// loading.value = false
// })
}
// 谐波次数变化
const amplitudeQuery = async (e: any) => {
currentLod.value = true
let form = {
endTime: props.rowList.endTime || datePickerRef.value.timeValue[1],
lineId: props.rowList.lineId || dotList.value.id,
startTime: props.rowList.startTime || datePickerRef.value.timeValue[0],
userId: dictData.state.area[0].id,
time: harmonicValue.value
}
const res3 = await queryCarryCapacityIData(form)
i_βmax.value = res3.data.i_βmax
setEChart(3, res3.data.data, '谐波电流幅值', 'A')
options.value = options3.value
currentLod.value = false
}
// 用户变化
const userChange = (e: any) => {
userData.value = e
}
// 模型训练
const modelTrain = () => {
modelTraining({
endTime: datePickerRef.value.timeValue[1],
lineId: dotList.value.id,
startTime: datePickerRef.value.timeValue[0],
time: 0,
userId: dictData.state.area[0].id
}).then(res => {})
}
const setEChart = (val: any, data: any, text: string, name: string) => {
// console.log("🚀 ~ setEChart ~ data:", data.map(item => item.value))
let [min, max] = yMethod(data.map(item => item.value))
let options = {
title: {
text: text,
x: 'center'
// textStyle: {
// fontWeight: 'normal'
// }
},
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, index: any) => {
str += `${el.marker}${el.seriesName.split('(')[0]}${
el.value[1] ? el.value[1] + ' ' + (el.value[2] || '') : '-'
}<br>`
})
return str
}
},
xAxis: {
// data: data.filter(item => item.phaseType == 'A').map(item => item.time),
name: '时间',
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
type: 'value',
name: name,
max: max,
min: min
},
color: ['#DAA520', '#2E8B57', '#A52a2a'],
series: [
{
name: 'A相',
type: 'line',
// smooth: true,
symbol: 'none',
data: data
.filter(item => item.phaseType == 'A')
.map(item => (item.value == 3.1415926 ? '' : [item.time, item.value.toFixed(2)]))
},
{
name: 'B相',
type: 'line',
// smooth: true,
symbol: 'none',
data: data
.filter(item => item.phaseType == 'B')
.map(item => (item.value == 3.1415926 ? '' : [item.time, item.value.toFixed(2)]))
},
{
name: 'C相',
type: 'line',
// smooth: true,
symbol: 'none',
data: data
.filter(item => item.phaseType == 'C')
.map(item => (item.value == 3.1415926 ? '' : [item.time, item.value.toFixed(2)]))
}
],
options: {
dataZoom: [
{
type: 'inside',
height: 13,
start: 0,
bottom: '20px',
end: 10
},
{
start: 0,
height: 13,
bottom: '20px',
end: 10
}
]
}
}
val == 1
? (options1.value = options)
: val == 2
? (options2.value = options)
: val == 3
? (options3.value = options)
: ''
}
// 承载能力评估
const assess = () => {
if (dotList.value.level != 6) {
return ElMessage.warning('请选择线路')
}
carryCapacityCal({
endTime: datePickerRef.value.timeValue[1],
lineId: dotList.value.id,
startTime: datePickerRef.value.timeValue[0],
userId: userData.value.userId, //dictData.state.area[0].id,
i_βmax: i_βmax.value,
p_βminMap: p_βminMap.value,
q_βminMap: q_βminMap.value,
s_T: lineList.value.standardCapacity,
s_pv: userData.value.protocolCapacity,
scale: userData.value.voltage,
stringMap: stringMap.value
}).then((res: any) => {
rendering(res.data)
})
}
const rendering = (data: any) => {
tableData.value = [
{
name: '配变首端电压',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '配变功率因素',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '等效负载率最小值',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
},
{
name: '各次谐波电流幅值',
level1: '/',
level2: '/',
level3: '/',
level4: '/'
}
]
let ChartsList = [
{
name: '安全',
value: 0
},
{
name: 'III级预警',
value: 0
},
{
name: 'II级预警',
value: 0
},
{
name: 'I级预警',
value: 0
},
{
name: '禁止接入',
value: 0
}
]
tableData.value[0][`level` + data.utlevel] = 1
tableData.value[1][`level` + data.pfTLevel] = 1
tableData.value[2][`level` + data.btlevel] = 1
tableData.value[3][`level` + data.ilevel] = 1
ChartsList[data.reslutLevel - 1].value = 1
showAssess.value = true
pieCharts.value = {
legend: {
type: 'scroll',
orient: 'vertical',
right: '10px',
top: '40px',
selectedMode: false // 是否允许点击
},
toolbox: { show: false },
xAxis: {
show: false
},
yAxis: {
show: false
},
color: ['#2E7D32', '#0288D1', '#F57C00', '#F57C00', '#C62828'],
dataZoom: { show: false },
series: [
{
type: 'pie',
center: ['40%', '50%'],
radius: ['45%', '58%'],
label: {
normal: {
show: false
}
},
data: ChartsList
}
],
options: {
title: [
{
text: '评估结果',
left: '25%'
// left: '25%'
},
{
text:
data.reslutLevel == 5
? '禁止接入'
: data.reslutLevel == 4
? 'I级预警'
: data.reslutLevel == 3
? 'II级预警'
: data.reslutLevel == 2
? 'III级预警'
: data.reslutLevel == 1
? '安全'
: '',
left: data.reslutLevel == 1 ? '33%' : '29%',
top: '45%',
textStyle: {
color:
data.reslutLevel == 5
? '#C62828'
: data.reslutLevel == 4
? '#F57C00'
: data.reslutLevel == 3
? '#F57C00'
: data.reslutLevel == 2
? '#0288D1'
: '#2E7D32',
foontWeight: '500'
}
}
]
}
}
}
// 下载模版
const exportTemplate = () => {
getExcelTemplate().then((res: any) => {
let blob = new Blob([res], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
})
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a') // 创建a标签
link.href = url
// link.download = "电压暂降事件分析报告"; // 设置下载的文件名
link.download = '数据模版' // 设置下载的文件名
document.body.appendChild(link)
link.click() //执行下载
document.body.removeChild(link)
})
}
// 离线导入
const choose = (e: any) => {
if (dotList.value.level != 6) {
return ElMessage.warning('请选择线路')
}
let form = {
endTime: datePickerRef.value.timeValue[1],
lineId: dotList.value.id,
startTime: datePickerRef.value.timeValue[0]
}
if (e.raw.type != 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') {
return ElMessage.warning('请选择Excel文件!')
}
uploadExcel(e.raw, form).then(res => {
ElMessage.success('导入成功')
})
}
const tabChange = (e: any) => {
nextTick(() => {
e == 2 ? (options.value = options2.value) : e == 3 ? (options.value = options3.value) : ''
})
}
const handleNodeClick = (data: any, node: any) => {
if (flag.value) {
lineList.value = {}
dotList.value = data
if (data.level == 6) {
getLineDetailData({ id: data.id }).then((res: any) => {
lineList.value = res.data
})
}
}
if (props.rowList.userId != undefined && flag.value) {
TreeRef.value.setKey(props.rowList.lineId)
onSubmit()
flag.value = false
}
}
// 初始化 echart 默认值
const initEchart = (val: number, text: string) => {
let list = {
title: {
text: text,
x: 'center'
},
xAxis: {
name: '时间',
type: 'time',
axisLabel: {
formatter: {
day: '{MM}-{dd}',
month: '{MM}',
year: '{yyyy}'
}
}
},
yAxis: {
type: 'value',
name: name
},
color: ['#DAA520', '#2E8B57', '#A52a2a'],
series: [
{
name: 'A相',
type: 'line',
// smooth: true,
symbol: 'none',
data: []
},
{
name: 'B相',
type: 'line',
// smooth: true,
symbol: 'none',
data: []
},
{
name: 'C相',
type: 'line',
symbol: 'none',
data: []
}
]
}
val == 1 ? (options1.value = list) : val == 2 ? (options2.value = list) : val == 3 ? (options3.value = list) : ''
}
// 导出
const onExport = () => {
// 转换成canvas
html2canvas(document.getElementById('evaluateTheResults'), {
scale: 2
}).then(function (canvas) {
// 创建a标签实现下载
let creatIMg = document.createElement('a')
creatIMg.download = '光伏电站承载能力评估结果.png' // 设置下载的文件名,
creatIMg.href = canvas.toDataURL() // 下载url
creatIMg.click()
creatIMg.remove() // 下载之后把创建的元素删除
})
}
onMounted(() => {
initEchart(1, '有功功率')
initEchart(2, '无功功率')
initEchart(3, '谐波电流幅值')
})
</script>
<style lang="scss" scoped>
.splitpanes.default-theme .splitpanes__pane {
background: #eaeef1;
}
.grid-content {
text-align: center;
}
.bottomBox {
margin-top: -3px;
}
.btnBox {
display: flex;
justify-content: end;
}
:deep(.vxe-table--header thead tr:first-of-type th:first-of-type) {
// background: var(--vxe-table-header-background-color);
background-image: linear-gradient(var(--vxe-table-border-color), var(--vxe-table-border-color)),
linear-gradient(#e8eaec00, #e8eaec00);
}
:deep(.vxe-table--header thead tr:first-of-type th:first-of-type:before) {
content: '';
position: absolute;
width: 1px;
height: 98px;
/*这里需要自己调整根据td的宽度和高度*/
top: 0;
left: 0;
background-color: grey;
opacity: 0.3;
display: block;
transform: rotate(-66deg);
/*这里需要自己调整,根据线的位置*/
transform-origin: top;
}
:deep(.vxe-table--header thead tr:last-of-type th:first-of-type:before) {
content: '';
position: absolute;
width: 1px;
height: 98px;
/*这里需要自己调整根据td的宽度和高度*/
bottom: 0;
right: 0;
background-color: grey;
opacity: 0.3;
display: block;
transform: rotate(-66deg);
/*这里需要自己调整,根据线的位置*/
transform-origin: bottom;
}
.SelectIcon {
height: 18px;
margin-top: 5px;
}
.el-select {
min-width: 80px;
}
:deep(.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-center) {
width: 80px !important;
}
.evaluateTheResults {
display: flex;
}
</style>