From e295922209fb87c6dcd68ea1560fd16c3e6d808c Mon Sep 17 00:00:00 2001 From: dongyukun <1208714201@qq.com> Date: 星期五, 27 六月 2025 09:36:51 +0800 Subject: [PATCH] Merge remote-tracking branch 'origin/feature/ai' --- src/views/model/pre/analysis/index.vue | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 455 insertions(+), 22 deletions(-) diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue index fd7b296..945e674 100644 --- a/src/views/model/pre/analysis/index.vue +++ b/src/views/model/pre/analysis/index.vue @@ -58,13 +58,18 @@ <div class="his-body"> <div class="his-body-left"> <div class="his-body-tree"> + <el-input + v-model="filterText" + class="mb-2" + placeholder="Filter" + /> <el-tree :data="treeData" show-checkbox node-key="id" - ref="tree" + ref="treeRef" highlight-current - :props="defaultProps" + :filter-node-method="filterNode" @check="onCheckTree"/> </div> </div> @@ -99,7 +104,7 @@ :min="1"/> </el-form-item> </el-col> - <el-col :span="4"> + <el-col :span="6"> <el-form-item> <el-button size="small" type="primary" plain :loading="loading2" @click="calAccuracyRate">计算精准度 @@ -133,6 +138,11 @@ {{ calRateForm.preCumulant }} </el-form-item> </el-col> + <el-col :span="4"> + <el-form-item label="平均绝对误差:" label-width="110px"> + {{ calRateForm.deviation }} + </el-form-item> + </el-col> </el-row> <el-row> <el-col :span="4"> @@ -160,11 +170,16 @@ {{ calRateForm.realCumulant }} </el-form-item> </el-col> + <el-col :span="4"> + <el-form-item label="累积量平均绝对误差:" label-width="152px"> + {{ calRateForm.deviationCumulant }} + </el-form-item> + </el-col> </el-row> </el-form> <el-form :inline="true" :model="formData" label-width="100px"> <el-row> - <el-col :span="16"> + <el-col :span="18"> <el-form-item label="数据类型"> <el-checkbox-group v-model="formData.chartCheck" @change="changeChartCheck"> <el-checkbox v-for="item in formData.chartOptions" :label="item" @@ -185,13 +200,43 @@ </el-col> </el-row> </el-form> - <div ref="dataAnalysisChart" style="height: 500px;"></div> + <div style="width: 100%;height: 700px;display: flex;flex-direction: row;"> + <div style="height: 100%;width: 80%"> + <div ref="dataAnalysisChart" style="height: 50%;width: 100%"></div> + <div ref="influenceFactorChart" style="height: 50%;width: 100%"></div> + </div> + <div style="width: 20%;height: 100%;"> + <div style="display: flex;flex-direction: row;align-items: center;margin-bottom: 4px;"> + <div style="font-weight: bold;font-size: 14px">影响因素:</div> + <div style="width: calc(100% - 80px);"> + <el-select v-model="influenceFactor" placeholder="请选择" size="small" @change="changeInfluenceFactor"> + <el-option + v-for="(influenceFactor,index) in influenceFactorList" + :key="index" + :label="influenceFactor.factorOutputName" + :value="influenceFactor.factorOutputId" + /> + </el-select> + </div> + </div> + <div class="chart-foot-table p-2" style="width: 100%;height: calc(100% - 20px);overflow-x: hidden;overflow-y: auto;"> + <div style="display: flex;flex-direction: column;align-items: center;margin-bottom: 4px;"> + <span>影响时间</span> + <span style="font-size: 16px;font-weight: bold">{{influenceFactorResultTime}}</span> + </div> + <div v-for="(result, index) in influenceFactorResult" :key="index" style="display: flex;flex-direction: row"> + <span>{{result.factorOutputName}}:</span> + <span>{{result.value}}</span> + </div> + </div> + </div> + </div> <div class="chart-foot"> <div class="chart-foot-content"> <h3 class="chart-foot-title">预警信息</h3> <div class="chart-foot-table"> <el-table :data="alarmList" style="width: 100%" v-loading="loadingAlarm" height="100px"> - <el-table-column prop="content" header-align="center" align="left" label="消息内容" min-width="200" /> + <el-table-column prop="content" header-align="center" align="left" label="消息内容" min-width="240" /> <el-table-column prop="alarmType" label="预警类型" header-align="center" align="left" min-width="150"/> <el-table-column prop="alarmTime" label="预警时间" header-align="center" align="left" min-width="150"/> </el-table> @@ -234,18 +279,20 @@ </el-card> </template> <script lang="ts" setup> -import {getYMDHMS} from "@/utils/dateUtil" +import {getYMDHMS,formatToDateTime} from "@/utils/dateUtil" import * as McsApi from '@/api/model/mcs' +import * as influenceFactorApi from '@/api/model/pre/influenceFactor/influenceFactorApi' import * as AlarmMessageApi from '@/api/model/pre/alarm/message' import * as ScheSuggestApi from '@/api/model/sche/suggest' import * as echarts from "echarts"; import {Search, DArrowLeft, DArrowRight, VideoPlay, VideoPause, CaretLeft, CaretRight} from '@element-plus/icons-vue' +import {lighten} from "@/utils/color"; defineOptions({name: 'AnalysisformData'}) const message = useMessage() // 消息弹窗 const {t} = useI18n() // 国际化 -const dataAnalysisChart = ref(null); + const loading1 = ref(false) // 列表的加载中 const loading2 = ref(false) // 列表的加载中 const total = ref(0) // 列表的总页数 @@ -265,8 +312,8 @@ currentStamp60: '', predictStamp: '', chartCheck: ['T+L', '真实值'], - chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值', '预测累计', '真实累计'], - checkedItemData: [], + chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'], + checkedItemData: undefined, backItem: '', backValue: 0, backCoe: 1, @@ -291,7 +338,9 @@ itemPreMax: 0, itemPreMin: 0, preCumulant: 0, - realCumulant: 0 + realCumulant: 0, + deviation: 0, //平均绝对误差 + deviationCumulant: 0, //累积量平均绝对误差 }) let itemData = ref({ currentTreeList: [], @@ -301,18 +350,36 @@ const treeData = ref([]) const itemDataObject = ref() const timer = ref() -let myChart = null; const isPlay = ref(false) const alarmList = ref([]) const suggestList = ref([]) const loadingAlarm = ref(false) const loadingAdjust = ref(false) +// 影响因素结果列表 +const influenceFactorResultList = ref([]) +// 影响因素列表 +const influenceFactorList = ref([]) +// 选中影响因素 +const influenceFactor = ref() + const formRules = reactive({ - calItem: [{required: true, message: '预测项不能为空', trigger: 'blur'}], IN_DEVIATION: [{required: true, message: '精准度偏差不能为空', trigger: 'blur'}], OUT_DEVIATION: [{required: true, message: '不可信率偏差不能为空', trigger: 'blur'}], }) + +// 树形过滤 +const filterText = ref('') +const treeRef = ref() +watch(filterText, (val) => { + treeRef.value!.filter(val) +}) +const filterNode = (value: string, data) => { + if (!value) return true + return data.label.includes(value) +} + +let xAxisData = [] /** 查询列表 */ const getList = async (isClear = true) => { @@ -340,6 +407,7 @@ formData.value.predictTime = data.predictTime; formData.value.startTime = data.startTime formData.value.endTime = data.endTime + const paramsAlarm = reactive({ outIds: outIds, @@ -508,6 +576,23 @@ } }); } + if (chartCheckArray.indexOf('T+L(未调整)') !== -1) { + let legendName = dataView.resultName + '(T+L(未调整))'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.preDataLOriginal + || [], + type: 'line', + showSymbol: false, + connectNulls: true, + yAxisIndex: yAxisIndex, + smooth: false, + lineStyle: { + width: 2 + } + }); + } if (chartCheckArray.indexOf('当时') !== -1) { let legendName = dataView.resultName + '(当时)'; legendData.push(legendName); @@ -596,7 +681,6 @@ } } - myChart = echarts.init(dataAnalysisChart.value); let option = { title: { text: '' @@ -617,8 +701,8 @@ }, grid: { top: '20%', - left: '3%', - right: '6%', + left: '5%', + right: '5%', bottom: '3%', containLabel: true }, @@ -644,7 +728,12 @@ if (isClear) { myChart.clear() } + myChart.setOption(option) + + + + } finally { loading1.value = false } @@ -652,10 +741,302 @@ calItemBaseVale() } +// 查询影响因素chart +const getInfluenceFactorChart = async (outId) => { + loading1.value = true + try { + + let outIds = [outId] + const params = reactive({ + outIds: outIds, + predictTime: formData.value.predictTime, + startTime: formData.value.startTime, + endTime: formData.value.endTime + }) + + const data = await McsApi.getPreDataCharts(params) + + if (!data?.dataViewList || data.dataViewList.length === 0) { + myInfluenceFactorChart.clear() + return + } + + xAxisData = data.categories; + let defaultYAxis = [ + { + type: 'value', + name: "累计值", + splitLine: {show: false}, + axisLine: {show: true}, + position: 'right' + }, + { + type: 'value', + name: "", + splitLine: {show: false}, + axisLine: {show: true}, + position: 'left' + } + ]; + let yAxisData = []; + let offset = 0; + let yAxisIndex = 0; + let legendData = []; + let yMaxArr = []; + let seriesData = []; + seriesData.push({ + name: '', + data: [null], + type: 'line', + smooth: true, + color: 'green', + markLine: { + silent: true, + lineStyle: { + color: '#32a487', + width: 2 + }, + data: [{ + xAxis: formData.value.predictTime + }], + label: { + normal: { + formatter: formData.value.predictTime + } + }, + symbol: ['circle', 'none'], + }, + }); + yAxisData.push({ + type: 'value', + name: "累计值", + position: 'right', + splitLine: { + show: false + }, + axisLine: { + show: true, + lineStyle: {} + }, + axisLabel: { + formatter: '{value}' + } + }) + for (let i = 0; i < data.dataViewList.length; i++) { + let dataView = data.dataViewList[i] + let maxValue = dataView.maxValue; + let minValue = dataView.minValue; + yAxisIndex = (formData.value.isMultipleY ? i : 0) + 1; + let yMax = maxValue; + if (maxValue < 0) { + maxValue = 1; + } else if (maxValue < 10) { + yMax = (Math.ceil(maxValue * 11) / 10).toFixed(1); + } else if (maxValue < 100) { + yMax = (Math.ceil(maxValue * 1.1 / 5) * 5); + } else { + yMax = (Math.ceil(maxValue * 1.1 / 10) * 10); + } + yMaxArr.push(yMax); + let yMin = minValue; + if (minValue >= 0) { + yMin = 0; + } else if (minValue > -10) { + yMin = (Math.floor(minValue * 11) / 10).toFixed(1); + } else if (minValue > -100) { + yMin = (Math.floor(minValue * 1.1 / 5) * 5); + } else { + yMin = (Math.floor(minValue * 1.1 / 10) * 10); + } + yAxisData.push({ + type: 'value', + name: "", + min: yMin, + max: yMax, + position: 'left', + offset: offset, + splitLine: { + show: false + }, + axisLine: { + show: true, + lineStyle: {} + }, + axisLabel: { + formatter: '{value}' + } + }) + offset = offset + 40 + //真实值 + legendData.push(dataView.resultName + '(真实)'); + seriesData.push({ + name: dataView.resultName + '(真实)', + data: dataView.realData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + smooth: false, + lineStyle: { + width: 2 + } + }); + //T+L + legendData.push(dataView.resultName + '(T+L)'); + seriesData.push({ + name: dataView.resultName + '(T+L)', + data: dataView.preDataL || [], + type: 'line', + showSymbol: false, + connectNulls: true, + yAxisIndex: yAxisIndex, + smooth: false, + lineStyle: { + width: 2 + } + }); + // 当时 + legendData.push(dataView.resultName + '(当时)'); + seriesData.push({ + name: dataView.resultName + '(当时)', + data: dataView.curData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: true, + smooth: false, + lineStyle: { + width: 3 + } + }); + //预测累计 + legendData.push(dataView.resultName + '(预测累计)'); + seriesData.push({ + name: dataView.resultName + '(预测累计)', + data: dataView.cumulantPreData || [], + type: 'line', + yAxisIndex: 0, + showSymbol: false, + connectNulls: true, + smooth: false, + lineStyle: { + width: 2, + type: 'dashed' + } + }); + // 真实累计 + legendData.push(dataView.resultName + '(真实累计)'); + seriesData.push({ + name: dataView.resultName + '(真实累计)', + data: dataView.cumulantRealData || [], + type: 'line', + yAxisIndex: 0, + showSymbol: false, + connectNulls: true, + smooth: false, + lineStyle: { + width: 2, + type: 'dashed' + } + }); + } + //如果最大值相差不大,改成一致大小 + if (yMaxArr.length > 1) { + let max = Math.max.apply(null, yMaxArr); + let min = Math.min.apply(null, yMaxArr); + if (Math.abs((max - min) / max) <= 0.2) { + for (let i = 0; i < yAxisData.length; i++) { + yAxisData[i].max = max; + } + } + } + + let option = { + title: { + text: '' + }, + tooltip: { + trigger: 'axis' + }, + toolbox: { + show: true, + feature: { + saveAsImage: {} + } + }, + legend: { + show: true, + data: legendData, + top: 10 + }, + grid: { + top: '20%', + left: '5%', + right: '5%', + bottom: '3%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xAxisData + }, + yAxis: formData.value.isMultipleY ? yAxisData : defaultYAxis, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100 + }, + { + start: 0, + end: 10 + } + ], + series: seriesData + } + myInfluenceFactorChart.clear() + myInfluenceFactorChart.setOption(option) + } finally { + loading1.value = false + } +} + + + onMounted(() => { + initChart() resetForm() getPreItemTree() }) + +const dataAnalysisChart = ref(); +const influenceFactorChart = ref(); +let myChart = ref({}); +let myInfluenceFactorChart = ref({}); + +function initChart() { + myChart = echarts.init(dataAnalysisChart.value) + myInfluenceFactorChart = echarts.init(influenceFactorChart.value) + // 监听点击事件 + myChart.getZr().on('click', 'series.line',function (params) { + var pointInPixel = [params.offsetX, params.offsetY]; + var pointInData = myChart.convertFromPixel('grid', pointInPixel); + const time = xAxisData[pointInData[0]]; + changeInfluenceFactorTime(time) + }); +} + +let influenceFactorResult = ref([]) +let influenceFactorResultTime = ref('') +// 影响因素时间改变 +function changeInfluenceFactorTime(time) { + if (time && new Date(time)?.getTime()) { + influenceFactorResultTime.value = time + influenceFactorResult.value = influenceFactorResultList.value?.[calRateForm.value.calItem]?.filter(e => e.time === new Date(time).getTime()).sort((a, b) => b.value - a.value) || []; + } +}// 选择影响因素 +function changeInfluenceFactor(value) { + getInfluenceFactorChart(value) +} async function getPreItemTree() { treeData.value = await McsApi.getPredictItemTree() @@ -740,7 +1121,7 @@ if (checked.checkedNodes) { let cns = [...checked.checkedNodes] for (let i = 0; i < cns.length; i++) { - if (cns[i].id.indexOf('-') !== -1) { + if (cns[i].disabled) { continue } formData.value.checkedItemData.push(cns[i]) @@ -760,7 +1141,7 @@ }, wait) } -function calItemBaseVale() { +const calItemBaseVale = async () => { if (!calRateForm.value.calItem) { calRateForm.value.itemPreMax = 0; calRateForm.value.itemPreMin = 0; @@ -770,6 +1151,9 @@ calRateForm.value.itemMin = 0; calRateForm.value.itemAvg = 0; calRateForm.value.realCumulant = 0; + + // 影响因素 + influenceFactorList.value = [] } else { let dataView = itemDataObject.value[calRateForm.value.calItem] calRateForm.value.itemPreMax = dataView.preMax; @@ -780,8 +1164,35 @@ calRateForm.value.itemMin = dataView.hisMin; calRateForm.value.itemAvg = dataView.hisAvg; calRateForm.value.realCumulant = dataView.hisCumulant; + calDeviation(dataView.realData,dataView.preDataL,'deviation') + calDeviation(dataView.cumulantRealData,dataView.cumulantPreData,'deviationCumulant') + calAccuracyRate() + + // 获取影响因素结果列表 + const outPutId = calRateForm.value.calItem + influenceFactorResultList.value = await influenceFactorApi.getResultList({ + outIds: [outPutId], + startTime: formData.value.startTime, + endTime: formData.value.endTime + }) + + // 默认影响时间 + changeInfluenceFactorTime(formData.value.predictTime); + + // 获取影响因素列表 + influenceFactorList.value = await influenceFactorApi.getListByOutId(outPutId) + if (influenceFactorList.value && influenceFactorList.value.length > 0) { + // 根据factorOutputId去重,因为不同的统计规则会有重复的影响因素 + influenceFactorList.value = Array.from(new Map(influenceFactorList.value.map(item => [item.factorOutputId, item])).values()); + // 默认选中第一个影响因素 + influenceFactor.value = influenceFactorList.value?.[0]?.factorOutputId + getInfluenceFactorChart(influenceFactorList.value?.[0]?.factorOutputId) + }else { + // 清除历史 + influenceFactor.value = undefined + myInfluenceFactorChart.clear() + } } - calAccuracyRate() } function calAccuracyRate() { @@ -836,6 +1247,24 @@ loading2.value = false; } +function calDeviation(realData,preDataL,key) { + if (realData == null || preDataL == null || realData.length === 0 || preDataL.length === 0) { + return; + } + const realObj = {} + realData.map(e => realObj[e[0]] = e[1]) + + let sum = 0; + let index = 0; + preDataL.forEach(e => { + if (realObj[e[0]] != undefined) { + sum += Math.abs(e[1] - realObj[e[0]]) + index++ + } + }) + calRateForm.value[key] = Number((sum / index).toFixed(2)) +} + function rightSearchDataByRange() { let mins = getRangeMins(); let startTime = formData.value.startTime; @@ -873,8 +1302,8 @@ currentStamp60: '', predictStamp: '', chartCheck: ['T+L', '真实值'], - chartOptions: ['T+N', 'T+L', '当时', '真实值', '调整值', '预测累计', '真实累计'], - checkedItemData: [], + chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'], + checkedItemData: undefined, backItem: '', backValue: 0, backCoe: 1, @@ -898,7 +1327,9 @@ itemPreMax: 0, itemPreMin: 0, preCumulant: 0, - realCumulant: 0 + realCumulant: 0, + deviation: 0, //平均绝对误差 + deviationCumulant: 0, //累积量平均绝对误差 } calRateFormRef.value?.resetFields() } @@ -929,6 +1360,8 @@ height: 100%; border: 1px solid lightgray; padding: 10px; + overflow-y: auto; + overflow-x: hidden; } .his-body-tree { -- Gitblit v1.9.3