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.
523 lines
14 KiB
523 lines
14 KiB
<template>
|
|
<div class="shore-power-usage">
|
|
<div 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">岸电使用率</span>
|
|
</div>
|
|
<div class="show-value">
|
|
|
|
<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>
|
|
<!-- <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
|
|
''}}量:</span>
|
|
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> -->
|
|
<el-button class="close-btn" size="small" type="text" @click.stop="handleClose()">x</el-button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
<div style="display: flex; align-items: center; justify-content: space-between;">
|
|
<div class="time-range-item">{{ startTimeDisplay }} 至 {{ realtimeDeviceDataTime }}</div>
|
|
|
|
<div v-show="timeRange !== 'year'" class="comparison-type-selector">
|
|
<button :class="['comparison-type-btn', { active: comparisonType === 'chain' }]"
|
|
@click="comparisonType = 'chain'">
|
|
环比
|
|
</button>
|
|
<button :class="['comparison-type-btn', { active: comparisonType === 'yearOnYear' }]"
|
|
@click="comparisonType = 'yearOnYear'">
|
|
同比
|
|
</button>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div v-if="timeRange === 'year'" class="card-content">
|
|
<ComparisonBarChart :chartData="getYearlyData" title="年度用电量对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
|
|
previousColor="#FF6384" />
|
|
</div>
|
|
<div v-if="timeRange === 'month'" class="card-content">
|
|
<ComparisonBarChart :chartData="getMonthlyComparisonData"
|
|
:title="comparisonType === 'yearOnYear' ? '月度用电量同比对比' : '月度用电量环比对比'" yAxisName="用电量(kWh)"
|
|
currentColor="#36A2EB" previousColor="#FF6384" />
|
|
</div>
|
|
<div class="card-content" v-if="timeRange === 'day'">
|
|
<ComparisonBarChart :chartData="getDailyComparisonData"
|
|
:title="comparisonType === 'yearOnYear' ? '日用电量同比对比' : '日用电量环比对比'" yAxisName="用电量(kWh)" currentColor="#36A2EB"
|
|
previousColor="#FF6384" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
import { MapApi } from "@/api/shorepower/map";
|
|
import ComparisonBarChart from './charts/ComparisonBarChart.vue'
|
|
import { ComparativeData, RealtimeDeviceData } from '@/types/shorepower';
|
|
|
|
interface Props {
|
|
// realtimeDeviceData: RealtimeDeviceData[];
|
|
// activeHeadGroup?: number;
|
|
handleClose: () => void;
|
|
realtimeDeviceDataTime: string;
|
|
// comparativeData: ComparativeData;
|
|
initialTimeRange?: 'realtime' | 'day' | 'month' | 'year';
|
|
}
|
|
|
|
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
export interface MeasureDataRealtimeRespVO {
|
|
/**
|
|
* 创建时间
|
|
*/
|
|
createTime: Date;
|
|
/**
|
|
* 设备编号code
|
|
*/
|
|
deviceCode: string;
|
|
/**
|
|
* 设备编号
|
|
*/
|
|
deviceId: number;
|
|
/**
|
|
* 设备名称
|
|
*/
|
|
deviceName: string;
|
|
/**
|
|
* 设备状态
|
|
*/
|
|
deviceStatus: number;
|
|
/**
|
|
* 编号
|
|
*/
|
|
id: number;
|
|
/**
|
|
* 增量费用
|
|
*/
|
|
incrementCost?: number;
|
|
/**
|
|
* 增量值
|
|
*/
|
|
incrementValue: number;
|
|
/**
|
|
* 采集时间
|
|
*/
|
|
measureTime: Date;
|
|
/**
|
|
* 采集值
|
|
*/
|
|
measureValue: number;
|
|
}
|
|
|
|
export interface deviceData {
|
|
[key: string]: MeasureDataRealtimeRespVO
|
|
}
|
|
|
|
const getYearlyData = computed(() => {
|
|
return [{
|
|
name: '本期上期对比',
|
|
growthRate: .11,
|
|
currentValue: 22,
|
|
previousValue: 33,
|
|
currentPeriod: '2024',
|
|
previousPeriod: '2025',
|
|
}]
|
|
})
|
|
|
|
// 日对比数据(根据对比类型选择环比或同比)- 本地模拟数据
|
|
const getDailyComparisonData = computed(() => {
|
|
// 本地模拟岸电使用率数据
|
|
const mockDailyData = {
|
|
chain: { // 环比数据
|
|
name: '本期上期对比',
|
|
growthRate: .52, // 增长率5.2%
|
|
currentValue: 85.5, // 当前值85.5%
|
|
previousValue: 81.3, // 上期值81.3%
|
|
currentPeriod: new Date().toISOString().split('T')[0], // 当前期间
|
|
previousPeriod: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 前一天
|
|
},
|
|
yearOnYear: { // 同比数据
|
|
name: '本期去年同期对比',
|
|
growthRate: .128, // 同比增长率12.8%
|
|
currentValue: 85.5, // 当前值85.5%
|
|
previousValue: 75.8, // 去年同期值75.8%
|
|
currentPeriod: new Date().toISOString().split('T')[0], // 当前期间
|
|
previousPeriod: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], // 去年同期
|
|
}
|
|
};
|
|
|
|
return [{
|
|
name: mockDailyData[comparisonType.value].name,
|
|
growthRate: mockDailyData[comparisonType.value].growthRate,
|
|
currentValue: convertPowerUsage(mockDailyData[comparisonType.value].currentValue, selectedCard.value),
|
|
previousValue: convertPowerUsage(mockDailyData[comparisonType.value].previousValue, selectedCard.value),
|
|
currentPeriod: mockDailyData[comparisonType.value].currentPeriod,
|
|
previousPeriod: mockDailyData[comparisonType.value].previousPeriod,
|
|
}]
|
|
})
|
|
|
|
// 月对比数据(根据对比类型选择环比或同比)- 本地模拟数据
|
|
const getMonthlyComparisonData = computed(() => {
|
|
// 本地模拟岸电使用率数据
|
|
const mockMonthlyData = {
|
|
chain: { // 环比数据
|
|
name: '本期上期对比',
|
|
growthRate: .83, // 增长率8.3%
|
|
currentValue: 87.2, // 当前值87.2%
|
|
previousValue: 80.5, // 上期值80.5%
|
|
currentPeriod: new Date().toISOString().slice(0, 7), // 当前月份,格式为YYYY-MM
|
|
previousPeriod: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 7), // 上个月,格式为YYYY-MM
|
|
},
|
|
yearOnYear: { // 同比数据
|
|
name: '本期去年同期对比',
|
|
growthRate: .156, // 同比增长率15.6%
|
|
currentValue: 87.2, // 当前值87.2%
|
|
previousValue: 75.4, // 去年同期值75.4%
|
|
currentPeriod: new Date().toISOString().slice(0, 7), // 当前月份,格式为YYYY-MM
|
|
previousPeriod: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 7), // 去年同期月份
|
|
}
|
|
};
|
|
|
|
return [{
|
|
name: mockMonthlyData[comparisonType.value].name,
|
|
growthRate: mockMonthlyData[comparisonType.value].growthRate,
|
|
currentValue: convertPowerUsage(mockMonthlyData[comparisonType.value].currentValue, selectedCard.value),
|
|
previousValue: convertPowerUsage(mockMonthlyData[comparisonType.value].previousValue, selectedCard.value),
|
|
currentPeriod: mockMonthlyData[comparisonType.value].currentPeriod,
|
|
previousPeriod: mockMonthlyData[comparisonType.value].previousPeriod,
|
|
}]
|
|
})
|
|
|
|
// 根据时间范围计算起始时间
|
|
const startTimeDisplay = computed(() => {
|
|
const now = new Date();
|
|
let startDate: Date;
|
|
|
|
switch (timeRange.value) {
|
|
case 'day':
|
|
// 今日的零点
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
break;
|
|
case 'month':
|
|
// 当月的第一天零点
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
break;
|
|
case 'year':
|
|
// 当年的第一天零点
|
|
startDate = new Date(now.getFullYear(), 0, 1);
|
|
break;
|
|
/* case 'realtime':
|
|
// 总数据从2020年开始
|
|
startDate = new Date(2020, 0, 1);
|
|
break; */
|
|
default:
|
|
// 默认为今日的零点
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
}
|
|
|
|
// 格式化日期时间
|
|
const year = startDate.getFullYear();
|
|
const month = String(startDate.getMonth() + 1).padStart(2, '0');
|
|
const day = String(startDate.getDate()).padStart(2, '0');
|
|
const hours = String(startDate.getHours()).padStart(2, '0');
|
|
const minutes = String(startDate.getMinutes()).padStart(2, '0');
|
|
const seconds = String(startDate.getSeconds()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
});
|
|
|
|
const selectedCard = ref<string>('overview')
|
|
|
|
// 时间范围选项
|
|
const timeRange = ref<'day' | 'month' | 'year'>(
|
|
(props.initialTimeRange === 'realtime' ? 'year' : props.initialTimeRange) || 'day'
|
|
)
|
|
|
|
// 比较类型选项:chain(环比)或 yearOnYear(同比)
|
|
const comparisonType = ref<'chain' | 'yearOnYear'>('chain')
|
|
|
|
// 时间范围选项定义
|
|
const timeRangeOptions = [
|
|
{ value: 'day', label: '当日' },
|
|
{ value: 'month', label: '当月' },
|
|
{ value: 'year', label: '当年' },
|
|
// { value: 'realtime', label: '汇总' },
|
|
]
|
|
|
|
// 处理时间范围选择
|
|
const handleTimeRangeChange = (range: 'day' | 'month' | 'year') => {
|
|
timeRange.value = range
|
|
// 这里可以添加根据时间范围切换数据源的逻辑
|
|
}
|
|
|
|
// 通用数据转换函数
|
|
const convertPowerUsage = (powerUsage: number, type: string): number => {
|
|
const conversionFactors: Record<string, number> = {
|
|
totalPower: 1, // 直接使用原值
|
|
fuel: 0.22 / 1, // 转化为千克
|
|
co2: 670 / 1000, // 克转化为千克
|
|
pm25: 1.46 / 1000, // 克转化为千克
|
|
nox: 18.1 / 1000, // 克转化为千克
|
|
so2: 10.5 / 1000 // 克转化为千克
|
|
};
|
|
|
|
const factor = conversionFactors[type] || 1;
|
|
return Number((powerUsage * factor).toFixed(2));
|
|
};
|
|
|
|
|
|
/* watch(() => props.realtimeDeviceData, (newValue) => {
|
|
handleGetRealTimeAllData(newValue)
|
|
}) */
|
|
|
|
// 组件挂载时初始化数据和定时器
|
|
// 添加一个变量来保存事件处理函数的引用
|
|
let cardSelectedHandler: any = null
|
|
|
|
onMounted(() => {
|
|
// handleGetAllDeviceDataByTimeRange()
|
|
// handleGetRealTimeAllData(props.realtimeDeviceData)
|
|
|
|
// 添加卡片选择事件监听器
|
|
// cardSelectedHandler = (event: CustomEvent) => {
|
|
// selectedCard.value = event.detail
|
|
// }
|
|
// window.addEventListener('cardSelected', cardSelectedHandler)
|
|
})
|
|
|
|
// 监听 activeHeadGroup 变化,当组件变为可见时触发 resize
|
|
watch(() => props.activeHeadGroup, (newVal) => {
|
|
if (newVal === 1) {
|
|
// 延迟执行 resize,确保 DOM 已经更新
|
|
setTimeout(() => {
|
|
window.dispatchEvent(new Event('resize'))
|
|
}, 100)
|
|
}
|
|
}, { immediate: true })
|
|
|
|
// 监听 initialTimeRange 变化,当父组件传递新的时间范围时更新当前时间范围
|
|
watch(() => props.initialTimeRange, (newVal) => {
|
|
console.log('newVal', newVal)
|
|
if (newVal) {
|
|
if (newVal === 'realtime') {
|
|
timeRange.value = 'year'
|
|
} else {
|
|
timeRange.value = newVal as 'day' | 'month' | 'year'
|
|
}
|
|
}
|
|
})
|
|
|
|
// 组件销毁前清除定时器
|
|
onBeforeUnmount(() => {
|
|
if (cardSelectedHandler) {
|
|
window.removeEventListener('cardSelected', cardSelectedHandler)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.shore-power-usage {
|
|
display: flex;
|
|
height: 100%;
|
|
width: 100%;
|
|
gap: 10px;
|
|
}
|
|
|
|
.card .card-content {
|
|
overflow: hidden !important;
|
|
}
|
|
|
|
.average {
|
|
|
|
.overview-value {
|
|
font-size: 36px;
|
|
}
|
|
|
|
.overview-label {
|
|
font-size: 18px;
|
|
}
|
|
}
|
|
|
|
.magnify {
|
|
width: 100%;
|
|
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: 100%;
|
|
width: calc(55%);
|
|
|
|
.card {
|
|
height: 100%;
|
|
}
|
|
|
|
|
|
.card .card-title .title-text {
|
|
width: 120px;
|
|
}
|
|
|
|
.overview-value {
|
|
font-size: 48px;
|
|
}
|
|
|
|
.overview-label {
|
|
font-size: 28px;
|
|
}
|
|
}
|
|
|
|
.right-row {
|
|
width: calc(45% - 8px);
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
gap: 8px;
|
|
// background-color: red;
|
|
}
|
|
|
|
.one-row {
|
|
height: 30%;
|
|
display: flex;
|
|
gap: 8px;
|
|
|
|
.card .card-title .title-text {
|
|
font-size: 14px;
|
|
}
|
|
|
|
.card {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.card {
|
|
padding: 6px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.time-range-selector {
|
|
display: flex;
|
|
align-items: center;
|
|
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: 18px;
|
|
font-weight: 600;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.show-value-value {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.time-range-item {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
font-weight: bold;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
}
|
|
|
|
.growth-rate {
|
|
flex: 1;
|
|
text-align: right;
|
|
margin-left: 10px;
|
|
font-weight: bold;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
font-size: 32px;
|
|
}
|
|
|
|
.growth-up {
|
|
color: #52C41A;
|
|
}
|
|
|
|
.growth-down {
|
|
color: #F5222D;
|
|
}
|
|
|
|
.growth-icon {
|
|
margin-right: 4px;
|
|
}
|
|
|
|
.comparison-type-selector {
|
|
display: inline-flex;
|
|
margin: 0 10px;
|
|
width: fit-content;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
padding: 2px;
|
|
// margin-bottom: 8px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.comparison-type-btn {
|
|
border: none;
|
|
background: transparent;
|
|
color: #ccc;
|
|
padding: 4px 8px;
|
|
border-radius: 2px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: all 0.3s;
|
|
|
|
&.active {
|
|
background: #1890ff;
|
|
color: white;
|
|
}
|
|
}
|
|
|
|
.right {
|
|
/* gap: 0 */
|
|
}
|
|
|
|
.close-btn {
|
|
width: 24px;
|
|
height: 24px;
|
|
// border-radius: 50%;
|
|
// background-color: #ff4d4f;
|
|
color: white;
|
|
border: none;
|
|
font-weight: bold;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-left: 10px;
|
|
// margin-bottom: 8px;
|
|
}
|
|
|
|
.close-btn:hover {
|
|
color: #f5222d;
|
|
}
|
|
</style>
|