From d7fad0f8d38ee60bd39634f814857c70a4b51d26 Mon Sep 17 00:00:00 2001 From: Jay <csj123456> Date: 星期一, 30 九月 2024 17:15:35 +0800 Subject: [PATCH] 预测数据分析前端代码 --- src/utils/dateUtil.ts | 17 + src/api/model/pre/predict/index.ts | 8 src/views/model/pre/analysis/index.vue | 687 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 712 insertions(+), 0 deletions(-) diff --git a/src/api/model/pre/predict/index.ts b/src/api/model/pre/predict/index.ts index 677b35a..e27d8c4 100644 --- a/src/api/model/pre/predict/index.ts +++ b/src/api/model/pre/predict/index.ts @@ -130,3 +130,11 @@ httpRequest } } + +export const getMmPredictItemTree = () => { + return request.get({ url: `/model/pre/item/tree`}) +} + +export const getViewCharts = (params) => { + return request.get({ url: `/model/pre/item/view-charts`,params}) +} diff --git a/src/utils/dateUtil.ts b/src/utils/dateUtil.ts index b83c3a5..658f0a1 100644 --- a/src/utils/dateUtil.ts +++ b/src/utils/dateUtil.ts @@ -21,3 +21,20 @@ } export const dateUtil = dayjs + +export function getYMDHMS (timestamp: number | string | Date) { + const time = new Date(timestamp) + const year = time.getFullYear() + let month = time.getMonth() + 1 + '' + let date = time.getDate() + '' + let hours = time.getHours() + '' + let minute = time.getMinutes() + '' + let second = time.getSeconds() + '' + + if (month < 10) { month = '0' + month } + if (date < 10) { date = '0' + date } + if (hours < 10) { hours = '0' + hours } + if (minute < 10) { minute = '0' + minute } + if (second < 10) { second = '0' + second } + return year + '-' + month + '-' + date + ' ' + hours + ':' + minute + ':' + second +} diff --git a/src/views/model/pre/analysis/index.vue b/src/views/model/pre/analysis/index.vue new file mode 100644 index 0000000..f61540c --- /dev/null +++ b/src/views/model/pre/analysis/index.vue @@ -0,0 +1,687 @@ +<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-item label="开始时间"> + <el-date-picker + size="mini" + v-model="formData.startTime" + type="datetime" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="结束时间"> + <el-date-picker + size="mini" + v-model="formData.endTime" + type="datetime" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="预测时间"> + <el-date-picker + size="mini" + v-model="formData.predictTime" + type="datetime" + placeholder="选择日期时间"/> + </el-form-item> + <el-form-item label="预测频率"> + <el-input-number size="mini" v-model="formData.predictFreq" controls-position="right" :min="1" + :max="10"/> + </el-form-item> + <el-form-item> + <el-button-group> + <el-button size="mini" type="primary" plain :icon="ArrowLeft" + v-loading="loading1" @click="leftSearchDataByRange()"/> + <el-button size="mini" type="primary" plain :icon="Search" + v-loading="loading1" @click="getList()">查询</el-button> + <el-button size="mini" type="primary" plain :icon="ArrowRight" + v-loading="loading1" @click="rightSearchDataByRange()"/> + </el-button-group> + </el-form-item> + + <div class="his-body"> + <div class="his-body-left"> + <div class="his-body-tree"> + <el-tree + :data="treeData" + show-checkbox + node-key="id" + ref="tree" + highlight-current + :props="defaultProps" + @check="onCheckTree"/> + </div> + </div> + <div class="his-body-right"> + <div class="his-body-chart"> + <el-form :inline="true" :model="calRateForm" ref="calRateForm" label-width="100px"> + <el-row> + <el-col :span="6" > + <el-form-item label="预测项" prop="calItem" style="width: 90%"> + <el-select v-model="calRateForm.calItem" @change="calItemBaseVale" placeholder="请选择"> + <el-option + v-for="item in formData.checkedItemData" + :key="item.id" + :label="item.label" + :value="item.id"/> + </el-select> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="精准度偏差" prop="IN_DEVIATION"> + <el-input-number size="mini" v-model="calRateForm.IN_DEVIATION" controls-position="right" :min="1" + :max="10"/> + </el-form-item> + </el-col> + <el-col :span="6"> + <el-form-item label="不可信率偏差" prop="OUT_DEVIATION"> + <el-input-number size="mini" v-model="calRateForm.OUT_DEVIATION" controls-position="right" + :min="1" + :max="20"/> + </el-form-item> + </el-col> + <el-col :span="4"> + <el-form-item> + <el-button size="mini" 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-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-row> + </el-form> + <el-form :inline="true" :model="formData" label-width="100px"> + <el-row> + <el-col :span="12"> + <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 id="data-analysis" style="height: 500px;"></div> + </div> + </div> + </div> + </el-form> + + </div> + </el-card> +</template> +<script lang="ts" setup> + import {getYMDHMS} from "@/utils/dateUtil" + import * as CategoryApi from "@/api/data/ind/category"; + import * as DmModule from '@/api/model/pre/dm' + import * as ItemApi from "@/api/data/ind/item/item"; + import * as MmPredictItem from '@/api/model/pre/predict' + import * as echarts from "echarts"; + import { onMounted, ref } from 'vue'; + import { Search, ArrowLeft, ArrowRight,} from '@element-plus/icons-vue' + + defineOptions({name: 'AnalysisformData'}) + + const message = useMessage() // 消息弹窗 + const { t } = useI18n() // 国际化 + const dataCategoryList = ref([] as CategoryApi.IndItemCategoryVO[]) + const loading1 = ref(false) // 列表的加载中 + const loading2 = ref(false) // 列表的加载中 + const total = ref(0) // 列表的总页数 + const list = ref([]) // 字典表格数据 + const queryParams = reactive({ + pageNo: 1, + pageSize: 10, + itemno: '', + itemname: '', + }) + 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', '当时', '真实值', '调整值'], + checkedItemData: [], + backItem: '', + backValue: 0, + backCoe: 1, + preCumulant: 0, + realCumulant: 0, + queryStep: 2, + isMultipleYRadio: '单坐标轴', + isMultipleY: false, + predictFreq: 3, + }) + let calRateForm = ref({ + calItem: '', + IN_DEVIATION: 0, + OUT_DEVIATION: 0, + IN_ACCURACY_RATE: 0, + OUT_ACCURACY_RATE: 0, + itemAvg: 0, + itemMax: 0, + itemMin: 0, + itemPreAvg: 0, + itemPreMax: 0, + itemPreMin: 0, + preCumulant:0, + realCumulant:0 + }) + let itemData = ref({ + currentTreeList: [], + chart: {}, + option: {} + }) + const chartContainer = ref(null); + const treeData = ref([]) + const itemDataObject = ref() + const timer = ref() + //const myChart = echarts.init(document.getElementById("data-analysis")); + /** 查询列表 */ + const getList = async () => { + loading1.value = true + try { + const data = formData.value + if (!formData.value.chartCheck) { + formData.value.chartCheck = ['真实值'] + } + let chartCheckArray = formData.value.chartCheck; + if (!formData.value.checkedItemData || formData.value.checkedItemData.length == 0) { + itemData.value.option = {}; + return; + } + let itemIdList = formData.value.checkedItemData.map(item => { + return item.id + }) + const params = ref({ + itemIds: itemIdList.join(','), + predictTime: formData.value.predictTime, + startTime: formData.value.startTime, + endTime: formData.value.endTime + }) + const res = await MmPredictItem.getViewCharts(params) + if (res.code !== 0) { + return message.error(res.msg) + } + formData.value.predictTime = res.data.predictTime; + formData.value.startTime = res.data.startTime + formData.value.endTime = res.data.endTime + + let xAxisData = res.data.categories; + 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 = {} + for (let i = 0; i < res.data.dataViewList.length; i++) { + let dataView = res.data.dataViewList[i] + itemDataObject.value.dataView.itemId = dataView; + let maxValue = dataView.maxValue; + let minValue = dataView.minValue; + yAxisIndex = formData.value.isMultipleY ? i : 0; + 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.itemName + '(真实)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.realData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + smooth: true, + lineStyle: { + width: 3 + } + }); + } + if (chartCheckArray.indexOf('T+N') !== -1) { + let legendName = dataView.itemName + '(T+N)'; + seriesData.push({ + name: legendName, + data: dataView.preDataN || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + smooth: true, + lineStyle: { + width: 3 + } + }); + } + if (chartCheckArray.indexOf('T+L') !== -1) { + let legendName = dataView.itemName + '(T+L)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.preDataL || [], + type: 'line', + showSymbol: false, + connectNulls: true, + yAxisIndex: yAxisIndex, + smooth: true, + lineStyle: { + width: 3 + } + }); + } + if (chartCheckArray.indexOf('当时') !== -1) { + let legendName = dataView.itemName + '(当时)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.curData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + smooth: true, + lineStyle: { + width: 3 + } + }); + } + if (chartCheckArray.indexOf('调整值') !== -1) { + let legendName = dataView.itemName + '(调整值)'; + legendData.push(legendName); + seriesData.push({ + name: legendName, + data: dataView.adjData || [], + type: 'line', + yAxisIndex: yAxisIndex, + showSymbol: false, + connectNulls: true, + smooth: true, + lineStyle: { + width: 3, + 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' + }, + legend: { + show: true, + data: legendData, + top: 10 + }, + grid: { + top: 50, + left: '3%', + right: '6%', + bottom: '3%', + containLabel: true + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: xAxisData + }, + yAxis: formData.value.isMultipleY ? yAxisData : { + type: 'value', + splitLine: {show: false}, + axisLine: {show: true} + }, + dataZoom: [ + { + type: 'inside', + start: 0, + end: 100 + }, + { + start: 0, + end: 10 + } + ], + series: seriesData + } + //chart.setOption(option) + } finally { + loading1.value = false + } + } + + onMounted(() => { + getPreItemTree() + getList() + }) + + async function getPreItemTree() { + treeData.value = await MmPredictItem.getMmPredictItemTree() + } + + 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) - 1000 * 60 * mins); + formData.value.predictTime = predictTime; + } + if (startTime) { + startTime = getYMDHMS(new Date(startTime) - 1000 * 60 * mins); + formData.value.startTime = startTime; + } + if (endTime) { + endTime = getYMDHMS(new Date(endTime) - 1000 * 60 * mins); + formData.value.endTime = endTime; + } + getList(); + } + + 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) { + formData.value.checkedItemData = []; + if (checked.checkedNodes) { + formData.value.checkedItemData = [...checked.checkedNodes] + } + //myChart.clear() + 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; + return + } else { + let dataView = itemDataObject[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; + } + } + + 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; + } + + 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 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) - 0 + 1000 * 60 * mins); + formData.value.predictTime = predictTime; + } + if (startTime) { + startTime = getYMDHMS(new Date(startTime) - 0 + 1000 * 60 * mins); + formData.value.startTime = startTime; + } + if (endTime) { + endTime = getYMDHMS(new Date(endTime) - 0 + 1000 * 60 * mins); + formData.value.endTime = endTime; + } + getList(); + } + +</script> +<style scoped> + .el-form-item { + margin-bottom: 0 !important; + } + + .his-body-chart { + height: 100%; + border: 1px solid lightgray; + padding: 10px; + } + + .his-body-tree { + height: 100%; + border: 1px solid lightgray; + padding: 10px; + } + + .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 - 48px - 38px - 130px)); + display: flex; + flex-direction: row; + justify-content: flex-start; + align-content: flex-start; + } +</style> -- Gitblit v1.9.3