From 3ee452aa3b7ce1c8313bae8a248520d69676dd99 Mon Sep 17 00:00:00 2001
From: dengzedong <dengzedong@email>
Date: 星期五, 18 四月 2025 18:21:45 +0800
Subject: [PATCH] 数据分析 左侧树单选、添加影响因素

---
 src/views/model/pre/analysis/index.vue |  429 ++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 387 insertions(+), 42 deletions(-)

diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue
index 8425969..0d10885 100644
--- a/src/views/model/pre/analysis/index.vue
+++ b/src/views/model/pre/analysis/index.vue
@@ -69,6 +69,7 @@
                 node-key="id"
                 ref="treeRef"
                 highlight-current
+                check-strictly
                 :filter-node-method="filterNode"
                 @check="onCheckTree"/>
             </div>
@@ -78,33 +79,23 @@
               <el-form :inline="true" :model="calRateForm" :rules="formRules" ref="calRateFormRef"
                        label-width="108px">
                 <el-row>
-                  <el-col :span="6">
-                    <el-form-item label="预测项" prop="calItem" style="width: 90%">
-                      <el-select size="small" v-model="calRateForm.calItem"
-                                 @change="calItemBaseVale"
-                                 placeholder="请选择">
-                        <el-option
-                          v-for="itemOut in formData.checkedItemData"
-                          :key="itemOut.id"
-                          :label="itemOut.label"
-                          :value="itemOut.id"/>
-                      </el-select>
-                    </el-form-item>
-                  </el-col>
-                  <el-col :span="6">
+<!--                  <el-col :span="6" style="display: flex;align-items: center;justify-content: center">-->
+<!--                    <span>预测项:{{formData.checkedItemData?.label || ''}}</span>-->
+<!--                  </el-col>-->
+                  <el-col :span="8">
                     <el-form-item label="精准度偏差" prop="IN_DEVIATION">
                       <el-input-number size="small" v-model="calRateForm.IN_DEVIATION"
                                        controls-position="right" :min="0"/>
                     </el-form-item>
                   </el-col>
-                  <el-col :span="6">
+                  <el-col :span="8">
                     <el-form-item label="不可信率偏差" prop="OUT_DEVIATION">
                       <el-input-number size="small" v-model="calRateForm.OUT_DEVIATION"
                                        controls-position="right"
                                        :min="1"/>
                     </el-form-item>
                   </el-col>
-                  <el-col :span="4">
+                  <el-col :span="8">
                     <el-form-item>
                       <el-button size="small" type="primary" plain :loading="loading2"
                                  @click="calAccuracyRate">计算精准度
@@ -200,7 +191,37 @@
                   </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>
@@ -249,18 +270,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) // 列表的总页数
@@ -281,7 +304,7 @@
   predictStamp: '',
   chartCheck: ['T+L', '真实值'],
   chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'],
-  checkedItemData: [],
+  checkedItemData: undefined,
   backItem: '',
   backValue: 0,
   backCoe: 1,
@@ -318,15 +341,20 @@
 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'}],
 })
@@ -342,6 +370,8 @@
   return data.label.includes(value)
 }
 
+let xAxisData = []
+
 /** 查询列表 */
 const getList = async (isClear = true) => {
   loading1.value = true
@@ -350,13 +380,11 @@
       formData.value.chartCheck = ['真实值']
     }
     let chartCheckArray = formData.value.chartCheck;
-    if (!formData.value.checkedItemData || formData.value.checkedItemData.length == 0) {
+    if (!formData.value.checkedItemData) {
       itemData.value.option = {};
       return;
     }
-    let outIds = formData.value.checkedItemData.map(item => {
-      return item.id
-    })
+    let outIds = [formData.value.checkedItemData.id]
     const params = reactive({
       outIds: outIds,
       predictTime: formData.value.predictTime,
@@ -364,10 +392,23 @@
       endTime: formData.value.endTime
     })
 
+
     const data = await McsApi.getPreDataCharts(params)
     formData.value.predictTime = data.predictTime;
     formData.value.startTime = data.startTime
     formData.value.endTime = data.endTime
+
+    // 获取影响因素结果列表
+    influenceFactorResultList.value = await influenceFactorApi.getResultList({
+      outIds: outIds,
+      startTime: data.startTime,
+      endTime: data.endTime
+    })
+
+    // 获取影响因素结果列表
+    influenceFactorList.value = await influenceFactorApi.getListByOutId(formData.value.checkedItemData.id)
+    // 根据factorOutputId去重,因为不同的统计规则会有重复的影响因素
+    influenceFactorList.value = Array.from(new Map(influenceFactorList.value.map(item => [item.factorOutputId, item])).values());
 
     const paramsAlarm = reactive({
       outIds: outIds,
@@ -382,7 +423,7 @@
     suggestList.value = await ScheSuggestApi.getListByOut(paramsAlarm)
     loadingAdjust.value = false
 
-    let xAxisData = data.categories;
+    xAxisData = data.categories;
     let defaultYAxis = [
       {
         type: 'value',
@@ -641,7 +682,6 @@
       }
     }
 
-    myChart = echarts.init(dataAnalysisChart.value);
     let option = {
       title: {
         text: ''
@@ -662,8 +702,8 @@
       },
       grid: {
         top: '20%',
-        left: '3%',
-        right: '6%',
+        left: '5%',
+        right: '5%',
         bottom: '3%',
         containLabel: true
       },
@@ -689,7 +729,12 @@
     if (isClear) {
       myChart.clear()
     }
+
     myChart.setOption(option)
+
+
+
+
   } finally {
     loading1.value = false
   }
@@ -697,10 +742,303 @@
   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) {
+      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'],
+      },
+    });
+    itemDataObject.value = {}
+    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]
+      itemDataObject.value[dataView.outId] = dataView;
+      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?.[formData.value.checkedItemData?.id]?.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()
@@ -781,16 +1119,21 @@
 }
 
 function onCheckTree(data, checked, indeterminate) {
-  formData.value.checkedItemData = [];
-  if (checked.checkedNodes) {
-    let cns = [...checked.checkedNodes]
-    for (let i = 0; i < cns.length; i++) {
-      if (cns[i].id.indexOf('-') !== -1) {
-        continue
-      }
-      formData.value.checkedItemData.push(cns[i])
-    }
-  }
+  // 单选
+  treeRef.value.setCheckedKeys([])
+  treeRef.value.setCheckedNodes([data])
+
+  formData.value.checkedItemData = data
+  calRateForm.value.calItem = data.id
+  // if (checked.checkedNodes) {
+  //   let cns = [...checked.checkedNodes]
+  //   for (let i = 0; i < cns.length; i++) {
+  //     if (cns[i].id.indexOf('-') !== -1) {
+  //       continue
+  //     }
+  //     formData.value.checkedItemData.push(cns[i])
+  //   }
+  // }
   debounce(getList, 1000);
 }
 
@@ -827,8 +1170,8 @@
     calRateForm.value.realCumulant = dataView.hisCumulant;
     calDeviation(dataView.realData,dataView.preDataL,'deviation')
     calDeviation(dataView.cumulantRealData,dataView.cumulantPreData,'deviationCumulant')
+    calAccuracyRate()
   }
-  calAccuracyRate()
 }
 
 function calAccuracyRate() {
@@ -939,7 +1282,7 @@
     predictStamp: '',
     chartCheck: ['T+L', '真实值'],
     chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'],
-    checkedItemData: [],
+    checkedItemData: undefined,
     backItem: '',
     backValue: 0,
     backCoe: 1,
@@ -996,6 +1339,8 @@
   height: 100%;
   border: 1px solid lightgray;
   padding: 10px;
+  overflow-y: auto;
+  overflow-x: hidden;
 }
 
 .his-body-tree {

--
Gitblit v1.9.3