<template> <div class="rateAnalysis" :class="!theme ? 'themeDark' : 'themeLight'"> <div class="rateAnalysisMain"> <div class="main_top"> <p class="topPsty">对风偏差分析</p> </div> <div class="main"> <div class="treeDataMain"> <tree-cop :data="treeData" @checkChange="funTreeCheckChange" :show-checkbox="false" :height="treeHeight" @currentChange="funCurrentChange" @refresh="funGetTree"> </tree-cop> </div> <div class="excelDataMain"> <excel-cop :checkIds="excelCheckIds" :showCheckbox="false" :data="excelList" :height="excelHeight" @excelChange="funExcelChange" @checkChange="funExcelCheckChange" :theme="theme"></excel-cop> </div> <div class="tableDataMain"> <el-row :style="{ height: tableHeight }"> <el-col :span="12" v-for="(item, index) in chartData" :key="item.id" style="height: 45%"> <el-icon :style="!theme ? 'color: #fff' : ''" size="18" @click="funActCop(item, 'chartCop' + (index + 1))"> <ZoomIn /> </el-icon> <chart-cop class="" height="100%" width="100%" :xAxis="item.xAxis" :isRadar="item.isRadar" :theme="theme" :echartsTheme="echartsTheme" :subtext="item.subtext" :title="item.title" :series="item.series"> </chart-cop> </el-col> <el-col :span="12" v-if="!!lineSeries.length" style="height: 50%"> <el-icon :style="!theme ? 'color: #fff' : ''" size="18" @click="funActCop({ xAxis: linexAxis, yAxis: lineyAxis, series: lineSeries, dataset: lineDataSet }, 'lineChartCop')"> <ZoomIn /> </el-icon> <line-chart-cop class="" height="100%" width="100%" :xAxis="linexAxis" :yAxis="lineyAxis" :theme="theme" :echartsTheme="echartsTheme" :series="lineSeries" subtext="对风偏差分析图" :dataset="lineDataSet"></line-chart-cop> </el-col> <el-col :span="12" v-if="!!lineSeries.length" style="height: 50%"> <el-icon :style="!theme ? 'color: #fff' : ''" size="18" @click="funActCop({ xAxis: scatterxData, yAxis: scatteryData, series: scatterSeries }, 'scatterSingleChartCop')"> <ZoomIn /> </el-icon> <scatter-single-chart-cop class="" height="95%" width="100%" :xAxis="scatterxData" :theme="theme" :echartsTheme="echartsTheme" :yAxis="scatteryData" :series="scatterSeries" subtext="静态偏航对风分析图"> </scatter-single-chart-cop> </el-col> </el-row> </div> </div> </div> <el-dialog custom-class="windLifeDialog" v-model="wtDialog" draggable title="风机功率点位"> <el-tabs v-model="wtTab"> <el-tab-pane label="数据" name="table"> <el-table :data="wtData" row-key="id" :max-height="550"> <el-table-column property="wtId" align="center" label="风机" /> <el-table-column property="time" sortable :width="160" align="center" label="时间" /> <el-table-column property="speed" sortable align="center" label="风速(m/s)" /> <el-table-column property="power" sortable align="center" label="功率(kW)" /> <el-table-column property="rr" sortable align="center" label="转速" /> <el-table-column property="filter" sortable align="center" label="是否有用点" /> </el-table> </el-tab-pane> <el-tab-pane label="故障" name="problem" disabled> </el-tab-pane> <el-tab-pane label="预警" name="warning" disabled> </el-tab-pane> </el-tabs> </el-dialog> <el-dialog custom-class="windLifeDialog" draggable width="80%" v-model="dialog" :title="actDiaTitle"> <el-form class="whitespace-nowrap" :inline="true" :model="queryForm"> <el-form-item label="" class="!mb-0"> <el-select v-model="queryForm.checkIds" clearable @clear="checkAll = false" collapse-tags multiple> <el-option label="全选" :class="{'selected': checkAll}" @click="funCheckAll"></el-option> <el-option v-for="item in chartExcelList" :key="item.id" :value="item.id" :label="item.name"> </el-option> </el-select> </el-form-item> <el-form-item class="!mb-0"> <submit-btn desc="查询" @click="funDiaSubmit"></submit-btn> <submit-btn desc="导出" @click="funDiaExport"></submit-btn> </el-form-item> </el-form> <div v-loading="exportLoading"> <div ref="diaPanelRef" style="height: 650px"> <component :is="item.actCop" :width="actCopList.length > 1 ? '50%' : '100%'" height="100%" v-for="item in actCopList" :key="item.id" :xAxis="item.xAxis" :subtext="item.subtext" :isRadar="item.isRadar" :title="item.title" :series="item.series" :theme="theme" :echartsTheme="echartsTheme" :isDiaAlone="(actCopList.length === 1)" @dblclick="funDbClick(item)" :yAxis="item.yAxis" :dataset="item.dataset" :brush="item.isBrush" @getSelected="funChartSelect"></component> </div> </div> </el-dialog> </div> </template> <script setup name="rateAnalysis"> import excelCop from '@/components/generatingCapacityComponent/excel.vue' import treeCop from '@/components/generatingCapacityComponent/tree.vue' import chartCop from './components/chart.vue' import lineChartCop from './components/lineChart.vue' // import SubmitBtn from '../../../components/SubmitBtn.vue' import scatterSingleChartCop from './components/scatterSingleChart.vue' // import { ElMessage } from 'element-plus'; import { onMounted, ref, onActivated, shallowRef, reactive, nextTick, watch } from 'vue' import { useStore } from 'vuex'; import httpRequest from '@/utils/request.js' import tools from '@tools/htmlToPdf.js' // import flowerRes from '@/data/flower.json' // import lineChartRes from '@/data/lineNew.json' /**配置参数 */ const treeHeight = ref(window.innerHeight - 125 + 'px') //tree高度 const excelHeight = ref(window.innerHeight - 125 + 'px') //excel高度 const tableHeight = ref(window.innerHeight - 125 + 'px') /**excel 开始 */ const excelCheckIds = ref([]) const excelList = ref([]) //点击excel项时 const funExcelChange = async (obj) => { excelCheckIds.value = [obj.id] //当为单选展示风机图表时 chartExcelList.value = excelList.value.map(o => { return { ...o, name: o.windturbine } }) // 选中excel当前项时, excel列表赋值给dialog 下拉框 queryForm.checkIds = excelList.value.map(o => o.id) checkAll.value = true funSubmit() } const funExcelCheckChange = ({ checkArr, data }) => { excelCheckIds.value = checkArr } /**tree 开始 */ const treeData = ref([]) const actTreeNode = ref(null) const funRepeatMap = (arr) => { return arr.map(o => { if (o.children) { const findIndex = o.children.findIndex(p => !!p.type) if (findIndex !== -1) { o.childs = o.children o.children = [] if (!actTreeNode.value) { actTreeNode.value = o } } } return { ...o, children: o.children.length ? funRepeatMap(o.children) : [] } }) } const funGetTree = async () => { const res = await httpRequest.get("/power/process/tree") actTreeNode.value = null excelList.value = [] treeData.value = funRepeatMap(res.data) if (actTreeNode.value) { funCurrentChange({ current: actTreeNode.value, currentNode: null }) funExcelChange({ id: actTreeNode.value.childs[0].id }) } } const funTreeCheckChange = ({ current, checkedNodes, checkedKeys, halfCheckedNodes, halfCheckedKeys }) => { //tree change -> excel change funCurrentChange({ current, currentNode: '' }) const checkIds = [] if (checkedNodes.length) { for (const node of checkedNodes) { if (node.childs && node.childs.length) { for (const child of node.childs) { checkIds.push(child.id) } } } } excelCheckIds.value = checkIds } const funCurrentChange = ({ current, currentNode }) => { if (current.childs) { excelList.value = current.childs.map(o => { return { id: o.id, interval: o.interval, path: o.path, prepareid: o.prepareid, station: o.station, time: o.time, type: o.type, windturbine: o.windturbine, name: o.path.substring(o.path.indexOf(o.station + '_') + (o.station + '_').length) } }) } else { excelList.value = [] } } /**chart */ const funText = (index) => { let str = '' switch (index) { case 0: str = '0-2.5' break case 1: str = '2.5-5' break case 2: str = '5-7.5' break case 3: str = '7.5-10' break case 4: str = '10-12.5' break case 5: str = '12.5-15' break case 6: str = '15-17.5' break case 7: str = '17.5-20' break case 8: str = '20-22.5' break case 9: str = '22.5-25' break case 10: str = '25-inf' break } return str } const chartData = ref([]) //roses的chartList let chartId = 1 /**submit */ const funSubmit = async () => { const rosesRes = await httpRequest.get('/wind/roses', { params: { ids: excelCheckIds.value.join(','), mode: 0 } }) const lineRes = await httpRequest.get('/wind/deviation/ratio', { params: { ids: excelCheckIds.value.join(','), mode: 0 } }) // const rosesRes = flowerRes // const lineRes = lineChartRes if (rosesRes.code === 200) { if (rosesRes.data.length) { // console.log(rosesRes.data) chartData.value = [] for (const chart of rosesRes.data) { chartData.value.push({ id: chartId, title: '', subtext: '风速风向玫瑰图', xAxis: { type: 'category', boundaryGap: false, data: ['N', '', 'N-E', '', 'E', '', 'S-E', '', 'S', '', 'S-W', '', 'W', '', 'W-N', '' ], splitLine: { show: true }, }, isRadar: false, series: chart.roses.length ? chart.roses.map((o, index) => { return { type: 'bar', data: o, coordinateSystem: 'polar', name: funText(index), stack: 'a', emphasis: { focus: 'series' } } }) : [] }) chartId++ chartData.value.push({ id: chartId, title: '', subtext: '风速风向频次玫瑰图', isRadar: true, xAxis: { type: 'category', boundaryGap: false, data: ['N', '', 'N-E', '', 'E', '', 'S-E', '', 'S', '', 'S-W', '', 'W', '', 'W-N', '' ], splitLine: { show: true } }, series: chart.count.length ? [...chart.count.map((o, index) => { return { type: 'bar', data: o, coordinateSystem: 'polar', name: funText(index), stack: 'a', emphasis: { focus: 'series' } } }), { type: 'radar', // coordinateSystem: 'polar', tooltip: { trigger: 'item', }, // smooth: true, // areaStyle: {}, name: '对风', data: [{ value: chart.radar, }], }] : [] }) chartId++ scatterSeries.value[0].data = chart.frequency.data.length ? chart.frequency.data.map(( item) => { return [item[1] + '', item[0] + '', (item[2] * 15).toFixed(1)]; }) : [] scatterSeries.value[0].markLine.data = [{ xAxis: `${chart.frequency.avg}`, name: `平均偏航:${chart.frequency.avg}度`, }] } } } if (lineRes.code === 200) { if (lineRes.data.length) { lineDataSet.value[0].source = lineRes.data[0].scatter.map(o => { return [o.x + '', o.y] }) lineSeries.value = [{ name: "对风频次", type: "line", symbol: "line", //设定为实心点 symbolSize: 0, //设定实心点的大小 smooth: true, //这个是把线变成曲线 data: lineRes.data[0].count, yAxisIndex: 1, }, { type: 'effectScatter', showEffectOn: "emphasis", rippleEffect: { scale: 1 }, legendHoverLink: false, name: '数据散点', symbolSize: 5, datasetIndex: 0, encode: { x: 'x', y: 'y' }, yAxisIndex: 0, } ] } } } /**lineChart */ const linexAxis = ref({ type: 'category', data: new Array(101).fill(-50).map((o, index) => Number((o + index).toFixed(1))), splitLine: { show: false }, axisTick: { show: true } }) const lineyAxis = ref([{ type: 'value', name: 'm/s', splitLine: { show: false }, axisTick: { show: true } }, { type: 'value', name: '频次', splitLine: { show: false }, axisTick: { show: true } }]) const lineSeries = ref([]) const lineDataSet = ref([{ source: [] }]) // 圈选散点触发函数 const funChartSelect = async (batch) => { const wDataArr = [] const yDataArr = [] let scatterls = [] let dataSetObj = [] wtData.value = [] if (batch.length && actCopList.value[0].dataset) { scatterls = batch[0].selected[1].dataIndex if (scatterls.length) { dataSetObj = JSON.parse(actCopList.value[0].dataset) if (scatterls.length) { for (const scatterIndex of scatterls) { wDataArr.push(dataSetObj[0].source[scatterIndex].k) } } const wtRes = await httpRequest.get('/power/fitting/filter', { params: { yk: yDataArr.join(','), wk: wDataArr.join(',') } }) if (wtRes.code === 200) { let id = 1 const tempArr = [] //用于以风机id 聚合dataArr if (wtRes.data.length) { for (const data of wtRes.data) { if (tempArr.length) { const findIndex = tempArr.findIndex(o => o.wtId === data.wtId) if (findIndex !== -1) { if (!tempArr[findIndex].children) { tempArr[findIndex].children = [] } tempArr[findIndex].children.push({ ...data, id: id, filter: data.filter === 0 ? '是' : '否' }) id++ } else { tempArr.push({ ...data, id: id, filter: data.filter === 0 ? '是' : '否' }) id++ } } else { tempArr.push({ ...data, id: id, filter: data.filter === 0 ? '是' : '否' }) id++ } } wtDialog.value = true nextTick(() => { wtTab.value = 'table' wtData.value = tempArr }) } } } } } /**scatter chart */ const scatterxData = ref([{ type: 'category', name: '度', data: new Array(61).fill(-30).map((o, index) => Number((o + index))), boundaryGap: false, splitLine: { show: true }, axisLine: { show: true } }]) const scatteryData = ref([{ type: 'category', data: [5, 6, 7, 8, 9, 10], axisLine: { show: false }, name: 'm/s', splitLine: { show: false }, }, ]) const scatterSeries = ref( [{ name: '对风偏航', type: 'scatter', symbolSize: function (val) { return val[2]; }, data: [], markLine: { symbol: 'none', label: { show: false, }, lineStyle: { color: '#F72C5B', width: '3', }, data: [{ // yAxis: powerproductionNum.value, }] }, animationDelay: function (idx) { return idx * 5; } }] ) /**dialog 数据 */ const wtDialog = ref(false) const wtData = ref([]) const wtTab = ref('table') /**dialog */ const dialog = ref(false) const actChartName = ref('') const actDiaTitle = ref('') const diaPanelRef = ref() const exportLoading = ref(false) const actCopList = ref([ // { // xAxis: [], // subtext: '', // title: '', // isRadar: false, // series: [], // yAxis: [], // dataset: [] // } ]) // 作为actCopList的备份 在actCopList赋值多个时 同时赋值, 在dialog弹出时清空. 作用: 在actCopList变化时, 重新赋值原始数据 const actCopListBak = ref([]) const checkAll = ref(true) const queryForm = reactive({ checkIds: [] }) const funCheckAll = () => { checkAll.value = !checkAll.value if (checkAll.value) { queryForm.checkIds = chartExcelList.value.map(o => o.id) } else { queryForm.checkIds = [] } } const chartExcelList = ref([]) //dialog 下拉项 const funActCop = (obj, type) => { switch (type) { case 'chartCop1': actChartName.value = 'chartCop1' obj.actCop = shallowRef(chartCop) actDiaTitle.value = '风速风向玫瑰图' break case 'chartCop2': actChartName.value = 'chartCop2' obj.actCop = shallowRef(chartCop) actDiaTitle.value = '风速风向频次玫瑰图' break case 'lineChartCop': actChartName.value = 'lineChartCop' obj.actCop = shallowRef(lineChartCop) actDiaTitle.value = '对风偏差分析图' break case 'scatterSingleChartCop': actChartName.value = 'scatterSingleChartCop' obj.actCop = shallowRef(scatterSingleChartCop) actDiaTitle.value = '静态偏航对风分析图' break } obj.isBrush = type === 'lineChartCop' ? false : false obj.id = chartId chartId++ dialog.value = true actCopListBak.value = [] nextTick(() => { actCopList.value = [obj] }) } const funDiaSubmit = async () => { let url = '' switch (actChartName.value) { case 'chartCop1': url = '/wind/roses' break case 'chartCop2': url = '/wind/roses' break case 'lineChartCop': url = '/wind/deviation/ratio' break case 'scatterSingleChartCop': url = '/wind/roses' break } if (url) { const res = await httpRequest.get(url, { params: { ids: queryForm.checkIds.join(','), mode: 0 } }) if (res.code === 200) { actCopList.value = [] actCopListBak.value = [] //清空备份 if (res.data.length) { for (const chart of res.data) { if (actChartName.value === 'chartCop1') { actCopList.value.push({ id: chartId, isBrush: false, actCop: shallowRef(chartCop), title: chart.wt, subtext: '风速风向玫瑰图', xAxis: { type: 'category', boundaryGap: false, data: ['N', '', 'N-E', '', 'E', '', 'S-E', '', 'S', '', 'S-W', '', 'W', '', 'W-N', '' ], splitLine: { show: true }, }, isRadar: false, series: chart.roses.length ? chart.roses.map((o, index) => { return { type: 'bar', data: o, coordinateSystem: 'polar', name: funText(index), stack: 'a', emphasis: { focus: 'series' } } }) : [] }) chartId++ } if (actChartName.value === 'chartCop2') { actCopList.value.push({ id: chartId, isBrush: false, actCop: shallowRef(chartCop), title: chart.wt, subtext: '风速风向频次玫瑰图', xAxis: { type: 'category', boundaryGap: false, data: ['N', '', 'N-E', '', 'E', '', 'S-E', '', 'S', '', 'S-W', '', 'W', '', 'W-N', '' ], splitLine: { show: true } }, isRadar: true, series: chart.count.length ? [...chart.count.map((o, index) => { return { type: 'bar', data: o, coordinateSystem: 'polar', name: funText(index), stack: 'a', emphasis: { focus: 'series' } } }), { type: 'radar', // coordinateSystem: 'polar', tooltip: { trigger: 'item' }, // smooth: true, // areaStyle: {}, name: '对风', data: [{ value: chart.radar, }], }] : [] }) chartId++ } if (actChartName.value === 'lineChartCop') { actCopList.value.push({ id: chartId, isBrush: false, actCop: shallowRef(lineChartCop), title: chart.wtId, subtext: '对风偏差分析图', xAxis: linexAxis.value, yAxis: lineyAxis.value, dataset: [{ source: chart.scatter.map(o => { return [o.x + '', o.y] }) }], isRadar: false, series: [{ name: "对风频次", type: "line", symbol: "line", //设定为实心点 symbolSize: 0, //设定实心点的大小 smooth: true, //这个是把线变成曲线 data: chart.count, yAxisIndex: 1, }, { type: 'effectScatter', showEffectOn: "emphasis", rippleEffect: { scale: 1 }, legendHoverLink: false, name: '数据散点', symbolSize: 5, datasetIndex: 0, encode: { x: 'x', y: 'y' }, yAxisIndex: 0, } ] }) chartId++ } if (actChartName.value === 'scatterSingleChartCop') { actCopList.value.push({ id: chartId, isBrush: false, actCop: shallowRef(scatterSingleChartCop), title: chart.wt, subtext: '静态偏航对风分析图', xAxis: scatterxData.value, yAxis: scatteryData.value, isRadar: false, series: [{ name: '对风偏航', type: 'scatter', symbolSize: function (val) { return val[2]; }, markLine: { symbol: 'none', label: { show: false, }, lineStyle: { color: '#F72C5B', width: '3' }, data: [{ name: `平均偏航:${chart.frequency.avg}度`, xAxis: `${chart.frequency.avg}`, }] }, data: chart.frequency.data.length ? chart.frequency.data .map((item) => { return [item[1] + '', item[0] + '', (item[2] * 15).toFixed(1)]; }) : [], animationDelay: function (idx) { return idx * 5; } }] }) chartId++ } } actCopListBak.value = actCopList.value } } } } const funDiaExport = () => { exportLoading.value = true tools.scrollToPDF(diaPanelRef.value, actDiaTitle.value, () => { exportLoading.value = false }) } const funDbClick = (obj) => { if (actCopListBak.value.length > 1) { //判断大于1时, 才有双击放大功能 if (actCopList.value.length === 1) { actCopList.value = actCopListBak.value } else { actCopList.value = [obj] } } } /**created */ // funGetTree() const theme = ref(null) const echartsTheme = ref('') const store = useStore() watch(() => store.state.theme, (newVal, oldVal) => { theme.value = newVal echartsTheme.value = !newVal ? 'dark' : '' funGetTree() }, { deep: true }) /**activated */ onMounted(() => { funGetTree() //test // funSubmit() // theme.value = store.state.theme echartsTheme.value = !theme.value ? 'dark' : '' tableHeight.value = window.innerHeight - 125 + 'px' excelHeight.value = (window.innerHeight - 125) + 'px' treeHeight.value = (window.innerHeight - 125) + 'px' window.addEventListener('resize', () => { tableHeight.value = window.innerHeight - 125 + 'px' excelHeight.value = (window.innerHeight - 125) + 'px' treeHeight.value = (window.innerHeight - 125) + 'px' }) }) </script> <style lang="less"> .rateAnalysis { height: 100%; .rateAnalysisMain { height: 100%; .main_top { height: 40px; display: flex; align-items: center; .topPsty { position: relative; top: 5px; padding: 7px 20px; font-size: 12px; font-weight: 600; margin-left: 10px; border-radius: 3px; } } .main { display: flex; justify-content: space-between; // width: calc(100% - 40px); width: 100%; .treeDataMain, .excelDataMain, .tableDataMain { padding: 10px; border-radius: 10px; } .treeDataMain { width: calc(20% - 20px); } .excelDataMain { width: calc(13% - 20px); .excelDataMain_top { padding: 5px 0; } .excelDataMain_bot { padding: 5px 0; } } .tableDataMain { width: calc(66% - 20px); .chartRowTop { display: flex; justify-content: space-between; } } } } } .themeDark { .rateAnalysisMain { .main_top { .topPsty { color: #1C99FF; background: #1E2126; } } .main { background: #13171e; .treeDataMain { background: transparent; } .excelDataMain { background: #313233; } .tableDataMain { margin-top: 5px; background: #212223; } } } } .themeLight { padding: 0; .rateAnalysisMain { .main_top { .topPsty { color: #2778FF; background: #FFFFFF; } } .main { background: #E6E8F2; .treeDataMain { background: transparent; } .excelDataMain { background: #F4F6FB; } .tableDataMain { background: #fff; margin-top: 5px; } } } } </style>