<template> <div class="dataAnalysisCom" :class="!theme ? 'themeDark' : 'themeLight'"> <div class="dataAnalysisComMain"> <div class="main_top"> <p class="topPsty">功率曲线拟合分析</p> <search-cop @submit="funSubmit"> </search-cop> </div> <div class="main"> <div class="treeDataMain"> <tree-cop :data="treeData" @checkChange="funTreeCheckChange" :show-checkbox="true" :height="treeHeight" @currentChange="funCurrentChange" @refresh="funGetTree"> </tree-cop> <tree-cop :dropdownMenu="['export','delete','save']" :data="processTreeData" :height="treeHeight" @currentChange="funProcessCurrentChange" @refresh="funGetProcessTree"> </tree-cop> </div> <div class="excelDataMain"> <div class="excelDataMain_top"> <excel-cop :checkIds="excelCheckIds" :showCheckbox="excelCheckboxShow" :data="excelList" :theme="theme" :height="excelHeight" @excelChange="funExcelChange" @checkChange="funExcelCheckChange"> </excel-cop> </div> <div class="excelDataMain_bot"> <excel-cop :data="excelFitList" :height="excelHeight" :theme="theme" @excelChange="funExcelChange"> </excel-cop> </div> </div> <div class="tableDataMain"> <SubmitBtn class="butten_com" desc="区域划分" v-if="activeTab === '2' && excelType === 'fitting'" @click="funChartArea"> </SubmitBtn> <el-tabs v-model="activeTab" @tab-click="handleClick"> <el-tab-pane label="表格数据" name="1"> <table-cop :data="tableData" :column="tableColumn" :theme="theme" :loading="tableLoading" :height="tableHeight" :tableId="tableShowId" :tableName="tableName"> </table-cop> </el-tab-pane> <el-tab-pane label="图表展示" name="2"> <div :style="{ height: typeof tableHeight === 'string' ? tableHeight : tableHeight + 'px' }"> <CurrentScatterChart ref="chartRef" width="100%" :height="`calc( ${tableHeight} - 20px )`" :chartTitle="avgObj.title+ ' ' +'平均Cp值:'+avgObj.cpavg+'; 静风频率:'+avgObj.frequency+'%; 曲线偏差率:'+avgObj.pcratio+'%'" :xAxisData="xAxisData" :yAxisData="{ splitLine: { show: false } }" :seriesData="seriesData" :showLegend="true" :brushSelected="!isChartArea" :theme="theme" :echartsTheme="echartsTheme" :dataSet="dataSet" @getSelected="funChartSelect" /> </div> </el-tab-pane> </el-tabs> </div> </div> </div> <el-dialog v-model="wtDialog" draggable title="风机功率点位"> <el-tabs v-model="wtTab" @tab-click="handleClick"> <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"> <el-table :data="faultData" row-key="id" :max-height="550"> <el-table-column property="deviceId" align="center" label="风机" /> <el-table-column property="ts" sortable :width="160" align="center" label="时间"> <template v-slot="scope"> {{new Date(scope.row.ts).formatDate("yyyy-MM-dd hh:mm:ss")}} </template> </el-table-column> <el-table-column property="characteristic" sortable align="center" label="特性" /> <el-table-column property="components" sortable align="center" label="部件" /> <el-table-column property="description" sortable align="center" label="描述" /> </el-table> </el-tab-pane> <el-tab-pane label="预警" name="warning"> <el-table :data="warnData" row-key="id" :max-height="550"> <el-table-column property="deviceId" align="center" label="风机" /> <el-table-column property="ts" sortable :width="160" align="center" label="时间"> <template v-slot="scope"> {{new Date(scope.row.ts).formatDate("yyyy-MM-dd hh:mm:ss")}} </template> </el-table-column> <el-table-column property="characteristic" sortable align="center" label="特性" /> <el-table-column property="components" sortable align="center" label="部件" /> <el-table-column property="description" sortable align="center" label="描述" /> </el-table> </el-tab-pane> </el-tabs> </el-dialog> </div> </template> <script setup name="prepare"> import searchCop from './components/search.vue' import SubmitBtn from '@/components/generatingCapacityComponent/SubmitBtn.vue' import excelCop from '@/components/generatingCapacityComponent/excel.vue' import treeCop from '@/components/generatingCapacityComponent/tree.vue' import tableCop from '@/components/generatingCapacityComponent/table.vue' import { ref, nextTick, onActivated, onMounted, reactive, watch } from 'vue' import { useStore } from 'vuex'; import httpRequest from '@/utils/request.js' import { ElMessage } from 'element-plus' import util from "@tools/util"; import CurrentScatterChart from './components/current-scatter-chart.vue' // import dotRes from '@/data/dot.json' // import tableRes from '@/data/table.json' // import areaDataRes from '@/data/areaData.json' /**配置参数 */ const tableHeight = ref(window.innerHeight - 170 + 'px') const excelHeight = ref((window.innerHeight - 131) / 2 + 'px') const treeHeight = ref((window.innerHeight - 131) / 2 + 'px') /**excel 开始 */ const excelCheckboxShow = ref(false) const excelType = ref('') const excelCheckIds = ref([]) const excelList = ref([]) const funExcelChange = async (obj) => { //点击excel项时 activeTab.value = '1' isChartArea.value = false tableShowId.value = obj.id tableName.value = obj.name excelType.value = obj.type // 接收excel的type 用于控制右侧tab展示 let res = null let chartRes = { scatterhs: [ [] ], scatterls: [ [] ], sjgl: [ [] ], llgl: [ [] ], cpz: [ [] ] } let chartResponse = null tableLoading.value = true if (obj.type === 'process') { res = await httpRequest.get('/power/process/show', { params: { id: obj.id } }) } else if (obj.type === 'fitting') { activeTab.value = '2' res = await httpRequest.get('/power/fitting/show', { params: { id: obj.id } }) // res = tableRes // chartResponse = dotRes chartResponse = await httpRequest.get('/power/fitting/curve', { params: { id: obj.id, p: 1 } }) } if (res.code === 200) { tableColumn.value = res.data.title.map(o => { return { prop: o.key, width: o.des === '时间' ? 100 : 80, label: o.des, } }) tableData.value = res.data.data tableLoading.value = false } else { tableLoading.value = false } if (chartResponse && chartResponse.code === 200) { chartRes = chartResponse.data markDot.pcl5 = chartRes.obj.pc5ratio markDot.pcl10 = chartRes.obj.pc10ratio markDot.pcl12 = chartRes.obj.pc12ratio markDot.pcl25 = chartRes.obj.pc25ratio avgObj.title = chartRes.obj.path.substring(chartRes.obj.path.indexOf(chartRes.obj.station + '_') + ( chartRes.obj.station + '_').length).split('_')[0]; avgObj.cpavg = Number(chartRes.obj.cpavg).toFixed(2) avgObj.frequency = Number(chartRes.obj.frequency).toFixed(2) avgObj.pcratio = Number(chartRes.obj.pcratio).toFixed(2) dataSet.value = JSON.stringify([{ source: chartRes.wyd // source: chartRes.scatterls }, { source: chartRes.yyd // source: chartRes.scatterhs } ]) const color = ["#1C99FF", "#FF8700", "#3D54BE", "#fa8c16", "#1DA0D7", "#DD5044"] seriesData.value = [{ name: "拟合功率", type: "line", symbol: "line", //设定为实心点 symbolSize: 0, //设定实心点的大小 smooth: true, //这个是把线变成曲线 data: chartRes.sjgl, xAxisIndex: 0, }, { name: "保证功率", type: "line", symbol: "line", //设定为实心点 symbolSize: 0, //设定实心点的大小 smooth: true, //这个是把线变成曲线 data: chartRes.llgl, xAxisIndex: 0, }, { type: 'effectScatter', showEffectOn: "emphasis", rippleEffect: { scale: 1 }, name: '无用点', symbolSize: (data) => { return data.s ? data.s > 10 ? 10 : data.s : 4 }, datasetIndex: 0, encode: { x: 'x', y: 'y' }, xAxisIndex: 0, yAxisIndex: 0, }, { type: 'effectScatter', showEffectOn: "emphasis", rippleEffect: { scale: 1 }, name: '有用点', symbolSize: (data) => { return data.s ? data.s > 10 ? 10 : data.s : 4 }, datasetIndex: 1, encode: { x: 'x', y: 'y' }, xAxisIndex: 0, yAxisIndex: 0, }, { name: "Cp值", type: "line", symbol: "line", //设定为实心点 symbolSize: 0, //设定实心点的大小 smooth: true, //这个是把线变成曲线 data: chartRes.cpz, xAxisIndex: 0, yAxisIndex: 1, }, ] } } const funExcelCheckChange = ({ checkArr, data }) => { //bug excelCheckIds.value = checkArr } /**excel fitData */ const excelFitList = ref([]) /**prepare tree 开始 */ const treeData = ref([]) const actTreeNode = ref(null) //当前激活的treeNode const funRepeatMap = (arr, type = 'prepare') => { 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 && type === 'process') { //判断当且仅有process获取tree时 赋值 actTreeNode.value = o } } } return { ...o, children: o.children ? funRepeatMap(o.children, type) : [] } }) } const funGetTree = async () => { const res = await httpRequest.get("/power/process/tree") treeData.value = funRepeatMap(res.data) excelList.value = [] } const funCurrentChange = ({ current, currentNode }) => { excelCheckboxShow.value = true 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 = [] } } const funTreeCheckChange = ({ current, checkedNodes, checkedKeys, halfCheckedNodes, halfCheckedKeys }) => { //tree change -> excel change funCurrentChange({ current, currentNode: '' }) const checkIds = [] if (checkedNodes.checkedNodes.length) { let checkArr = checkedNodes.checkedNodes checkArr.forEach(it => { if (it.childs && it.childs.length) { it.childs.forEach(iv => { checkIds.push(iv.id) }) } }) } excelCheckIds.value = checkIds } /**process tree 开始 */ const processTreeData = ref([]) const funGetProcessTree = async (flag = true) => { //flag控制是否获取tree的第一项 true为可获取 actTreeNode.value = null const res = await httpRequest.get("/power/fitting/tree") excelFitList.value = [] processTreeData.value = funRepeatMap(res.data, flag ? 'process' : 'prepare') //flag控制对actTreeNode赋值 if (actTreeNode.value) { funProcessCurrentChange({ current: actTreeNode.value, currentNode: null }) const child = actTreeNode.value.childs[0] const obj = { id: child.id, interval: child.interval, path: child.path, prepareid: child.prepareid, station: child.station, time: child.time, type: child.type, windturbine: child.windturbine, name: child.path.substring(child.path.indexOf(child.station + '_') + (child.station + '_') .length) } funExcelChange(obj) } } const funProcessCurrentChange = ({ current, currentNode }) => { if (current.childs) { excelFitList.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) } }) if (excelFitList.value.length > 0) { funExcelChange(excelFitList.value[0]) } } else { excelFitList.value = [] } } /**table 开始 */ const tableShowId = ref('') const tableColumn = ref([]) const tableLoading = ref(false) const tableName = ref('') const tableData = ref([]) /**table 结束 */ /**search 开始 */ const funSubmit = async (query) => { if (!excelCheckIds.value.length) { ElMessage.error('请勾选要预处理的项') return false } const params = { ...query, ids: excelCheckIds.value.join(',') } const res = await httpRequest.get('/power/fitting/data', { params: params }) if (res.code === 200) { ElMessage.success(res.msg) funGetProcessTree(false) //阻止获取tree第一项数据及图表 const excelInfo = res.data /**拟合完成后 显示右侧图表及数据 */ funExcelChange({ id: excelInfo.id, name: excelInfo.path.substring(excelInfo.path.indexOf(excelInfo.station + '_') + ( excelInfo.station + '_').length), type: 'fitting' }) } } /**chart Data */ const avgObj = reactive({ //平均cpz等 title: '', cpavg: '', frequency: '', pcratio: '' }) const markDot = reactive({ //3-5 point点等 pcl5: null, pcl10: null, pcl12: null, pcl25: null }) const xAxisData = ref([]) const chartRef = ref() //chart 的ref const seriesData = ref([]) const isChartArea = ref(false) // 用来控制图表是否区域划分 const dataSet = ref('') const funChartSelect = async (batch) => { const wDataArr = [] const yDataArr = [] let scatterls = [] let scatterhs = [] let dataSetObj = [] wtData.value = [] if (batch.length && dataSet.value) { scatterls = batch[0].selected[2].dataIndex scatterhs = batch[0].selected[3].dataIndex if (scatterls.length || scatterhs.length) { dataSetObj = JSON.parse(dataSet.value) if (scatterls.length) { for (const scatterIndex of scatterls) { wDataArr.push(dataSetObj[0].source[scatterIndex].k) } } if (scatterhs.length) { for (const scatterIndex of scatterhs) { yDataArr.push(dataSetObj[1].source[scatterIndex].k) } } const wtRes = await httpRequest.get('/power/fitting/filter', { params: { yk: yDataArr.join(','), wk: wDataArr.join(','), only: dataRadom.value } }) 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 }) } } } } } const funChartArea = () => { if (seriesData.value.length) { if (!isChartArea.value) { // 请求一下 seriesData.value[0] = { ...seriesData.value[0], markLine: { symbol: 'none', label: { show: false }, lineStyle: { color: 'rgba(96,174,255, 1)' }, data: [{ xAxis: 3, valueIndex: 0, }, { xAxis: 5, valueIndex: 0 }, { xAxis: 10, valueIndex: 0 }, { xAxis: 12, valueIndex: 0 }, { xAxis: 25, valueIndex: 0 }, ] }, markArea: { label: { fontSize: util.vh(12), }, itemStyle: { color: 'rgba(236,245,255, 0)' }, emphasis: { itemStyle: { color: 'rgba(96,174,255, 0.5)' } }, data: [ [{ name: `3~5m 偏差率: ${markDot.pcl5}%`, xAxis: 3, }, { xAxis: 5, } ], [{ name: `5~10m 偏差率: ${markDot.pcl10}%`, xAxis: 5, }, { xAxis: 10, } ], [{ name: `10~12m 偏差率: ${markDot.pcl12}%`, xAxis: 10, }, { xAxis: 12, } ], [{ name: `12~25m 偏差率: ${markDot.pcl25}%`, xAxis: 12, }, { xAxis: 25, } ], ] }, } isChartArea.value = true } else { seriesData.value[0] = { ...seriesData.value[0], markLine: null, markArea: null, } isChartArea.value = false } } } const handleClick = (val) => { wtTab.value = val.props.name if (wtTab.value === 'problem') { faultDataFn() } else if (wtTab.value === 'warning') { warnDataFn() } } //故障数据 const faultDataFn = async () => { let params = { only: dataRadom.value, table: 'alarmWt' } const faultRes = await httpRequest.get('/power/fitting/alarms', { params: params }) faultData.value = faultRes.data } //预警数据 const warnDataFn = async () => { let params = { only: dataRadom.value, table: 'alarmCt' } const warnRes = await httpRequest.get('/power/fitting/alarms', { params: params }) warnData.value = warnRes.data } /**dialog 数据 */ const wtDialog = ref(false) const wtData = ref([]) const faultData = ref([]) const warnData = ref([]) const wtTab = ref('table') // 随机数 const dataRadom = ref(null) /**tab */ const activeTab = ref('1') /**created */ // funGetTree() // funGetProcessTree() const theme = ref(null) const echartsTheme = ref('') const store = useStore() watch(() => store.state.theme, (newVal, oldVal) => { theme.value = newVal echartsTheme.value = !newVal ? 'dark' : '' funGetTree() funGetProcessTree() }, { deep: true }) /**mounted */ onMounted(() => { funGetTree() funGetProcessTree() theme.value = store.state.theme echartsTheme.value = !theme.value ? 'dark' : '' dataRadom.value = (new Date().getTime()).toString() tableHeight.value = window.innerHeight - 170 + 'px' excelHeight.value = (window.innerHeight - 131) / 2 + 'px' treeHeight.value = (window.innerHeight - 131) / 2 + 'px' window.addEventListener('resize', () => { tableHeight.value = window.innerHeight - 170 + 'px' excelHeight.value = (window.innerHeight - 131) / 2 + 'px' treeHeight.value = (window.innerHeight - 131) / 2 + 'px' }) /**test */ // funExcelChange({ // id: 1, // name: 'excel', // type: 'fitting', // }) }) </script> <style lang="less"> .dataAnalysisCom { height: 100%; .dataAnalysisComMain { 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; width: 100%; .treeDataMain, .excelDataMain, .tableDataMain { border-radius: 10px; } .treeDataMain { margin-right: 10px; padding: 10px 0 10px 10px; width: calc(19% - 20px); } .excelDataMain { margin-right: 10px; padding: 10px 0 10px 10px; width: calc(15% - 20px); .excelDataMain_top { padding: 5px 0; } .excelDataMain_bot { padding: 5px 0; } } .tableDataMain { padding: 10px; width: calc(66% - 20px); position: relative; .butten_com { position: absolute; right: 20px; z-index: 111111; } } } } } .themeDark { .dataAnalysisComMain { .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; .dataAnalysisComMain { .main_top { .topPsty { color: #2778FF; background: #FFFFFF; } } .main { background: #E6E8F2; .treeDataMain { background: transparent; } .excelDataMain { background: #F4F6FB; } .tableDataMain { background: #fff; margin-top: 5px; } } } } </style>