From 0568fa140511a5df539dbc87759bb2040e7d8b10 Mon Sep 17 00:00:00 2001 From: houzhongjian <houzhongyi@126.com> Date: 星期二, 06 五月 2025 15:06:52 +0800 Subject: [PATCH] 模型管理数据分析页面增加授权功能,以供第三方嵌入 --- src/router/modules/remaining.ts | 9 src/views/model/pre/analysis/third.vue | 1428 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/permission.ts | 4 3 files changed, 1,440 insertions(+), 1 deletions(-) diff --git a/src/permission.ts b/src/permission.ts index 9120c1a..5e01684 100644 --- a/src/permission.ts +++ b/src/permission.ts @@ -49,11 +49,13 @@ // 路由不重定向白名单 const whiteList = [ '/login', + '/sso', '/social-login', '/auth-redirect', '/bind', '/register', - '/oauthLogin/gitee' + '/oauthLogin/gitee', + '/model/analysis' ] // 路由加载前 diff --git a/src/router/modules/remaining.ts b/src/router/modules/remaining.ts index c47f1a7..6c21655 100644 --- a/src/router/modules/remaining.ts +++ b/src/router/modules/remaining.ts @@ -470,6 +470,15 @@ } ] }, + { + path: '/model/analysis', + name: 'AnalysisformDataThird', + component: () => import('@/views/model/pre/analysis/third.vue'), + meta: { + hidden: true, + noTagsView: true + }, + } ] export default remainingRouter diff --git a/src/views/model/pre/analysis/third.vue b/src/views/model/pre/analysis/third.vue new file mode 100644 index 0000000..1f4c1da --- /dev/null +++ b/src/views/model/pre/analysis/third.vue @@ -0,0 +1,1428 @@ +<template> + <el-card shadow="never" class="aui-card--fill"> + <div class="mod-his__index"> + <el-form :inline="true" :model="formData" label-width="70px"> + <el-form-item label="开始时间"> + <el-date-picker + v-model="formData.startTime" + type="datetime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="结束时间"> + <el-date-picker + v-model="formData.endTime" + type="datetime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="预测时间"> + <el-date-picker + v-model="formData.predictTime" + type="datetime" + format="YYYY-MM-DD HH:mm:00" + value-format="YYYY-MM-DD HH:mm:00" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="预测频率"> + <el-input-number v-model="formData.predictFreq" controls-position="right" + :min="1" + :max="10"/> + </el-form-item> + <el-form-item> + <el-button-group> + <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="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="treeRef" + highlight-current + check-strictly + :filter-node-method="filterNode" + @check="onCheckTree"/> + </div> + </div> + <div class="his-body-right"> + <div class="his-body-chart"> + <el-form :inline="true" :model="calRateForm" :rules="formRules" ref="calRateFormRef" + label-width="108px"> + <el-row> +<!-- <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="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="8"> + <el-form-item> + <el-button size="small" type="primary" plain :loading="loading2" + @click="calAccuracyRate">计算精准度 + </el-button> + </el-form-item> + </el-col> + </el-row> + <el-row> + <el-col :span="4"> + <el-form-item label="精准度:"> + {{ calRateForm.IN_ACCURACY_RATE }}% + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="预测平均值:"> + {{ calRateForm.itemPreAvg }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="预测最大值:"> + {{ calRateForm.itemPreMax }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="预测最小值:"> + {{ calRateForm.itemPreMin }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="预测累积量:"> + {{ 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"> + <el-form-item label="不可信率:"> + {{ calRateForm.OUT_ACCURACY_RATE }}% + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="真实平均值:"> + {{ calRateForm.itemAvg }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="真实最大值:"> + {{ calRateForm.itemMax }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="真实最小值:"> + {{ calRateForm.itemMin }} + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item label="真实累积量:"> + {{ 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="18"> + <el-form-item label="数据类型"> + <el-checkbox-group v-model="formData.chartCheck" @change="changeChartCheck"> + <el-checkbox v-for="item in formData.chartOptions" :label="item" + :key="item">{{ item }} + </el-checkbox> + </el-checkbox-group> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item> + <el-radio v-model="formData.isMultipleY" :label="false" + @input="onChangeMultipleY">单坐标轴 + </el-radio> + <el-radio v-model="formData.isMultipleY" :label="true" + @input="onChangeMultipleY">多坐标轴 + </el-radio> + </el-form-item> + </el-col> + </el-row> + </el-form> + <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> + </el-form> + </div> + </el-card> +</template> +<script lang="ts" setup> +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"; +import * as LoginApi from '@/api/login' +import * as authUtil from '@/utils/auth' + +defineOptions({name: 'AnalysisformDataThird'}) + +const message = useMessage() // 消息弹窗 +const {t} = useI18n() // 国际化 + +const loading1 = ref(false) // 列表的加载中 +const loading2 = ref(false) // 列表的加载中 +const total = ref(0) // 列表的总页数 +const list = ref([]) // 字典表格数据 +let formData = ref({ + rangeDate: '', + startTime: '', + endTime: '', + predictTime: '', + predictTimeStr: '', + startTimeStr: '', + endTimeStr: '', + predictTimeStamp: 0, + startTimeStamp: 0, + endTimeStamp: 0, + currentStamp: '', + currentStamp60: '', + predictStamp: '', + chartCheck: ['T+L', '真实值'], + chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'], + checkedItemData: undefined, + backItem: '', + backValue: 0, + backCoe: 1, + preCumulant: 0, + realCumulant: 0, + queryStep: 2, + isMultipleYRadio: '单坐标轴', + isMultipleY: false, + predictFreq: 2, +}) +const calRateFormRef = ref() +const calRateForm = ref({ + calItem: undefined, + IN_DEVIATION: 10, + OUT_DEVIATION: 50, + IN_ACCURACY_RATE: 0, + OUT_ACCURACY_RATE: 0, + itemAvg: 0, + itemMax: 0, + itemMin: 0, + itemPreAvg: 0, + itemPreMax: 0, + itemPreMin: 0, + preCumulant: 0, + realCumulant: 0, + deviation: 0, //平均绝对误差 + deviationCumulant: 0, //累积量平均绝对误差 +}) +let itemData = ref({ + currentTreeList: [], + chart: {}, + option: {} +}) +const treeData = ref([]) +const itemDataObject = ref() +const timer = ref() +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({ + 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) => { + loading1.value = true + try { + if (!formData.value.chartCheck) { + formData.value.chartCheck = ['真实值'] + } + let chartCheckArray = formData.value.chartCheck; + if (!formData.value.checkedItemData) { + itemData.value.option = {}; + return; + } + 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 + + // 默认影响时间 + 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; + 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 + if (chartCheckArray.indexOf('真实值') !== -1) { + let legendName = dataView.resultName + '(真实)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.realData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + smooth: false, + lineStyle: { + width: 2 + } + }); + } + 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: 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.preDataL || [], + type: 'line', + showSymbol: false, + connectNulls: true, + yAxisIndex: yAxisIndex, + 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 + } + }); + } + if (chartCheckArray.indexOf('当时') !== -1) { + let legendName = dataView.resultName + '(当时)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.curData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: true, + smooth: false, + lineStyle: { + width: 3 + } + }); + } + if (chartCheckArray.indexOf('调整值') !== -1) { + let legendName = dataView.resultName + '(调整值)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.adjData || [], + type: 'line', + yAxisIndex: yAxisIndex, + 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.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' + } + }); + } + } + //如果最大值相差不大,改成一致大小 + 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 + } + 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(() => { + if(!authUtil.getTenantId()) { + handleLogin() + } + initChart() + resetForm() + getPreItemTree() +}) + +const loginData = reactive({ + isShowPassword: false, + captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE, + tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE, + loginForm: { + tenantName: 'ansteel', + username: 'model', + password: 'anxin123456!@', + captchaVerification: '', + rememberMe: true // 默认记录我。如果不需要,可手动修改 + } +}) + +// 登录 +const handleLogin = async () => { + try { + await getTenantId() + const res = await LoginApi.login(loginData.loginForm) + if (!res) { + return + } + if (loginData.loginForm.rememberMe) { + authUtil.setLoginForm(loginData.loginForm) + } else { + authUtil.removeLoginForm() + } + authUtil.setToken(res) + } catch (e) { + message.error("对不起,您没有权限,请联系管理员") + } +} + +// 获取租户 ID +const getTenantId = async () => { + if (loginData.tenantEnable === 'true') { + const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName) + authUtil.setTenantId(res) + } +} + +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() { + let mins = getRangeMins(); + 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 getRangeMins() { + let result: string | number = 0; + if (formData.value.startTime && formData.value.endTime) { + let startStamp = new Date(formData.value.startTime).getTime(); + let endStamp = new Date(formData.value.endTime).getTime(); + let queryStep = ((endStamp - startStamp) / (1000 * 60)).toFixed(0); + result = queryStep >= 0 ? queryStep : 0; + } + return result; +} + +function onCheckTree(data, checked, indeterminate) { + // 单选 + 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); +} + +function debounce(func, wait) { + let args = []; + if (timer.value) { + clearTimeout(timer.value); + } + timer.value = setTimeout(() => { + func.apply(this, args); + timer.value = null; + }, wait) +} + +function calItemBaseVale() { + if (!calRateForm.value.calItem) { + calRateForm.value.itemPreMax = 0; + calRateForm.value.itemPreMin = 0; + calRateForm.value.itemPreAvg = 0; + calRateForm.value.preCumulant = 0; + calRateForm.value.itemMax = 0; + calRateForm.value.itemMin = 0; + calRateForm.value.itemAvg = 0; + calRateForm.value.realCumulant = 0; + } else { + let dataView = itemDataObject.value[calRateForm.value.calItem] + calRateForm.value.itemPreMax = dataView.preMax; + calRateForm.value.itemPreMin = dataView.preMin; + calRateForm.value.itemPreAvg = dataView.preAvg; + calRateForm.value.preCumulant = dataView.preCumulant; + calRateForm.value.itemMax = dataView.hisMax; + 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() { + const valid = calRateFormRef.value.validate() + if (!valid) return + + 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() { + let mins = getRangeMins(); + 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); +} + +/** 重置表单 */ +const resetForm = () => { + formData.value = { + rangeDate: '', + startTime: '', + endTime: '', + predictTime: '', + predictTimeStr: '', + startTimeStr: '', + endTimeStr: '', + predictTimeStamp: 0, + startTimeStamp: 0, + endTimeStamp: 0, + currentStamp: '', + currentStamp60: '', + predictStamp: '', + chartCheck: ['T+L', '真实值'], + chartOptions: ['T+N', 'T+L','T+L(未调整)', '当时', '真实值', '调整值', '预测累计', '真实累计'], + checkedItemData: undefined, + backItem: '', + backValue: 0, + backCoe: 1, + preCumulant: 0, + realCumulant: 0, + queryStep: 2, + isMultipleYRadio: '单坐标轴', + isMultipleY: false, + predictFreq: 2, + } + calRateForm.value = { + calItem: undefined, + IN_DEVIATION: 10, + OUT_DEVIATION: 50, + IN_ACCURACY_RATE: 0, + OUT_ACCURACY_RATE: 0, + itemAvg: 0, + itemMax: 0, + itemMin: 0, + itemPreAvg: 0, + itemPreMax: 0, + itemPreMin: 0, + preCumulant: 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; +} + +.his-body-chart { + 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 10px 20px 10px; + overflow-y: auto; +} + +.his-body-right { + width: 80%; + height: 100%; + padding-top: 10px; +} + +.his-body-left { + width: 20%; + height: 100%; + padding: 10px 10px 0 0; +} + +.his-body { + width: 100%; + height: calc(calc(100vh - 68px - 38px - 60px)); + display: flex; + flex-direction: row; + justify-content: flex-start; + align-content: flex-start; +} +</style> -- Gitblit v1.9.3