统计分析

This commit is contained in:
geting 2024-11-27 09:47:29 +08:00
parent 0c96a07849
commit cc7fc7fc64
4 changed files with 257 additions and 199 deletions

View File

@ -13,9 +13,16 @@
</template>
</el-popover>
</el-menu>
<PowerCurveAnalysis v-if="activeIndex == 0"></PowerCurveAnalysis>
<TrendAnalysis v-if="activeIndex == 1"></TrendAnalysis>
<TrendComparison v-if="activeIndex == 2"></TrendComparison>
<keep-alive>
<PowerCurveAnalysis v-if="activeIndex == 0"></PowerCurveAnalysis>
</keep-alive>
<keep-alive>
<TrendAnalysis v-if="activeIndex == 1"></TrendAnalysis>
</keep-alive>
<keep-alive>
<TrendComparison v-if="activeIndex == 2"></TrendComparison>
</keep-alive>
</div>
</template>
<script setup lang="ts">

View File

@ -54,7 +54,10 @@
</div>
</div>
</div>
<div ref="chartContainer" style="position: absolute; bottom: 0px; width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<div
ref="chartContainer"
style="height: calc(100% - 240px); width: calc(100% - 60px); border: 1px solid rgb(217, 217, 217); margin: 30px"
></div>
</div>
</template>
<script setup lang="ts">
@ -124,10 +127,7 @@ const option: any = {
},
},
series: [],
grid: {
left: '3%',
right: '3%',
},
grid: {},
}
const chart: any = ref(null)
@ -148,10 +148,7 @@ onMounted(() => {
type: 'line',
},
],
grid: {
left: '3%',
right: '3%',
},
grid: {},
})
chart.value.on('legendselectchanged', function (event: any) {
var isSelected = event.selected[event.name]
@ -264,6 +261,14 @@ const getDateRange = (type: 'week' | 'month') => {
const isLoading = ref(false)
const statAnalysisOperate = () => {
const deviceId = statAnalysisDeviceId.value.split(':')[2]
if (!deviceId) {
ElMessage.info('请选择风机!')
return
} else if (!statAnalysisTime.value.length) {
ElMessage.info('请选择查询时间!')
return
}
isLoading.value = true
option.series = []
option.legend.data = []
@ -271,7 +276,7 @@ const statAnalysisOperate = () => {
const requestData = {
devices: [
{
deviceId: statAnalysisDeviceId.value.split(':')[2],
deviceId: deviceId,
attributes: ['iGenPower', 'iWindSpeed'],
},
],
@ -392,6 +397,9 @@ const statAnalysisExport = () => {
<style scoped lang="scss">
.statAnalysis {
height: 100%;
.contain {
height: calc(100% - 60px);
}
.headerPart {
padding: 20px;
display: flex;

View File

@ -1,105 +1,117 @@
<template>
<div class="contain">
<el-header class="headerPart">
<div class="topLeft">
<div class="selectPart">
<span>{{ t('statAnalysis.deviceId') }}</span>
<el-select
v-model="statAnalysisSelect.deviceId"
@change="deviceIdChange"
:placeholder="'请选择' + t('statAnalysis.deviceId')"
class="statAnalysisSelect"
>
<el-option v-for="v in statAnalysisSelectOptions.deviceId" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.attributes') }}</span>
<el-input
class="statAnalysisSelect"
v-model="statAnalysisSelect.attributes"
@click="attributesChange"
:placeholder="'请选择' + t('statAnalysis.attributes')"
></el-input>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select v-model="statAnalysisSelect.interval" :placeholder="'请选择' + t('statAnalysis.interval')" class="statAnalysisSelect">
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
</div>
<div class="topRight">
<el-button type="primary" :loading="isLoading" @click="statAnalysisOperate()">{{ t('statAnalysis.search') }}</el-button>
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
</div>
</el-header>
<div class="timeColumns">
<div class="headerPart" v-for="(time, index) in times" :key="index">
<div class="left">
<div class="headerPart">
<div class="topLeft">
<div class="selectPart">
<el-input v-model="customName[index]" class="customName"></el-input>
<span>{{ t('statAnalysis.time') }}</span>
<el-date-picker
class="datetime-picker"
v-model="times[index]"
:type="statAnalysisSelect.interval == '1d' ? 'daterange' : 'datetimerange'"
:value-format="statAnalysisSelect.interval == '1d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:teleported="false"
:shortcuts="shortcuts"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
@change="timechange(index)"
/>
<span>{{ t('statAnalysis.deviceId') }}</span>
<el-select
v-model="statAnalysisSelect.deviceId"
@change="deviceIdChange"
:placeholder="'请选择' + t('statAnalysis.deviceId')"
class="statAnalysisSelect"
>
<el-option v-for="v in statAnalysisSelectOptions.deviceId" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
<div class="topLeft">
<div class="icon">
<el-button v-show="index === times.length - 1" type="primary" size="small" :icon="Plus" circle @click="addTime(index)">
</el-button>
<div class="selectPart">
<span>{{ t('statAnalysis.attributes') }}</span>
<el-input
class="statAnalysisSelect"
v-model="statAnalysisSelect.attributes"
@click="attributesChange"
:placeholder="'请选择' + t('statAnalysis.attributes')"
></el-input>
</div>
<div class="selectPart">
<span>{{ t('statAnalysis.interval') }}</span>
<el-select
v-model="statAnalysisSelect.interval"
:placeholder="'请选择' + t('statAnalysis.interval')"
class="statAnalysisSelect"
>
<el-option v-for="v in statAnalysisSelectOptions.interval" :key="v.value" :label="v.label" :value="v.value"></el-option>
</el-select>
</div>
</div>
<div class="topRight">
<el-button type="primary" :icon="Plus" @click="addTime()"> 增加</el-button>
<el-button type="primary" :loading="isLoading" @click="statAnalysisOperate()">{{ t('statAnalysis.search') }}</el-button>
<el-button style="color: #0064aa" @click="statAnalysisExport()">{{ t('statAnalysis.export') }}</el-button>
</div>
</div>
<div class="timeColumns">
<div class="moduleRow" v-for="(time, index) in times" :key="index">
<div class="item">
<el-icon v-show="index !== 0" class="removeModule" @click="switchTime(index)">
<Close />
</el-icon>
<div class="selectPart">
<span>曲线名称</span>
<el-input v-model="customName[index]" class="customName"></el-input>
</div>
<div class="icon">
<el-button v-show="index !== 0" type="danger" size="small" :icon="Delete" @click="switchTime(index)" circle></el-button>
<div class="selectPart">
<span>{{ t('statAnalysis.time') }}</span>
<el-date-picker
class="datetime-picker"
v-model="times[index]"
:type="statAnalysisSelect.interval == '1d' ? 'daterange' : 'datetimerange'"
:value-format="statAnalysisSelect.interval == '1d' ? 'YYYY-MM-DD' : 'YYYY-MM-DD HH:mm:ss'"
:teleported="false"
:shortcuts="shortcuts"
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
@change="timechange(index)"
/>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['max'] !== ''">
<span>{{ t('statAnalysis.max') }}</span>
<span class="max">{{ calculate[index]['max'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['min'] !== ''">
<span>{{ t('statAnalysis.min') }}</span>
<span class="min">{{ calculate[index]['min'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['average'] !== ''">
<span>{{ t('statAnalysis.average') }}</span>
<span class="average">{{ calculate[index]['average'] }}</span>
<div class="topLeft">
<div class="selectPart" v-if="calculate[index] && calculate[index]['max'] !== ''">
<span>{{ t('statAnalysis.max') }}</span>
<span class="max">{{ calculate[index]['max'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['min'] !== ''">
<span>{{ t('statAnalysis.min') }}</span>
<span class="min">{{ calculate[index]['min'] }}</span>
</div>
<div class="selectPart" v-if="calculate[index] && calculate[index]['average'] !== ''">
<span>{{ t('statAnalysis.average') }}</span>
<span class="average">{{ calculate[index]['average'] }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div ref="chartContainer" style="position: absolute; bottom: 0px; width: 100%; height: 400px; border: 1px solid rgb(217, 217, 217)"></div>
<el-dialog v-model="showMeasure" title="测点名称" :width="800">
<template #header>
<div class="measureSlotHeader">
<span style="font-size: 20px">测点名称</span>
</div>
</template>
<div class="measureSlot">
<MeasurementPage :show="showMeasure" :iotModelId="iotModelId" :irn="irn" @handleRadioChange="handleRadioChange"></MeasurementPage>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="selectstatAnalysisAttributes"> 保存 </el-button>
<el-button @click="showMeasure = false">取消</el-button>
</span>
</template>
</el-dialog>
<div class="right">
<div
ref="chartContainer"
style="position: absolute; top: 127px; width: calc(100% - 430px); height: 400px; border: 1px solid rgb(217, 217, 217)"
></div>
</div>
</div>
<el-dialog v-model="showMeasure" title="测点名称" :width="800">
<template #header>
<div class="measureSlotHeader">
<span style="font-size: 20px">测点名称</span>
</div>
</template>
<div class="measureSlot">
<MeasurementPage :show="showMeasure" :iotModelId="iotModelId" :irn="irn" @handleRadioChange="handleRadioChange"></MeasurementPage>
</div>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" @click="selectstatAnalysisAttributes"> 保存 </el-button>
<el-button @click="showMeasure = false">取消</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { reactive, ref, onMounted, markRaw } from 'vue'
import { useI18n } from 'vue-i18n'
import { queryWindTurbinesPages, historyReq, trendAnalyseExport } from '/@/api/backend/statAnalysis/request'
import { ElMessage } from 'element-plus'
import { DArrowRight, Plus, Delete } from '@element-plus/icons-vue'
import { DArrowRight, Plus, Delete, Close } from '@element-plus/icons-vue'
import MeasurementPage from './analysisAttributes.vue'
import * as echarts from 'echarts'
const { t } = useI18n()
@ -111,9 +123,9 @@ const statAnalysisSelect = reactive({
time: '',
})
const times: any = reactive([{ time: '' }])
const addTime = (index: any) => {
const addTime = () => {
times.push({ time: '' })
customName.push(statAnalysisSelect.attributes + String(index + 2))
customName.push(statAnalysisSelect.attributes + String(times.length))
}
const switchTime = (index: number) => {
times.splice(index, 1)
@ -186,10 +198,7 @@ const option: any = {
type: 'value',
},
series: [],
grid: {
left: '3%',
right: '3%',
},
grid: {},
}
const statAnalysisSelectOptions: any = reactive({
@ -220,10 +229,7 @@ onMounted(() => {
type: 'line',
},
],
grid: {
left: '3%',
right: '3%',
},
grid: {},
})
}
queryWindTurbines()
@ -343,6 +349,19 @@ const calculate: any = ref([{ max: '', min: '', average: '' }])
var xDatas: any = []
const isLoading = ref(false)
const statAnalysisOperate = () => {
const findTime = times.filter((item: any) => {
return Array.isArray(item)
})
if (!statAnalysisSelect.deviceId) {
ElMessage.info('请选择风机!')
return
} else if (!statAnalysisSelect.attributeCode) {
ElMessage.info('请选择测点名称!')
return
} else if (!findTime.length) {
ElMessage.info('请选择查询时间!')
return
}
isLoading.value = true
option.series = []
option.legend.data = []
@ -389,9 +408,7 @@ const historyDataReq = (promises: any) => {
const deviceId = statAnalysisSelect.deviceId
const attributeCode = statAnalysisSelect.attributeCode
results.forEach((res: any, index: number) => {
console.log(times[index])
const resData = (res && deviceId in res && res[deviceId][attributeCode]) || undefined
if (!resData['values'].length) {
ElMessage.info(`${customName[index]}数据为空`)
return
@ -401,14 +418,9 @@ const historyDataReq = (promises: any) => {
new Date(times[index][1]).getTime(),
statAnalysisSelect.interval || '5m'
)
console.log(alltimes)
console.log('🚀 ~ results.forEach ~ resData:', resData)
const fillData = fillMissingData(alltimes, resData)
console.log('🚀 ~ results.forEach ~ fillData:', fillData)
const xData = fillData['times']
const yData = fillData['values']
console.log(xData)
calculate.value[index] = calculateStats(resData['values'])
xDatas.push({
series: String(customName[index]),
@ -552,74 +564,26 @@ const timestampToTime = (timestamp: any) => {
height: 100%;
.contain {
height: calc(100% - 60px);
display: flex;
.right {
width: calc(100% - 418px);
}
}
.headerPart {
padding: 20px;
display: flex;
justify-content: space-between;
align-items: center;
.topLeft {
display: flex;
.icon {
width: 40px;
height: 40px;
padding: 10px 0;
}
.selectPart {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
margin-right: 20px;
span {
margin-right: 10px;
}
.statAnalysisSelect {
width: 200px;
:deep(.el-select__wrapper) {
height: 40px;
}
:deep(.el-input__inner) {
height: 38px;
}
}
.max,
.min,
.average {
border-radius: 6px;
height: 40px;
width: 72px;
text-align: center;
line-height: 40px;
}
.max {
background: rgba(254, 55, 49, 0.2);
border: 1px solid #fe3731;
color: #fe3731;
}
.min {
background: rgba(0, 160, 150, 0.2);
border: 1px solid #00a096;
color: #00a096;
}
.average {
background: rgba(0, 100, 170, 0.2);
border: 1px solid #0064aa;
color: #0064aa;
}
.customName {
width: 120px;
margin-right: 10px;
}
}
.dialog-footer button:first-child {
margin-right: 10px;
}
}
.topRight {
// width: 436px;
display: flex;
justify-content: space-between;
margin: 12px 0;
.el-button {
width: 100px;
height: 40px;
@ -627,12 +591,92 @@ const timestampToTime = (timestamp: any) => {
}
}
.timeColumns {
height: calc(100% - 480px);
overflow-y: auto;
.moduleRow {
.item {
position: relative;
margin: 10px 8px;
width: 402px;
min-height: 170px;
padding: 20px 5px 30px 5px;
border-radius: 20px;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.12);
.topLeft {
display: flex;
}
.selectPart {
width: 370px;
justify-content: left;
span {
white-space: nowrap;
}
}
}
}
.removeModule {
position: absolute;
top: 2px;
right: 5px;
color: rgba(0, 0, 0, 0);
cursor: pointer;
transition: color 0.5s;
}
&:hover {
.removeModule {
color: rgba(0, 0, 0, 1);
}
}
.headerPart {
padding: 2px 20px;
}
}
.selectPart {
display: flex;
justify-content: space-between;
align-items: center;
height: 40px;
margin-right: 20px;
width: 340px;
margin: 4px 0;
span {
margin-right: 10px;
}
.statAnalysisSelect {
width: 200px;
:deep(.el-select__wrapper) {
height: 40px;
}
:deep(.el-input__inner) {
height: 38px;
}
}
.max,
.min,
.average {
border-radius: 6px;
height: 40px;
width: 72px;
text-align: center;
line-height: 40px;
}
.max {
background: rgba(254, 55, 49, 0.2);
border: 1px solid #fe3731;
color: #fe3731;
}
.min {
background: rgba(0, 160, 150, 0.2);
border: 1px solid #00a096;
color: #00a096;
}
.average {
background: rgba(0, 100, 170, 0.2);
border: 1px solid #0064aa;
color: #0064aa;
}
.customName {
width: 360px;
}
}
#myEChart {
width: 100%;
height: 300px;

View File

@ -199,10 +199,7 @@ const option: any = {
type: 'value',
},
series: [],
grid: {
left: '3%',
right: '3%',
},
grid: {},
}
const customName = reactive(['1'])
@ -222,10 +219,7 @@ onMounted(() => {
type: 'line',
},
],
grid: {
left: '3%',
right: '3%',
},
grid: {},
})
}
queryWindTurbines()
@ -487,29 +481,31 @@ const timestampToTime = (timestamp: any) => {
height: 38px;
}
}
.max,
.min,
.average {
border-radius: 6px;
height: 40px;
width: 72px;
text-align: center;
line-height: 40px;
}
.max {
background: rgba(254, 55, 49, 0.2);
border: 1px solid #fe3731;
color: #fe3731;
}
.min {
background: rgba(0, 160, 150, 0.2);
border: 1px solid #00a096;
color: #00a096;
}
.average {
background: rgba(0, 100, 170, 0.2);
border: 1px solid #0064aa;
color: #0064aa;
.calc {
.max,
.min,
.average {
border-radius: 6px;
height: 40px;
width: 72px;
text-align: center;
line-height: 40px;
}
.max {
background: rgba(254, 55, 49, 0.2);
border: 1px solid #fe3731;
color: #fe3731;
}
.min {
background: rgba(0, 160, 150, 0.2);
border: 1px solid #00a096;
color: #00a096;
}
.average {
background: rgba(0, 100, 170, 0.2);
border: 1px solid #0064aa;
color: #0064aa;
}
}
}
.dialog-footer button:first-child {
@ -532,6 +528,9 @@ const timestampToTime = (timestamp: any) => {
.headerPart {
padding: 2px 20px;
}
.topLeft {
display: flex !important;
}
}
#myEChart {
width: 100%;