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.
1204 lines
41 KiB
1204 lines
41 KiB
<template>
|
|
<div class="shore-power-usage">
|
|
<template v-if="pageType === 'realtime'">
|
|
<div 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">
|
|
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
|
|
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
|
|
<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>
|
|
<!-- 返回上个页面 -->
|
|
<el-button type="primary" @click.stop="pageType = 'history'">历史查询</el-button>
|
|
|
|
<el-button type="primary" @click.stop="handleGoBack()">返回</el-button>
|
|
|
|
</div>
|
|
<div v-if="card.type === 'chart'" class="show-value">
|
|
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
|
|
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
|
|
<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 type="primary" @click.stop="handleGoBack()">返回</el-button>
|
|
|
|
</div>
|
|
|
|
</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>
|
|
</div>
|
|
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color"
|
|
:magnify-mode="true" />
|
|
</div>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<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>
|
|
</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>
|
|
</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>
|
|
</div>
|
|
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
<template v-if="pageType === 'history'">
|
|
<div 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">
|
|
<div class="separate-date-pickers">
|
|
<div class="date-picker-group">
|
|
<span class="date-label">开始时间:</span>
|
|
<el-date-picker v-if="historyTimeGranularity === 'year'" v-model="startDate" type="year"
|
|
placeholder="开始年" format="YYYY" value-format="YYYY" class="date-picker"
|
|
@change="handleStartDateChange" />
|
|
<el-date-picker v-else-if="historyTimeGranularity === 'month'" v-model="startDate" type="month"
|
|
placeholder="开始月" format="YYYY-MM" value-format="YYYY-MM" class="date-picker"
|
|
@change="handleStartDateChange" />
|
|
<el-date-picker v-else v-model="startDate" type="date" placeholder="开始日" format="YYYY-MM-DD"
|
|
value-format="YYYY-MM-DD" class="date-picker" @change="handleStartDateChange" />
|
|
</div>
|
|
<div class="date-picker-group">
|
|
<span class="date-label">结束时间:</span>
|
|
<el-date-picker v-if="historyTimeGranularity === 'year'" v-model="endDate" type="year"
|
|
placeholder="结束年" format="YYYY" value-format="YYYY" class="date-picker"
|
|
@change="handleEndDateChange" />
|
|
<el-date-picker v-else-if="historyTimeGranularity === 'month'" v-model="endDate" type="month"
|
|
placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM" class="date-picker"
|
|
@change="handleEndDateChange" />
|
|
<el-date-picker v-else v-model="endDate" type="date" placeholder="结束日" format="YYYY-MM-DD"
|
|
value-format="YYYY-MM-DD" class="date-picker" @change="handleEndDateChange" />
|
|
</div>
|
|
</div>
|
|
<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">
|
|
<div class="time-range-item">记录时间范围: {{ selectedDateRangeDisplay }}</div>
|
|
<!-- <div class="time-range-item">{{ realtimeDeviceDataTime }}</div> -->
|
|
<!-- <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
|
|
''}}量:</span> -->
|
|
<!-- <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> -->
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title" :color="card.color"
|
|
:magnify-mode="true" />
|
|
</div>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<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> -->
|
|
</div>
|
|
<div v-if="card.type === 'chart'" class="show-value">
|
|
<span class="show-value-label">区间量</span>
|
|
<div class="show-value-value">{{ historyData[card.value] }}</div>
|
|
</div>
|
|
</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>
|
|
</div>
|
|
<WaveLineChart v-else :chart-data="getHistoryChartData(card.id)" :title="card.title"
|
|
:color="card.color" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
import WaveLineChart from './charts/WaveLineChart.vue'
|
|
import { MapApi } from "@/api/shorepower/map";
|
|
import dayjs from 'dayjs';
|
|
import { RealtimeDeviceData } from '@/types/shorepower';
|
|
import { formatTimestamp, parseRangeToTimestamp } from './utils';
|
|
|
|
|
|
interface Props {
|
|
realtimeDeviceData: RealtimeDeviceData[];
|
|
activeHeadGroup?: number;
|
|
handleGoBack: () => void;
|
|
realtimeDeviceDataTime: string;
|
|
initialTimeRange?: string;
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
// 定义组件属性
|
|
interface ChartDataItem {
|
|
name: string;
|
|
value: number;
|
|
}
|
|
|
|
interface CardInfo {
|
|
id: string;
|
|
title: string;
|
|
type: 'overview' | 'chart';
|
|
unit?: string;
|
|
color?: string;
|
|
value: string;
|
|
}
|
|
|
|
interface RawItem {
|
|
measureValue: number;
|
|
measureTime: Date;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
export interface deviceData {
|
|
[key: string]: MeasureDataRealtimeRespVO
|
|
}
|
|
|
|
// const props = defineProps<Props>()
|
|
|
|
const pageType = ref<'realtime' | 'history'>('realtime')
|
|
const dateRange = ref<string[]>([])
|
|
const startDate = ref<string>('')
|
|
const endDate = ref<string>('')
|
|
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 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 selectedDateRangeDisplay = computed(() => {
|
|
/* if (!dateRange.value || dateRange.value.length !== 2) {
|
|
return '';
|
|
} */
|
|
|
|
// const [startDate, endDate] = dateRange.value;
|
|
// const startDate = startDate.value
|
|
// const endDate = endDate.value
|
|
|
|
// 根据不同的时间粒度格式化日期
|
|
switch (historyTimeGranularity.value) {
|
|
case 'year':
|
|
// 年格式: YYYY
|
|
return `${startDate.value} - ${endDate.value}`;
|
|
case 'month':
|
|
// 月格式: YYYY-MM
|
|
return `${startDate.value} - ${endDate.value}`;
|
|
default:
|
|
// 日格式: YYYY-MM-DD
|
|
return `${startDate.value} - ${endDate.value}`;
|
|
}
|
|
});
|
|
|
|
const displayMode = ref<'average' | 'magnify'>('magnify')
|
|
const selectedCard = ref<string>('overview')
|
|
// 创建统一的模拟数据源
|
|
const commonChartData = ref<ChartDataItem[]>([])
|
|
|
|
// 实时数据图表数组
|
|
const totalPowerChartData = ref<ChartDataItem[]>([])
|
|
const co2ReductionChartData = ref<ChartDataItem[]>([])
|
|
const so2ReductionChartData = ref<ChartDataItem[]>([])
|
|
const noxReductionChartData = ref<ChartDataItem[]>([])
|
|
const pm25ReductionChartData = ref<ChartDataItem[]>([])
|
|
const fuelReductionChartData = ref<ChartDataItem[]>([])
|
|
|
|
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,
|
|
})
|
|
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,
|
|
},
|
|
|
|
})
|
|
|
|
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 handleStartDateChange = (date: string | null) => {
|
|
if (date) {
|
|
startDate.value = date
|
|
// 如果结束日期为空,自动设置一个合理的结束日期
|
|
if (!endDate.value) {
|
|
const start = new Date(date)
|
|
let end = new Date(date)
|
|
|
|
switch (historyTimeGranularity.value) {
|
|
case 'year':
|
|
end.setFullYear(start.getFullYear() + 1)
|
|
break
|
|
case 'month':
|
|
end.setMonth(start.getMonth() + 1)
|
|
break
|
|
default:
|
|
end.setDate(start.getDate() + 7) // 默认设置一周后
|
|
break
|
|
}
|
|
|
|
// 格式化结束日期
|
|
const formatEndDate = (date: Date) => {
|
|
const year = date.getFullYear()
|
|
const month = String(date.getMonth() + 1).padStart(2, '0')
|
|
const day = String(date.getDate()).padStart(2, '0')
|
|
|
|
switch (historyTimeGranularity.value) {
|
|
case 'year':
|
|
return year.toString()
|
|
case 'month':
|
|
return `${year}-${month}`
|
|
default:
|
|
return `${year}-${month}-${day}`
|
|
}
|
|
}
|
|
|
|
endDate.value = formatEndDate(end)
|
|
}
|
|
|
|
// 只有当开始和结束日期都设置时才触发查询
|
|
if (startDate.value && endDate.value) {
|
|
handleSelectDate([startDate.value, endDate.value])
|
|
}
|
|
}
|
|
}
|
|
|
|
// 处理结束日期变化
|
|
const handleEndDateChange = (date: string | null) => {
|
|
if (date) {
|
|
endDate.value = date
|
|
// 只有当开始和结束日期都设置时才触发查询
|
|
if (startDate.value && endDate.value) {
|
|
handleSelectDate([startDate.value, endDate.value])
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
// 获取对应卡片的图表数据
|
|
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
|
|
}
|
|
}
|
|
// 获取对应卡片的图表数据
|
|
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
|
|
}
|
|
}
|
|
|
|
// 时间范围选项
|
|
const timeRange = ref<'realtime' | 'day' | 'month' | 'quarter' | 'year'>(
|
|
(props.initialTimeRange as 'realtime' | 'day' | 'month' | 'quarter' | 'year') || 'day'
|
|
)
|
|
|
|
// 时间范围选项定义
|
|
const timeRangeOptions = [
|
|
{ value: 'day', label: '当日' },
|
|
{ value: 'month', label: '当月' },
|
|
{ value: 'year', label: '当年' },
|
|
{ value: 'realtime', label: '汇总' },
|
|
]
|
|
|
|
// 处理时间范围选择
|
|
const handleTimeRangeChange = (range: 'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year') => {
|
|
timeRange.value = range
|
|
// 这里可以添加根据时间范围切换数据源的逻辑
|
|
}
|
|
|
|
// 历史模式时间颗粒度选项
|
|
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获取选中日期范围内的数据
|
|
}
|
|
|
|
// 定义卡片信息
|
|
const cards = ref<CardInfo[]>([
|
|
{
|
|
id: 'overview',
|
|
title: '总览',
|
|
type: 'overview'
|
|
},
|
|
{
|
|
id: 'fuel',
|
|
title: '减少燃油(吨)',
|
|
type: 'chart',
|
|
unit: '吨',
|
|
color: '#4CAF50', // 绿色 - 代表节能、清洁能源
|
|
value: 'fuel'
|
|
},
|
|
{
|
|
id: 'co2',
|
|
title: '减少CO₂排放(千克)',
|
|
type: 'chart',
|
|
unit: '千克',
|
|
color: '#2E7D32', // 深绿色/蓝绿 - 温室气体(国际常用低碳绿色系)
|
|
value: 'co2'
|
|
},
|
|
{
|
|
id: 'pm25',
|
|
title: '减少PM2.5排放(千克)',
|
|
type: 'chart',
|
|
unit: '千克',
|
|
color: '#9E9E9E', // 灰色 - 颗粒物(国际通用)
|
|
value: 'pm25'
|
|
},
|
|
{
|
|
id: 'nox',
|
|
title: '减少NOₓ排放(千克)',
|
|
type: 'chart',
|
|
unit: '千克',
|
|
color: '#FF9800', // 橙色 - 氮氧化物(国际通用)
|
|
value: 'nox'
|
|
},
|
|
{
|
|
id: 'so2',
|
|
title: '减少SO₂排放(千克)',
|
|
type: 'chart',
|
|
unit: '千克',
|
|
color: '#FFEB3B', // 黄色 - 二氧化硫(国际通用)
|
|
value: 'so2'
|
|
}
|
|
])
|
|
|
|
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
|
|
}
|
|
|
|
// 定时器引用
|
|
let timer: ReturnType<typeof setInterval>;
|
|
|
|
// 追加新数据
|
|
const appendNewData = () => {
|
|
const now = new Date()
|
|
commonChartData.value.push({
|
|
name: now.toLocaleTimeString(),
|
|
value: Math.floor(Math.random() * 100) + 10
|
|
})
|
|
}
|
|
|
|
const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]) => {
|
|
try {
|
|
const res: RealtimeDeviceData[] = realtimeDeviceData.filter(item => item.deviceCode.includes('Kwh'));
|
|
const ids = res.map(item => item.id);
|
|
const params = {
|
|
ids: ids.join(','),
|
|
// year: new Date().getFullYear()
|
|
}
|
|
if (!params.ids) return;
|
|
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);
|
|
}
|
|
|
|
// const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params);
|
|
|
|
const realTimeSum = res.reduce((acc, item) => acc + item.measureValue, 0);
|
|
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) // 转化为吨
|
|
|
|
const incrementRealTimeSum = Object.values(res).reduce((acc, item) => acc + item.measureValue, 0);
|
|
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
/* const handleGetAllDeviceDataByTimeRange = async () => {
|
|
try {
|
|
// 当前时间的时间戳(毫秒)
|
|
const nowTimestamp = dayjs().valueOf().toString();
|
|
|
|
// 两天前的时间戳(毫秒)
|
|
const twoDaysAgoTimestamp = dayjs().subtract(2, 'day').valueOf().toString();
|
|
|
|
|
|
|
|
} catch (error) {
|
|
console.error(error)
|
|
}
|
|
} */
|
|
|
|
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) {
|
|
timeRange.value = newVal as 'realtime' | 'day' | 'month' | 'year'
|
|
}
|
|
})
|
|
|
|
// 组件销毁前清除定时器
|
|
onBeforeUnmount(() => {
|
|
if (cardSelectedHandler) {
|
|
window.removeEventListener('cardSelected', cardSelectedHandler)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.shore-power-usage {
|
|
display: flex;
|
|
height: 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: 70%;
|
|
|
|
.card {
|
|
height: 100%;
|
|
}
|
|
|
|
.overview-value {
|
|
font-size: 48px;
|
|
}
|
|
|
|
.overview-label {
|
|
font-size: 28px;
|
|
}
|
|
}
|
|
|
|
.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);
|
|
}
|
|
|
|
.separate-date-pickers {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.date-picker-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.date-label {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: rgba(255, 255, 255, 0.8);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.date-picker {
|
|
width: 140px;
|
|
}
|
|
|
|
.right {
|
|
/* gap: 0 */
|
|
}
|
|
</style>
|