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

<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>