552 lines
18 KiB
Vue
552 lines
18 KiB
Vue
<template>
|
|
<div class="contain">
|
|
<el-header class="headerPart">
|
|
<div class="topLeft">
|
|
<div class="selectPart">
|
|
<span>{{ t('statAnalysis.deviceId') }}</span>
|
|
<el-select v-model="statAnalysisDeviceId" :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 v-if="hasAuthority" class="selectPart">
|
|
<span>风速来源</span>
|
|
<el-select v-model="statAnalysisSpeedSource" placeholder="请选择风速来源" class="statAnalysisSelect">
|
|
<el-option v-for="v in statAnalysisSelectOptions.speedSource" :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">
|
|
<div class="topLeft">
|
|
<div class="selectPart">
|
|
<span>{{ t('statAnalysis.time') }}</span>
|
|
<el-date-picker
|
|
class="datetime-picker"
|
|
v-model="statAnalysisTime"
|
|
type="datetimerange"
|
|
value-format="YYYY-MM-DD HH:mm:ss"
|
|
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)]"
|
|
:teleported="false"
|
|
:shortcuts="shortcuts"
|
|
/>
|
|
</div>
|
|
|
|
<div class="selectPart">
|
|
<span>{{ t('statAnalysis.madeinfatory') }}</span>
|
|
<el-select
|
|
v-model="statAnalysisFatory"
|
|
:placeholder="'请选择' + t('statAnalysis.madeinfatory')"
|
|
class="statAnalysisSelect"
|
|
clearable
|
|
>
|
|
<el-option
|
|
v-for="v in statAnalysisFatoryList"
|
|
:key="v.id"
|
|
:label="v.madeinfactory + v.model"
|
|
:value="`${v.madeinfactory}:${v.model}`"
|
|
></el-option>
|
|
</el-select>
|
|
</div>
|
|
<div class="selectPart">
|
|
<span> 显示曲线 </span>
|
|
<el-switch v-model="AvgWindSpeedSwitch" @change="changeUpdateAvgWindSpeed"></el-switch>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</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">
|
|
import { reactive, ref, onMounted } from 'vue'
|
|
import { useI18n } from 'vue-i18n'
|
|
import { queryWindTurbinesPages, historyReq, powerCurveExport } from '/@/api/backend/statAnalysis/request'
|
|
import { theoreticalpowerCurveList, powerCurveQuery } from '/@/api/backend/theoreticalpowerCurve/request'
|
|
import { ElMessage } from 'element-plus'
|
|
import * as echarts from 'echarts'
|
|
import { getCutDecimalsValue } from '/@/views/backend/equipment/airBlower/utils'
|
|
|
|
const { t } = useI18n()
|
|
const AvgWindSpeedSwitch = ref(false)
|
|
const chartType = ref('scatter') // 默认散点图
|
|
const statAnalysisFatory = ref('')
|
|
const statAnalysisFatoryList: any = ref([])
|
|
const hasAuthority = ref(false)
|
|
const statAnalysisSpeedSource = ref('AvgWindSpeed_10min')
|
|
const statAnalysisDeviceId = ref('')
|
|
|
|
import { useAdminInfo } from '/@/stores/adminInfo'
|
|
const adminInfo = useAdminInfo()
|
|
|
|
hasAuthority.value = Object.values(adminInfo.authorities).includes(105)
|
|
const statAnalysisSelectOptions: any = reactive({
|
|
speedSource: [
|
|
{ label: '原始风速', value: 'AvgWindSpeed_10min' },
|
|
{ label: '处理后风速', value: 'AvgWindSpeedCal_10min' },
|
|
],
|
|
deviceId: [],
|
|
})
|
|
|
|
const changeUpdateAvgWindSpeed = (val: any) => {
|
|
chartType.value = val ? 'line' : 'scatter'
|
|
updateChart()
|
|
}
|
|
|
|
const seriesDataInit = ref([])
|
|
const calculateData: any = ref([])
|
|
const updateChart = () => {
|
|
const series = {
|
|
type: chartType.value,
|
|
data: chartType.value === 'scatter' ? seriesDataInit.value : calculateData.value,
|
|
name: '实际功率',
|
|
symbolSize: 5,
|
|
symbol: 'circle',
|
|
}
|
|
option.series[0] = series
|
|
if (!option.legend.data.includes('实际功率')) {
|
|
option.legend.data.push('实际功率')
|
|
}
|
|
chart.value.setOption(option)
|
|
}
|
|
|
|
const getFormattedDate = (offset: number) => {
|
|
const date = new Date()
|
|
date.setDate(date.getDate() + offset)
|
|
const year = date.getFullYear()
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
return `${year}-${month}-${day}`
|
|
}
|
|
|
|
const statAnalysisTime = ref([getFormattedDate(0) + ' 00:00:00', getFormattedDate(0) + ' 23:59:59'])
|
|
const chartContainer = ref<HTMLElement | null>(null)
|
|
|
|
const option: any = {
|
|
tooltip: {
|
|
formatter: function (params: any) {
|
|
return '风速:' + params.value[0] + 'm/s ' + ' <br/>' + '功率:' + params.value[1] + 'KW'
|
|
},
|
|
},
|
|
legend: {
|
|
icon: 'circle',
|
|
itemGap: 20,
|
|
itemWidth: 8,
|
|
itemHeight: 8,
|
|
selectedMode: true,
|
|
data: [],
|
|
},
|
|
xAxis: {
|
|
type: 'value',
|
|
name: 'm/s',
|
|
splitLine: {
|
|
show: false,
|
|
},
|
|
axisLine: {
|
|
show: true,
|
|
},
|
|
axisTick: {
|
|
show: true,
|
|
},
|
|
},
|
|
yAxis: {
|
|
name: 'KW',
|
|
splitLine: {
|
|
show: false,
|
|
},
|
|
axisLine: {
|
|
show: false,
|
|
},
|
|
axisTick: {
|
|
show: false,
|
|
},
|
|
},
|
|
series: [],
|
|
grid: {},
|
|
}
|
|
|
|
const chart: any = ref(null)
|
|
onMounted(() => {
|
|
if (chartContainer.value) {
|
|
chart.value = echarts.init(chartContainer.value)
|
|
chart.value.on('legendselectchanged', function (event: any) {
|
|
var isSelected = event.selected[event.name]
|
|
var series = chart.value.getOption().series
|
|
for (var i = 0; i < series.length; i++) {
|
|
if (series[i].name === event.name) {
|
|
series[i].show = isSelected
|
|
}
|
|
}
|
|
chart.value.setOption({ series: series })
|
|
})
|
|
}
|
|
queryWindTurbines().then((res: any) => {
|
|
statAnalysisDeviceId.value = res.value
|
|
statAnalysisOperate()
|
|
})
|
|
queryfactoery()
|
|
})
|
|
|
|
const queryfactoery = () => {
|
|
theoreticalpowerCurveList().then((res: any) => {
|
|
if (res.code == 200) {
|
|
statAnalysisFatoryList.value = res.rows
|
|
}
|
|
})
|
|
}
|
|
|
|
const queryWindTurbines = () => {
|
|
return new Promise((resolve) => {
|
|
queryWindTurbinesPages()
|
|
.then((res) => {
|
|
if (res.code == 200) {
|
|
statAnalysisSelectOptions.deviceId = res.data.map((item: any) => {
|
|
return {
|
|
value: `${item.madeinfactory}:${item.model}:${item.irn}`,
|
|
label: item.name ?? '-',
|
|
iotModelId: item.modelId,
|
|
}
|
|
})
|
|
resolve(statAnalysisSelectOptions.deviceId[0])
|
|
} else {
|
|
ElMessage.error(res.msg)
|
|
}
|
|
})
|
|
.catch((err) => {
|
|
ElMessage.error(err ?? '查询失败')
|
|
})
|
|
})
|
|
}
|
|
|
|
window.onresize = () => {
|
|
chart.value.resize()
|
|
}
|
|
|
|
const shortcuts = [
|
|
{
|
|
text: '今天',
|
|
value: () => {
|
|
const start = getFormattedDate(0) + ' 00:00:00'
|
|
const end = getFormattedDate(0) + ' 23:59:59'
|
|
return [start, end]
|
|
},
|
|
},
|
|
{
|
|
text: '昨天',
|
|
value: () => {
|
|
const start = getFormattedDate(-1) + ' 00:00:00'
|
|
const end = getFormattedDate(-1) + ' 23:59:59'
|
|
return [start, end]
|
|
},
|
|
},
|
|
{
|
|
text: '前3天',
|
|
value: () => {
|
|
const start = getFormattedDate(-3) + ' 00:00:00'
|
|
const end = getFormattedDate(-1) + ' 23:59:59'
|
|
return [start, end]
|
|
},
|
|
},
|
|
{
|
|
text: '本周',
|
|
value: () => {
|
|
return getDateRange('week')
|
|
},
|
|
},
|
|
{
|
|
text: '本月',
|
|
value: () => {
|
|
return getDateRange('month')
|
|
},
|
|
},
|
|
]
|
|
|
|
const getDateRange = (type: 'week' | 'month') => {
|
|
const today = new Date()
|
|
if (type === 'week') {
|
|
const dayOfWeek = today.getDay()
|
|
const startOfWeek = new Date(today)
|
|
startOfWeek.setDate(today.getDate() - dayOfWeek + (dayOfWeek === 0 ? -6 : 1))
|
|
startOfWeek.setHours(0, 0, 0, 0)
|
|
const endOfWeek = new Date(startOfWeek)
|
|
endOfWeek.setDate(startOfWeek.getDate() + 6)
|
|
endOfWeek.setHours(23, 59, 59, 999)
|
|
return [startOfWeek, endOfWeek]
|
|
}
|
|
if (type === 'month') {
|
|
const startOfMonth = new Date(today.getFullYear(), today.getMonth(), 1)
|
|
startOfMonth.setHours(0, 0, 0, 0)
|
|
const endOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0)
|
|
endOfMonth.setHours(23, 59, 59, 999)
|
|
return [startOfMonth, endOfMonth]
|
|
}
|
|
}
|
|
|
|
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 = []
|
|
chart.value.setOption(option, { notMerge: true })
|
|
const requestData = {
|
|
devices: [
|
|
{
|
|
deviceId: deviceId,
|
|
attributes: [statAnalysisSpeedSource.value, 'AvgActivePower_10min'],
|
|
},
|
|
],
|
|
startTime: new Date(statAnalysisTime.value[0]).getTime(),
|
|
endTime: new Date(statAnalysisTime.value[1]).getTime(),
|
|
}
|
|
const params = statAnalysisFatory.value ? statAnalysisFatory.value : statAnalysisDeviceId.value
|
|
const promise1 = new Promise((resolve, reject) => {
|
|
const madeinfactory = params.split(':')[0]
|
|
const model = params.split(':')[1]
|
|
powerCurveQuery(madeinfactory, model)
|
|
.then((res) => {
|
|
if (res.code == 200) {
|
|
resolve(res.data)
|
|
} else {
|
|
isLoading.value = false
|
|
ElMessage.warning('查询失败')
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
resolve(error)
|
|
})
|
|
})
|
|
const promise2 = new Promise((resolve, reject) => {
|
|
historyReq(requestData)
|
|
.then((res) => {
|
|
if (res.code == 200) {
|
|
resolve(res.data)
|
|
} else {
|
|
isLoading.value = false
|
|
ElMessage.warning('查询失败')
|
|
}
|
|
})
|
|
.catch((error) => {
|
|
resolve(error)
|
|
})
|
|
})
|
|
|
|
Promise.all([promise1, promise2])
|
|
.then((results: any) => {
|
|
isLoading.value = false
|
|
const resData0 = results[1][statAnalysisDeviceId.value.split(':')[2]]
|
|
const resData1 = results[0]
|
|
if (resData0) {
|
|
const iGenPower = resData0['AvgActivePower_10min']['values']
|
|
const iWindSpeed = resData0[statAnalysisSpeedSource.value]['values']
|
|
if (!iWindSpeed.length) {
|
|
ElMessage.info(`实际功率数据为空`)
|
|
} else {
|
|
const seriesData = iGenPower.map((item: any, index: number) => {
|
|
return [getCutDecimalsValue(iWindSpeed[index], 2), getCutDecimalsValue(item, 2)]
|
|
})
|
|
// seriesData.sort((a: any, b: any) => {
|
|
// return a[0] - b[0]
|
|
// })
|
|
seriesDataInit.value = seriesData
|
|
calculateData.value = calculateAverages(seriesDataInit.value)
|
|
updateChart()
|
|
}
|
|
}
|
|
if (resData1.length) {
|
|
const seriesData = resData1.map((item: any) => {
|
|
return [getCutDecimalsValue(item.speed, 2), getCutDecimalsValue(item.power, 2)]
|
|
})
|
|
const series = {
|
|
type: 'line',
|
|
data: seriesData,
|
|
name: '理论功率',
|
|
smooth: true,
|
|
symbolSize: 0.1,
|
|
symbol: 'circle',
|
|
}
|
|
option.series.push(series)
|
|
option.legend.data.push('理论功率')
|
|
}
|
|
chart.value.setOption(option)
|
|
})
|
|
.catch((error) => {
|
|
console.log(error)
|
|
isLoading.value = false
|
|
ElMessage.warning(error)
|
|
})
|
|
}
|
|
|
|
const statAnalysisExport = () => {
|
|
const params = statAnalysisFatory.value ? statAnalysisFatory.value : statAnalysisDeviceId.value
|
|
const windSource = statAnalysisSelectOptions.speedSource.filter((item: any) => item.value == statAnalysisSpeedSource.value)
|
|
const requestData = {
|
|
devices: [
|
|
{
|
|
deviceId: statAnalysisDeviceId.value.split(':')[2],
|
|
attributes: [statAnalysisSpeedSource.value, 'AvgActivePower_10min'],
|
|
},
|
|
],
|
|
startTime: new Date(statAnalysisTime.value[0]).getTime(),
|
|
endTime: new Date(statAnalysisTime.value[1]).getTime(),
|
|
madeinfactory: params.split(':')[0],
|
|
model: params.split(':')[1],
|
|
windSource: windSource[0]['label'],
|
|
displayCurve: AvgWindSpeedSwitch.value ? 1 : 0,
|
|
}
|
|
powerCurveExport(requestData).then((res: any) => {
|
|
const downloadUrl = window.URL.createObjectURL(res)
|
|
const a = document.createElement('a')
|
|
a.href = downloadUrl
|
|
a.download = '功率曲线' + new Date().getTime()
|
|
document.body.appendChild(a)
|
|
a.click()
|
|
window.URL.revokeObjectURL(downloadUrl)
|
|
document.body.removeChild(a)
|
|
})
|
|
}
|
|
|
|
const calculateAverages = (data: any) => {
|
|
let maxWindSpeed = Math.max(...data.map((item: any) => item[0]))
|
|
let interval = 0.5
|
|
let result = []
|
|
|
|
for (let windSpeed = 0; windSpeed <= maxWindSpeed; windSpeed += interval) {
|
|
let sumPower = 0
|
|
let count = 0
|
|
|
|
for (let i = 0; i < data.length; i++) {
|
|
let currentWindSpeed = data[i][0]
|
|
let currentPower = data[i][1]
|
|
|
|
if (currentWindSpeed >= windSpeed && currentWindSpeed < windSpeed + interval) {
|
|
sumPower += currentPower
|
|
count++
|
|
}
|
|
}
|
|
|
|
if (count > 0) {
|
|
let averagePower = sumPower / count
|
|
result.push([windSpeed + interval, 2, getCutDecimalsValue(averagePower, 2)])
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
</script>
|
|
<style scoped lang="scss">
|
|
.contain {
|
|
height: 100%;
|
|
.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;
|
|
}
|
|
.customName {
|
|
width: fit-content;
|
|
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;
|
|
}
|
|
}
|
|
.dialog-footer button:first-child {
|
|
margin-right: 10px;
|
|
}
|
|
}
|
|
.topRight {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
.el-button {
|
|
width: 100px;
|
|
height: 40px;
|
|
}
|
|
}
|
|
}
|
|
.timeColumns {
|
|
max-height: 120px;
|
|
overflow: hidden;
|
|
.headerPart {
|
|
padding: 0px 10px 20px 20px;
|
|
}
|
|
&.expand {
|
|
max-height: max-content;
|
|
height: auto;
|
|
overflow: inherit;
|
|
}
|
|
}
|
|
.timeColumns.expand + div.ralIcon {
|
|
transform: rotate(-90deg);
|
|
}
|
|
.ralIcon {
|
|
width: 100%;
|
|
text-align: center;
|
|
transform: rotate(90deg);
|
|
}
|
|
#myEChart {
|
|
width: 100%;
|
|
height: 300px;
|
|
}
|
|
}
|
|
</style>
|