From 0568fa140511a5df539dbc87759bb2040e7d8b10 Mon Sep 17 00:00:00 2001
From: houzhongjian <houzhongyi@126.com>
Date: 星期二, 06 五月 2025 15:06:52 +0800
Subject: [PATCH] 模型管理数据分析页面增加授权功能,以供第三方嵌入

---
 src/views/model/pre/analysis/index.vue |  900 +++++++++++++++++++++++++++++++++++++++++++++++++++--------
 1 files changed, 771 insertions(+), 129 deletions(-)

diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue
index ef66aba..e377c25 100644
--- a/src/views/model/pre/analysis/index.vue
+++ b/src/views/model/pre/analysis/index.vue
@@ -1,7 +1,7 @@
 <template>
   <el-card shadow="never" class="aui-card--fill">
     <div class="mod-his__index">
-      <el-form :inline="true" :model="formData" label-width="80px">
+      <el-form :inline="true" :model="formData" label-width="70px">
         <el-form-item label="开始时间">
           <el-date-picker
             v-model="formData.startTime"
@@ -33,26 +33,44 @@
         </el-form-item>
         <el-form-item>
           <el-button-group>
-            <el-button type="primary" plain :icon="ArrowLeft"
+            <el-button type="primary" plain :icon="DArrowLeft"
                        :loading="loading1" @click="leftSearchDataByRange()"/>
             <el-button type="primary" plain :icon="Search"
                        :loading="loading1" @click="getList()">查询
             </el-button>
-            <el-button type="primary" plain :icon="ArrowRight"
+            <el-button type="primary" plain :icon="DArrowRight"
                        :loading="loading1" @click="rightSearchDataByRange()"/>
+          </el-button-group>
+        </el-form-item>
+        <el-form-item>
+          <el-button-group>
+            <el-button type="primary" plain :icon="CaretLeft"
+                       @click="playChart(true)"/>
+            <el-button type="primary" plain :icon="VideoPlay" v-if="!isPlay"
+                       @click="playHandle('play')"/>
+            <el-button type="primary" plain :icon="VideoPause" v-if="isPlay"
+                       @click="playHandle('pause')"/>
+            <el-button type="primary" plain :icon="CaretRight"
+                       @click="playChart(false)"/>
           </el-button-group>
         </el-form-item>
 
         <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"
+                check-strictly
+                :filter-node-method="filterNode"
                 @check="onCheckTree"/>
             </div>
           </div>
@@ -61,35 +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="1"
-                                       :max="10"/>
+                                       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"
-                                       :max="20"/>
+                                       :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">计算精准度
@@ -123,6 +129,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">
@@ -150,11 +161,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="12">
+                  <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"
@@ -175,7 +191,77 @@
                   </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="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>
+                  </div>
+                </div>
+                <div class="chart-foot-content">
+                  <h3 class="chart-foot-title">调度建议</h3>
+                  <div class="chart-foot-table">
+                    <el-table :data="suggestList" style="width: 100%" v-loading="loadingAdjust" height="100px">
+                      <el-table-column
+                        prop="scheduleTime"
+                        label="调度时间"
+                        header-align="center"
+                        align="left"
+                        min-width="160"
+                      />
+                      <el-table-column
+                        prop="content"
+                        label="内容"
+                        min-width="300"
+                        header-align="center" align="left"
+                      />
+                      <el-table-column
+                        prop="adjustValue"
+                        label="调整值"
+                        header-align="center"
+                        align="center"
+                        min-width="100"
+                      />
+                    </el-table>
+                  </div>
+
+                </div>
+              </div>
             </div>
           </div>
         </div>
@@ -184,16 +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, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue'
+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) // 列表的总页数
@@ -213,8 +303,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,
@@ -223,13 +313,13 @@
   queryStep: 2,
   isMultipleYRadio: '单坐标轴',
   isMultipleY: false,
-  predictFreq: 3,
+  predictFreq: 2,
 })
 const calRateFormRef = ref()
 const calRateForm = ref({
   calItem: undefined,
-  IN_DEVIATION: 0,
-  OUT_DEVIATION: 0,
+  IN_DEVIATION: 10,
+  OUT_DEVIATION: 50,
   IN_ACCURACY_RATE: 0,
   OUT_ACCURACY_RATE: 0,
   itemAvg: 0,
@@ -239,7 +329,9 @@
   itemPreMax: 0,
   itemPreMin: 0,
   preCumulant: 0,
-  realCumulant: 0
+  realCumulant: 0,
+  deviation: 0, //平均绝对误差
+  deviationCumulant: 0, //累积量平均绝对误差
 })
 let itemData = ref({
   currentTreeList: [],
@@ -249,41 +341,114 @@
 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 () => {
+const getList = async (isClear = true) => {
   loading1.value = true
   try {
     if (!formData.value.chartCheck) {
       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,
       startTime: formData.value.startTime,
       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
 
-    let xAxisData = data.categories;
+    // 默认影响时间
+    changeInfluenceFactorTime(data.predictTime);
+
+    // 获取影响因素结果列表
+    influenceFactorResultList.value = await influenceFactorApi.getResultList({
+      outIds: outIds,
+      startTime: data.startTime,
+      endTime: data.endTime
+    })
+
+    // 获取影响因素结果列表
+    influenceFactorList.value = await influenceFactorApi.getListByOutId(formData.value.checkedItemData.id)
+    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)
+    }
+
+
+    const paramsAlarm = reactive({
+      outIds: outIds,
+      predictTime: formData.value.predictTime
+    })
+
+    loadingAlarm.value = true
+    alarmList.value = await AlarmMessageApi.getListByOut(paramsAlarm)
+    loadingAlarm.value = false
+
+    loadingAdjust.value = true
+    suggestList.value = await ScheSuggestApi.getListByOut(paramsAlarm)
+    loadingAdjust.value = false
+
+    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;
@@ -314,12 +479,27 @@
       },
     });
     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;
+      yAxisIndex = (formData.value.isMultipleY ? i : 0) + 1;
       let yMax = maxValue;
       if (maxValue < 0) {
         maxValue = 1;
@@ -369,7 +549,7 @@
           type: 'line',
           yAxisIndex: yAxisIndex,
           showSymbol: false,
-          smooth: true,
+          smooth: false,
           lineStyle: {
             width: 2
           }
@@ -377,13 +557,14 @@
       }
       if (chartCheckArray.indexOf('T+N') !== -1) {
         let legendName = dataView.resultName + '(T+N)';
+        legendData.push(legendName);
         seriesData.push({
           name: legendName,
           data: dataView.preDataN || [],
           type: 'line',
           yAxisIndex: yAxisIndex,
           showSymbol: false,
-          smooth: true,
+          smooth: false,
           lineStyle: {
             width: 2
           }
@@ -399,7 +580,24 @@
           showSymbol: false,
           connectNulls: true,
           yAxisIndex: yAxisIndex,
-          smooth: true,
+          smooth: false,
+          lineStyle: {
+            width: 2
+          }
+        });
+      }
+      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
           }
@@ -413,10 +611,10 @@
           data: dataView.curData || [],
           type: 'line',
           yAxisIndex: yAxisIndex,
-          showSymbol: false,
-          smooth: true,
+          showSymbol: true,
+          smooth: false,
           lineStyle: {
-            width: 2
+            width: 3
           }
         });
       }
@@ -430,7 +628,51 @@
           yAxisIndex: yAxisIndex,
           showSymbol: false,
           connectNulls: true,
-          smooth: true,
+          smooth: false,
+          lineStyle: {
+            width: 2,
+            type: 'dashed'
+          }
+        });
+      }
+
+      if (chartCheckArray.indexOf('预测累计') !== -1) {
+        let legendName = dataView.resultName + '(预测累计)';
+        legendData.push(legendName);
+        let seriesLeiJiData = []
+        if (dataView.cumulantPreData) {
+          seriesLeiJiData = dataView.cumulantPreData
+        }
+        seriesData.push({
+          name: legendName,
+          data: seriesLeiJiData,
+          type: 'line',
+          yAxisIndex: 0,
+          showSymbol: false,
+          connectNulls: true,
+          smooth: false,
+          lineStyle: {
+            width: 2,
+            type: 'dashed'
+          }
+        });
+      }
+
+      if (chartCheckArray.indexOf('真实累计') !== -1) {
+        let legendName = dataView.resultName + '(真实累计)';
+        legendData.push(legendName);
+        let seriesLeiJiData = []
+        if (dataView.cumulantRealData) {
+          seriesLeiJiData = dataView.cumulantRealData
+        }
+        seriesData.push({
+          name: legendName,
+          data: seriesLeiJiData,
+          type: 'line',
+          yAxisIndex: 0,
+          showSymbol: false,
+          connectNulls: true,
+          smooth: false,
           lineStyle: {
             width: 2,
             type: 'dashed'
@@ -448,13 +690,19 @@
         }
       }
     }
-    myChart = echarts.init(dataAnalysisChart.value);
+
     let option = {
       title: {
         text: ''
       },
       tooltip: {
         trigger: 'axis'
+      },
+      toolbox: {
+        show: true,
+        feature: {
+          saveAsImage: {}
+        }
       },
       legend: {
         show: true,
@@ -463,8 +711,8 @@
       },
       grid: {
         top: '20%',
-        left: '3%',
-        right: '6%',
+        left: '5%',
+        right: '5%',
         bottom: '3%',
         containLabel: true
       },
@@ -473,11 +721,7 @@
         boundaryGap: false,
         data: xAxisData
       },
-      yAxis: formData.value.isMultipleY ? yAxisData : {
-        type: 'value',
-        splitLine: {show: false},
-        axisLine: {show: true}
-      },
+      yAxis: formData.value.isMultipleY ? yAxisData : defaultYAxis,
       dataZoom: [
         {
           type: 'inside',
@@ -491,20 +735,366 @@
       ],
       series: seriesData
     }
-    myChart.clear()
+    if (isClear) {
+      myChart.clear()
+    }
+
     myChart.setOption(option)
+
+
+
+
+  } finally {
+    loading1.value = false
+  }
+
+  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'],
+      },
+    });
+    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()
+}
+
+function changeChartCheck(value) {
+  getList(true)
+}
+
+function onChangeMultipleY() {
+  getList(true)
+}
+
+function playChart(isBack = false) {
+  let mins = isBack ? formData.value.predictFreq * -1 : formData.value.predictFreq
+  let startTime = formData.value.startTime;
+  let endTime = formData.value.endTime;
+  let predictTime = formData.value.predictTime;
+  if (predictTime) {
+    predictTime = getYMDHMS(new Date(predictTime).getTime() + 1000 * 60 * mins);
+    formData.value.predictTime = predictTime;
+  }
+  if (startTime) {
+    startTime = getYMDHMS(new Date(startTime).getTime() + 1000 * 60 * mins);
+    formData.value.startTime = startTime;
+  }
+  if (endTime) {
+    endTime = getYMDHMS(new Date(endTime).getTime() + 1000 * 60 * mins);
+    formData.value.endTime = endTime;
+  }
+  getList(false);
+}
+
+function playHandle(type) {
+  isPlay.value = 'play' === type
+  let doPlay = setInterval(function () {
+    if (isPlay.value) {
+      playChart()
+    } else {
+      clearInterval(doPlay);
+    }
+    if (new Date().getTime() - new Date(formData.value.predictTime).getTime() < 1000 * 60 ) {
+      isPlay.value = false
+      clearInterval(doPlay);
+    }
+  }, 1000)
 }
 
 function leftSearchDataByRange() {
@@ -513,18 +1103,18 @@
   let endTime = formData.value.endTime;
   let predictTime = formData.value.predictTime;
   if (predictTime) {
-    predictTime = getYMDHMS(new Date(predictTime) - 1000 * 60 * mins);
+    predictTime = getYMDHMS(new Date(predictTime).getTime() - 1000 * 60 * mins);
     formData.value.predictTime = predictTime;
   }
   if (startTime) {
-    startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins);
+    startTime = getYMDHMS(new Date(startTime).getTime() - 1000 * 60 * mins);
     formData.value.startTime = startTime;
   }
   if (endTime) {
-    endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins);
+    endTime = getYMDHMS(new Date(endTime).getTime() - 1000 * 60 * mins);
     formData.value.endTime = endTime;
   }
-  getList();
+  getList(false);
 }
 
 function getRangeMins() {
@@ -539,10 +1129,21 @@
 }
 
 function onCheckTree(data, checked, indeterminate) {
-  formData.value.checkedItemData = [];
-  if (checked.checkedNodes) {
-    formData.value.checkedItemData = [...checked.checkedNodes]
-  }
+  // 单选
+  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);
 }
 
@@ -577,61 +1178,80 @@
     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()
   }
 }
 
 function calAccuracyRate() {
-  this.$refs['calRateForm'].validate((valid) => {
-    if (!valid) {
-      return false
-    }
-    let dataView = itemDataObject[calRateForm.value.calItem]
-    let seriesReaData = dataView.realData;
-    let seriesPreData = dataView.preDataL;
-    if (seriesReaData == null || seriesPreData == null ||
-      seriesReaData.length === 0 || seriesPreData.length === 0) {
-      loading2.value = false;
-      return;
-    }
-    let predictValueMap = {};
-    seriesPreData.forEach(function (item) {
-      predictValueMap[item[0]] = item[1];
-    })
-    let pointValueMap = {};
-    seriesReaData.forEach(function (item) {
-      pointValueMap[item[0]] = item[1];
-    })
-    let inDeviation = Number(calRateForm.value.IN_DEVIATION);
-    let outDeviation = Number(calRateForm.value.OUT_DEVIATION);
-    if (inDeviation === 0 && outDeviation === 0) {
-      loading2.value = false;
-      return;
-    }
-    let inDeviationCount = 0;
-    let outDeviationCount = 0;
-    let totalCount = 0;
-    for (let key in predictValueMap) {
-      let predictValue = predictValueMap[key];
-      let pointValue = pointValueMap[key];
-      if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) {
-        continue;
-      }
-      let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1;
-      if (deviationAbs < inDeviation) {
-        inDeviationCount = inDeviationCount + 1;
-      }
-      if (deviationAbs > outDeviation) {
-        outDeviationCount = outDeviationCount + 1;
-      }
-      totalCount = totalCount + 1;
-    }
+  const valid = calRateFormRef.value.validate()
+  if (!valid) return
 
-    let rateIn = (inDeviationCount / totalCount * 100).toFixed(2);
-    let rateOut = (outDeviationCount / totalCount * 100).toFixed(2);
-    calRateForm.value.IN_ACCURACY_RATE = Number(rateIn);
-    calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut);
+  let dataView = itemDataObject.value[calRateForm.value.calItem]
+  let seriesReaData = dataView.realData;
+  let seriesPreData = dataView.preDataL;
+  if (seriesReaData == null || seriesPreData == null ||
+    seriesReaData.length === 0 || seriesPreData.length === 0) {
     loading2.value = false;
+    return;
+  }
+  let predictValueMap = {};
+  seriesPreData.forEach(function (item) {
+    predictValueMap[item[0]] = item[1];
   })
+  let pointValueMap = {};
+  seriesReaData.forEach(function (item) {
+    pointValueMap[item[0]] = item[1];
+  })
+  let inDeviation = Number(calRateForm.value.IN_DEVIATION);
+  let outDeviation = Number(calRateForm.value.OUT_DEVIATION);
+  if (inDeviation === 0 && outDeviation === 0) {
+    loading2.value = false;
+    return;
+  }
+  let inDeviationCount = 0;
+  let outDeviationCount = 0;
+  let totalCount = 0;
+  for (let key in predictValueMap) {
+    let predictValue = predictValueMap[key];
+    let pointValue = pointValueMap[key];
+    if (pointValue == null || "" === pointValue || predictValue == null || "" === predictValue) {
+      continue;
+    }
+    let deviationAbs = (predictValue - pointValue) >= 0 ? (predictValue - pointValue) : (predictValue - pointValue) * -1;
+    if (deviationAbs < inDeviation) {
+      inDeviationCount = inDeviationCount + 1;
+    }
+    if (deviationAbs > outDeviation) {
+      outDeviationCount = outDeviationCount + 1;
+    }
+    totalCount = totalCount + 1;
+  }
+
+  let rateIn = (inDeviationCount / totalCount * 100).toFixed(2);
+  let rateOut = (outDeviationCount / totalCount * 100).toFixed(2);
+  calRateForm.value.IN_ACCURACY_RATE = Number(rateIn);
+  calRateForm.value.OUT_ACCURACY_RATE = Number(rateOut);
+  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() {
@@ -640,18 +1260,18 @@
   let endTime = formData.value.endTime;
   let predictTime = formData.value.predictTime;
   if (predictTime) {
-    predictTime = getYMDHMS(new Date(predictTime) - 0 + 1000 * 60 * mins);
+    predictTime = getYMDHMS(new Date(predictTime).getTime() + 1000 * 60 * mins);
     formData.value.predictTime = predictTime;
   }
   if (startTime) {
-    startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins);
+    startTime = getYMDHMS(new Date(startTime).getTime() + 1000 * 60 * mins);
     formData.value.startTime = startTime;
   }
   if (endTime) {
-    endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins);
+    endTime = getYMDHMS(new Date(endTime).getTime() + 1000 * 60 * mins);
     formData.value.endTime = endTime;
   }
-  getList();
+  getList(false);
 }
 
 /** 重置表单 */
@@ -671,8 +1291,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,
@@ -681,12 +1301,12 @@
     queryStep: 2,
     isMultipleYRadio: '单坐标轴',
     isMultipleY: false,
-    predictFreq: 3,
+    predictFreq: 2,
   }
   calRateForm.value = {
     calItem: undefined,
-    IN_DEVIATION: 0,
-    OUT_DEVIATION: 0,
+    IN_DEVIATION: 10,
+    OUT_DEVIATION: 50,
     IN_ACCURACY_RATE: 0,
     OUT_ACCURACY_RATE: 0,
     itemAvg: 0,
@@ -696,12 +1316,31 @@
     itemPreMax: 0,
     itemPreMin: 0,
     preCumulant: 0,
-    realCumulant: 0
+    realCumulant: 0,
+    deviation: 0, //平均绝对误差
+    deviationCumulant: 0, //累积量平均绝对误差
   }
   calRateFormRef.value?.resetFields()
 }
 </script>
 <style scoped>
+.chart-foot-table {
+  border: 1px solid #bababa;
+}
+.chart-foot-title {
+  font-size: 14px;
+}
+.chart-foot-content {
+  height: 100%;
+  width: 50%;
+  padding: 5px;
+}
+.chart-foot {
+  height: 120px;
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+}
 .el-form-item {
   margin-bottom: 0 !important;
 }
@@ -710,12 +1349,15 @@
   height: 100%;
   border: 1px solid lightgray;
   padding: 10px;
+  overflow-y: auto;
+  overflow-x: hidden;
 }
 
 .his-body-tree {
   height: 100%;
   border: 1px solid lightgray;
-  padding: 10px;
+  padding: 10px 10px 20px 10px;
+  overflow-y: auto;
 }
 
 .his-body-right {
@@ -732,7 +1374,7 @@
 
 .his-body {
   width: 100%;
-  height: calc(calc(100vh - 68px - 38px - 160px));
+  height: calc(calc(100vh - 68px - 38px - 60px));
   display: flex;
   flex-direction: row;
   justify-content: flex-start;

--
Gitblit v1.9.3