You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1270 lines
50 KiB

<template>
<div class="shore-power-usage">
4 weeks ago
<template v-if="pageType === 'realtime'">
<!-- Average Mode -->
<template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;">
<div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @click.stop />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
4 weeks ago
</div>
4 weeks ago
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
4 weeks ago
</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="right average" style="width: 40%;">
<div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @click.stop />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@click.stop />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
1 month ago
</div>
4 weeks ago
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
4 weeks ago
</template>
<!-- Magnify Mode -->
<div v-if="displayMode === 'magnify'" class="magnify">
<div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-button type="primary" @click.stop="pageType = 'history'">历史查询</el-button>
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
4 weeks ago
</div>
4 weeks ago
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color"
:magnify-mode="true" />
4 weeks ago
</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="one-row">
<div v-for="card in cards.filter(c => c.id !== selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'overview'" class="time-range-selector">
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
1 month ago
</div>
4 weeks ago
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
1 month ago
</div>
</div>
</div>
</div>
1 month ago
</template>
4 weeks ago
<template v-if="pageType === 'history'">
<!-- Average Mode -->
<template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;">
<div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
4 weeks ago
</div>
4 weeks ago
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
4 weeks ago
</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="right average" style="width: 40%;">
<div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" @click.stop />
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
1 month ago
</div>
4 weeks ago
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
1 month ago
</div>
</div>
</div>
4 weeks ago
</template>
<!-- Magnify Mode -->
<div v-if="displayMode === 'magnify'" class="magnify">
<div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" v-model="dateRange" type="yearrange"
range-separator="至" start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @change="handleSelectDate" />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" v-model="dateRange" type="monthrange"
range-separator="至" start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM"
value-format="YYYY-MM" class="date-range-picker" @change="handleSelectDate" />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" v-model="dateRange" type="week"
range-separator="至" start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" class="date-range-picker" @change="handleSelectDate" /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@change="handleSelectDate" />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="pageType = 'realtime'">实时显示</el-button>
<!-- <button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button> -->
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
4 weeks ago
</div>
4 weeks ago
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ historyData.totalPower }}</div>
<div class="overview-label">区间用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title" :color="card.color"
:magnify-mode="true" />
4 weeks ago
</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="one-row">
<div v-for="card in cards.filter(c => c.id !== selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">{{ card.title }}</span>
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'overview'" class="time-range-selector">
<!-- <button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button> -->
1 month ago
</div>
4 weeks ago
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
1 month ago
</div>
4 weeks ago
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ historyData.totalPower }}</div>
<div class="overview-label">区间用电(千瓦时)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.fuel }}</div>
<div class="overview-label">减少燃油()</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.co2 }}</div>
<div class="overview-label">减少二氧化碳排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.pm25 }}</div>
<div class="overview-label">减少PM2.5排放(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.nox }}</div>
<div class="overview-label">减少氮氧化物(千克)</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ historyData.so2 }}</div>
<div class="overview-label">减少二氧化硫(千克)</div>
</div>
1 month ago
</div>
4 weeks ago
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title"
:color="card.color" />
1 month ago
</div>
</div>
</div>
</div>
4 weeks ago
</template>
</div>
</template>
<script setup lang="ts">
1 month ago
import { ref, onMounted, onBeforeUnmount } from 'vue'
4 weeks ago
import WaveLineChart from './charts/WaveLineChart.vue'
import { MapApi } from "@/api/shorepower/map";
import dayjs from 'dayjs';
4 weeks ago
import { RealtimeDeviceData } from '@/types/shorepower';
4 weeks ago
import { formatTimestamp, parseRangeToTimestamp } from './utils';
4 weeks ago
4 weeks ago
interface Props {
realtimeDeviceData: RealtimeDeviceData[];
activeHeadGroup?: number;
}
const props = defineProps<Props>()
// 定义组件属性
interface ChartDataItem {
name: string;
value: number;
}
1 month ago
interface CardInfo {
id: string;
title: string;
type: 'overview' | 'chart';
unit?: string;
color?: string;
4 weeks ago
value: string;
}
interface ShorePowerUsageData {
totalPower: number; // 累计用电(千瓦时)
fuelReduction: number; // 减少燃油(吨)
co2Reduction: number; // 减少二氧化碳排放(千克)
pm25Reduction: number; // 减少PM2.5排放(千克)
noxReduction: number; // 减少氮氧化物(千克)
so2Reduction: number; // 减少二氧化硫(千克)
}
export interface MeasureDataRealtimeRespVO {
/**
* 创建时间
*/
createTime: Date;
/**
* 设备编号code
*/
deviceCode: string;
/**
* 设备编号
*/
deviceId: number;
/**
* 设备名称
*/
deviceName: string;
/**
* 设备状态
*/
deviceStatus: number;
/**
* 编号
*/
id: number;
/**
* 增量费用
*/
incrementCost?: number;
/**
* 增量值
*/
incrementValue: number;
/**
* 采集时间
*/
measureTime: Date;
/**
* 采集值
*/
measureValue: number;
1 month ago
}
4 weeks ago
export interface deviceData {
[key: string]: MeasureDataRealtimeRespVO
}
4 weeks ago
// const props = defineProps<Props>()
4 weeks ago
const pageType = ref<'realtime' | 'history'>('realtime')
const dateRange = ref<string[]>([])
4 weeks ago
const totalPower = ref<string>('0')
const fuelReduction = ref<string>('0')
const co2Reduction = ref<string>('0')
const pm25Reduction = ref<string>('0')
const noxReduction = ref<string>('0')
const so2Reduction = ref<string>('0')
const displayMode = ref<'average' | 'magnify'>('magnify')
1 month ago
const selectedCard = ref<string>('overview')
// 创建统一的模拟数据源
const commonChartData = ref<ChartDataItem[]>([])
4 weeks ago
// 实时数据图表数组
const totalPowerChartData = ref<ChartDataItem[]>([])
const co2ReductionChartData = ref<ChartDataItem[]>([])
const so2ReductionChartData = ref<ChartDataItem[]>([])
const noxReductionChartData = ref<ChartDataItem[]>([])
const pm25ReductionChartData = ref<ChartDataItem[]>([])
const fuelReductionChartData = ref<ChartDataItem[]>([])
4 weeks ago
const h_totalPowerChartData = ref<ChartDataItem[]>([])
const h_co2ReductionChartData = ref<ChartDataItem[]>([])
const h_so2ReductionChartData = ref<ChartDataItem[]>([])
const h_noxReductionChartData = ref<ChartDataItem[]>([])
const h_pm25ReductionChartData = ref<ChartDataItem[]>([])
const h_fuelReductionChartData = ref<ChartDataItem[]>([])
const historyData = ref({
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
})
4 weeks ago
const chartData = ref({
'realtime': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'day': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'week': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'month': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'quarter': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'year': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
})
4 weeks ago
function aggregateByIndex(data: RawItem[][]): ChartDataItem[] {
if (data.length === 0) return [];
// 找出最大长度(防止越界)
const maxLength = Math.max(...data.map(group => group.length));
const result: ChartDataItem[] = [];
for (let i = 0; i < maxLength; i++) {
let totalValue = 0;
let timeLabel = '';
for (const group of data) {
const item = group[i];
if (item) {
totalValue += item.measureValue;
// 第一次遇到有效时间就记录(假设所有 group[i].measureTime 相同)
if (!timeLabel) {
timeLabel = formatTimestamp(item.measureTime);
}
}
}
// 如果这一列完全没有数据,跳过(或根据需求处理)
if (timeLabel) {
result.push({
name: timeLabel,
value: totalValue
});
}
}
return result;
}
const calculateTotalDiff = (data) => {
return data.reduce((total, subArray) => {
if (subArray.length === 0) {
return total; // 空数组跳过
}
const first = subArray[0].measureValue;
const last = subArray[subArray.length - 1].measureValue;
return total + (last - first);
}, 0);
}
const handleSelectDate = async (range: string[] | null) => {
if (!range || range.length !== 2) {
// 清空逻辑
return;
}
const timestamps = parseRangeToTimestamp(range, historyTimeGranularity.value);
if (!timestamps) return;
const [start, end] = timestamps;
let timeTypeId = 0
switch (historyTimeGranularity.value) {
case 'day':
timeTypeId = 3
break
/* case 'week':
timeTypeId = 4
break */
case 'month':
timeTypeId = 4
break
case 'year':
timeTypeId = 5
break
default:
timeTypeId = 0
break
}
const params = {
start,
end,
timeType: timeTypeId // 或者映射为后端需要的 code
};
console.log(params)
const res = await MapApi.getByStartAndEndTimeAndTimeType(params)
const array = Object.values(res);
if (array.length === 0) {
historyData.value = {
totalPower: 0,
fuel: 0,
co2: 0,
pm25: 0,
nox: 0,
so2: 0,
}
h_fuelReductionChartData.value = [
{ name: '', value: 0 }
]
h_co2ReductionChartData.value = [
{ name: '', value: 0 }
]
h_so2ReductionChartData.value = [
{ name: '', value: 0 }
]
h_noxReductionChartData.value = [
{ name: '', value: 0 }
]
h_pm25ReductionChartData.value = [
{ name: '', value: 0 }
]
return;
}
console.log(array)
const totalDiff = calculateTotalDiff(array);
console.log(totalDiff)
historyData.value = {
totalPower: Number(totalDiff.toFixed(2)),
fuel: Number((totalDiff * 0.22 / 1).toFixed(2)), // 转化为千克
co2: Number((totalDiff * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((totalDiff * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((totalDiff * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((totalDiff * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
const chartData = aggregateByIndex(array);
const fuelChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 0.22 / 1).toFixed(2)) }))
h_fuelReductionChartData.value = fuelChartData;
const co2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 670 / 1000).toFixed(2)) }))
h_co2ReductionChartData.value = co2ChartData;
const pm25ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 1.46 / 1000).toFixed(2)) }))
h_pm25ReductionChartData.value = pm25ChartData;
const noxChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 18.1 / 1000).toFixed(2)) }))
h_noxReductionChartData.value = noxChartData;
const so2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 10.5 / 1000).toFixed(2)) }))
h_so2ReductionChartData.value = so2ChartData;
};
4 weeks ago
// 获取对应卡片的图表数据
const getChartData = (cardId: string): ChartDataItem[] => {
switch (cardId) {
case 'overview':
return totalPowerChartData.value
case 'fuel':
return fuelReductionChartData.value
case 'co2':
return co2ReductionChartData.value
case 'pm25':
return pm25ReductionChartData.value
case 'nox':
return noxReductionChartData.value
case 'so2':
return so2ReductionChartData.value
default:
return commonChartData.value
}
}
4 weeks ago
// 获取对应卡片的图表数据
const getHistoryChartData = (cardId: string): ChartDataItem[] => {
switch (cardId) {
case 'overview':
return totalPowerChartData.value
case 'fuel':
return h_fuelReductionChartData.value
case 'co2':
return h_co2ReductionChartData.value
case 'pm25':
return h_pm25ReductionChartData.value
case 'nox':
return h_noxReductionChartData.value
case 'so2':
return h_so2ReductionChartData.value
default:
return commonChartData.value
}
}
4 weeks ago
// 时间范围选项
4 weeks ago
const timeRange = ref<'realtime' | 'day' | 'month' | 'quarter' | 'year'>('day')
4 weeks ago
// 时间范围选项定义
const timeRangeOptions = [
4 weeks ago
{ value: 'day', label: '本日' },
{ value: 'month', label: '本月' },
4 weeks ago
{ value: 'year', label: '本年' },
{ value: 'realtime', label: '总' },
4 weeks ago
]
// 处理时间范围选择
const handleTimeRangeChange = (range: 'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year') => {
timeRange.value = range
// 这里可以添加根据时间范围切换数据源的逻辑
}
4 weeks ago
// 历史模式时间颗粒度选项
const historyTimeGranularity = ref<'year' | 'month' | 'week' | 'day'>('day')
// 历史模式时间颗粒度选项定义
const historyTimeGranularityOptions = [
{ value: 'year', label: '年' },
{ value: 'month', label: '月' },
// { value: 'week', label: '周' },
{ value: 'day', label: '日' }
]
// 处理历史模式时间颗粒度选择
const handleHistoryTimeGranularityChange = (granularity: 'year' | 'month' | 'week' | 'day') => {
historyTimeGranularity.value = granularity
// 这里可以添加根据时间颗粒度切换数据源的逻辑
}
// 处理日期范围确认
const handleDateRangeConfirm = () => {
// 这里可以添加处理日期范围确认的逻辑
console.log('日期范围已确认,当前颗粒度:', historyTimeGranularity.value)
// 可以调用API获取选中日期范围内的数据
}
1 month ago
// 定义卡片信息
const cards = ref<CardInfo[]>([
{
id: 'overview',
title: '总览',
type: 'overview'
},
{
id: 'fuel',
title: '减少燃油(吨)',
type: 'chart',
unit: '吨',
4 weeks ago
color: '#4CAF50', // 绿色 - 代表节能、清洁能源
value: 'fuel'
1 month ago
},
{
id: 'co2',
title: '减少CO₂排放(千克)',
type: 'chart',
unit: '千克',
4 weeks ago
color: '#2E7D32', // 深绿色/蓝绿 - 温室气体(国际常用低碳绿色系)
value: 'co2'
1 month ago
},
{
id: 'pm25',
title: '减少PM2.5排放(千克)',
type: 'chart',
unit: '千克',
4 weeks ago
color: '#9E9E9E', // 灰色 - 颗粒物(国际通用)
value: 'pm25'
1 month ago
},
{
id: 'nox',
title: '减少NOₓ排放(千克)',
type: 'chart',
unit: '千克',
4 weeks ago
color: '#FF9800', // 橙色 - 氮氧化物(国际通用)
value: 'nox'
1 month ago
},
{
id: 'so2',
title: '减少SO₂排放(千克)',
type: 'chart',
unit: '千克',
4 weeks ago
color: '#FFEB3B', // 黄色 - 二氧化硫(国际通用)
value: 'so2'
1 month ago
}
])
const handleSelectCard = (cardId: string) => {
// 如果当前已经是放大模式且点击的是当前选中的大卡片,则返回平均模式
if (displayMode.value === 'magnify' && selectedCard.value === cardId) {
displayMode.value = 'average'
} else {
// 否则进入放大模式并选中指定卡片
displayMode.value = 'magnify'
selectedCard.value = cardId
}
}
// 生成初始数据
const generateInitialData = () => {
const now = new Date()
const data: ChartDataItem[] = []
// 生成过去5分钟的数据,每10秒一条
for (let i = 29; i >= 0; i--) {
const time = new Date(now.getTime() - i * 10000)
data.push({
name: time.toLocaleTimeString(),
value: Math.floor(Math.random() * 100) + 10
})
}
return data
}
// 定时器引用
4 weeks ago
let timer: ReturnType<typeof setInterval>;
1 month ago
// 追加新数据
const appendNewData = () => {
const now = new Date()
commonChartData.value.push({
name: now.toLocaleTimeString(),
value: Math.floor(Math.random() * 100) + 10
})
}
4 weeks ago
const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]) => {
4 weeks ago
try {
4 weeks ago
const res: RealtimeDeviceData[] = realtimeDeviceData.filter(item => item.deviceCode.includes('Kwh'));
const ids = res.map(item => item.id);
4 weeks ago
console.log(ids);
const params = {
ids: ids.join(','),
// year: new Date().getFullYear()
}
4 weeks ago
if (!params.ids) return;
4 weeks ago
let yearDataRes: RealtimeDeviceData[] = []
let weekDataRes: RealtimeDeviceData[] = []
let monthDataRes: RealtimeDeviceData[] = []
let dayDataRes: RealtimeDeviceData[] = []
try {
yearDataRes = await MapApi.getYearDataByIdList(params);
weekDataRes = await MapApi.getWeekDataByIdList(params);
monthDataRes = await MapApi.getMonthDataByIdList(params);
dayDataRes = await MapApi.getDayDataByIdList(params);
} catch (error) {
console.log(error);
}
4 weeks ago
// const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params);
4 weeks ago
const realTimeSum = res.reduce((acc, item) => acc + item.measureValue, 0);
4 weeks ago
const daySum = realTimeSum - Object.values(dayDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const weekSum = realTimeSum - Object.values(weekDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const monthSum = realTimeSum - Object.values(monthDataRes).reduce((acc, item) => acc + item.measureValue, 0);
// const quarterSum = Object.values(quarterDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const yearSum = realTimeSum - Object.values(yearDataRes).reduce((acc, item) => acc + item.measureValue, 0);
// totalPower.value = realTimeSum.toFixed(2)
// co2Reduction.value = (realTimeSum * 670 / 1000000).toFixed(2) // 克转化为吨
// so2Reduction.value = (realTimeSum * 10.5 / 1000000).toFixed(2) // 克转化为吨
// noxReduction.value = (realTimeSum * 18.1 / 1000000).toFixed(2) // 克转化为吨
// pm25Reduction.value = (realTimeSum * 1.46 / 1000000).toFixed(2) // 克转化为吨
// fuelReduction.value = (realTimeSum * 0.22 / 1000).toFixed(2) // 转化为吨
4 weeks ago
const incrementRealTimeSum = Object.values(res).reduce((acc, item) => acc + item.measureValue, 0);
4 weeks ago
chartData.value.realtime = {
totalPower: Number(realTimeSum.toFixed(2)),
fuel: Number((realTimeSum * 0.22 / 1).toFixed(2)), // 转化为千克
co2: Number((realTimeSum * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((realTimeSum * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((realTimeSum * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((realTimeSum * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
chartData.value.day = {
totalPower: Number(daySum.toFixed(2)),
fuel: Number((daySum * 0.22 / 1).toFixed(2)), // 转化为千克
co2: Number((daySum * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((daySum * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((daySum * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((daySum * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
chartData.value.week = {
totalPower: Number(weekSum.toFixed(2)),
fuel: Number((weekSum * 0.22 / 1).toFixed(2)), // 转化为吨
co2: Number((weekSum * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((weekSum * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((weekSum * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((weekSum * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
chartData.value.month = {
totalPower: Number(monthSum.toFixed(2)),
fuel: Number((monthSum * 0.22 / 1).toFixed(2)), // 转化为吨
co2: Number((monthSum * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((monthSum * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((monthSum * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((monthSum * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
chartData.value.year = {
totalPower: Number(yearSum.toFixed(2)),
fuel: Number((yearSum * 0.22 / 1).toFixed(2)), // 转化为千克
co2: Number((yearSum * 670 / 1000).toFixed(2)), // 克转化为千克
pm25: Number((yearSum * 1.46 / 1000).toFixed(2)), // 克转化为千克
nox: Number((yearSum * 18.1 / 1000).toFixed(2)), // 克转化为千克
so2: Number((yearSum * 10.5 / 1000).toFixed(2)), // 克转化为千克
}
// 记录实时数据到图表数组
const now = new Date()
const timestamp = now.toLocaleTimeString()
// 计算各指标值(转换为合适的单位)并保留两位小数
const totalPowerValue = Number(incrementRealTimeSum.toFixed(2))
const co2Value = Number((incrementRealTimeSum * 670 / 1000).toFixed(2)) // 克转化为千克
const so2Value = Number((incrementRealTimeSum * 10.5 / 1000).toFixed(2)) // 克转化为千克
const noxValue = Number((incrementRealTimeSum * 18.1 / 1000).toFixed(2)) // 克转化为千克
const pm25Value = Number((incrementRealTimeSum * 1.46 / 1000).toFixed(2)) // 克转化为千克
const fuelValue = Number((incrementRealTimeSum * 0.22 / 1).toFixed(2)) // 转化为千克
// 添加到各图表数据数组
totalPowerChartData.value.push({ name: timestamp, value: totalPowerValue })
co2ReductionChartData.value.push({ name: timestamp, value: co2Value })
so2ReductionChartData.value.push({ name: timestamp, value: so2Value })
noxReductionChartData.value.push({ name: timestamp, value: noxValue })
pm25ReductionChartData.value.push({ name: timestamp, value: pm25Value })
fuelReductionChartData.value.push({ name: timestamp, value: fuelValue })
// 保持数组最多包含20个数据点(对应约3分钟,每10秒一条)
const maxDataPoints = 10
totalPowerChartData.value = totalPowerChartData.value.slice(-maxDataPoints)
co2ReductionChartData.value = co2ReductionChartData.value.slice(-maxDataPoints)
so2ReductionChartData.value = so2ReductionChartData.value.slice(-maxDataPoints)
noxReductionChartData.value = noxReductionChartData.value.slice(-maxDataPoints)
pm25ReductionChartData.value = pm25ReductionChartData.value.slice(-maxDataPoints)
fuelReductionChartData.value = fuelReductionChartData.value.slice(-maxDataPoints)
// 更新通用图表数据(保持向后兼容)
commonChartData.value.push({ name: timestamp, value: totalPowerValue })
commonChartData.value = commonChartData.value.slice(-maxDataPoints)
} catch (error) {
console.error(error)
}
}
4 weeks ago
/* const handleGetAllDeviceDataByTimeRange = async () => {
4 weeks ago
try {
// 当前时间的时间戳(毫秒)
const nowTimestamp = dayjs().valueOf().toString();
// 两天前的时间戳(毫秒)
const twoDaysAgoTimestamp = dayjs().subtract(2, 'day').valueOf().toString();
4 weeks ago
4 weeks ago
} catch (error) {
console.error(error)
}
4 weeks ago
} */
4 weeks ago
4 weeks ago
watch(() => props.realtimeDeviceData, (newValue) => {
handleGetRealTimeAllData(newValue)
})
1 month ago
// 组件挂载时初始化数据和定时器
onMounted(() => {
4 weeks ago
// handleGetAllDeviceDataByTimeRange()
4 weeks ago
handleGetRealTimeAllData(props.realtimeDeviceData)
1 month ago
})
4 weeks ago
// 监听 activeHeadGroup 变化,当组件变为可见时触发 resize
watch(() => props.activeHeadGroup, (newVal) => {
if (newVal === 1) {
// 延迟执行 resize,确保 DOM 已经更新
setTimeout(() => {
window.dispatchEvent(new Event('resize'))
}, 100)
}
}, { immediate: true })
1 month ago
// 组件销毁前清除定时器
onBeforeUnmount(() => {
4 weeks ago
1 month ago
})
</script>
1 month ago
<style lang="scss" scoped>
.shore-power-usage {
display: flex;
height: 100%;
gap: 10px;
}
1 month ago
4 weeks ago
.card .card-content {
overflow: hidden !important;
}
4 weeks ago
.average {
.overview-value {
font-size: 36px;
}
.overview-label {
font-size: 18px;
}
}
1 month ago
.magnify {
4 weeks ago
width: 40%;
1 month ago
padding: 12px;
height: calc(100vh - 72px);
position: absolute;
top: 72px;
left: 0px;
display: flex;
flex-direction: column;
/* background-color: red; */
gap: 8px;
overflow-y: auto;
.big {
height: 70%;
.card {
height: 100%;
}
.overview-value {
4 weeks ago
font-size: 48px;
1 month ago
}
.overview-label {
4 weeks ago
font-size: 28px;
1 month ago
}
}
.one-row {
height: 30%;
display: flex;
gap: 8px;
4 weeks ago
.card .card-title .title-text {
font-size: 14px;
}
1 month ago
.card {
flex: 1;
}
}
}
.card {
padding: 6px;
cursor: pointer;
}
4 weeks ago
.time-range-selector {
display: flex;
gap: 8px;
}
.show-value {
font-size: 18px;
font-weight: 600;
font-weight: bold;
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
color: #FFF;
}
.show-value-label {
font-size: 12px;
font-weight: 600;
font-weight: bold;
}
.show-value-value {
font-size: 18px;
font-weight: 600;
font-weight: bold;
}
.time-range-btn {
padding: 4px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
background: transparent;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.time-range-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.time-range-btn.active {
background: rgba(76, 175, 80, 0.8);
border-color: #4CAF50;
color: #fff;
}
1 month ago
.right {
/* gap: 0 */
}
</style>