Browse Source

update

feature
jiangAB 2 weeks ago
parent
commit
c91b673487
  1. 41
      public/map/components/CompanyShorePower.vue
  2. 14
      public/map/components/ShipShorePower.vue
  3. 144
      public/map/components/ShorePowerUsage.vue
  4. 223
      public/map/components/ShorePowerUsageSingleData.vue
  5. 141
      public/map/components/ShowData.vue
  6. 445
      public/map/components/cesiumMap.vue
  7. 226
      public/map/components/charts/ComparisonBarChart.vue
  8. 162
      public/map/index.vue
  9. 8
      src/api/shorepower/map/index.ts
  10. 36
      src/types/shorepower.d.ts

41
public/map/components/CompanyShorePower.vue

@ -146,7 +146,8 @@
@click.stop="() => handleTimeRangeChange(company.id, option.value)">
{{ option.label }}
</button>
<div class="company-total">总量: {{ handGetDeviceData(company.shorePowerIds) }}</div>
<div class="company-total">总量: {{ handGetDeviceData(company.shorePowerIds,
getCompanyTimeRange(company.id)) }}</div>
</div>
</div>
@ -158,7 +159,7 @@
<div class="shorepower-item" v-for="(shorePower, shorePowerIndex) in (disteItem.shorePowerList || [])"
:key="shorePowerIndex">
<div class="shorepower-value" @click="handleClickhorePowerValue(shorePower)">{{
handGetDeviceData(shorePower.totalPowerDeviceId) || 0 }}</div>
handGetDeviceData(shorePower.totalPowerDeviceId, getCompanyTimeRange(company.id)) || 0 }}</div>
<div class="shorepower-label" @click="handleClickShorePowerName(shorePower)">{{ shorePower.name }}
</div>
@ -211,6 +212,9 @@ interface Props {
realtimeDeviceDataTime: string;
shorePowerList: ShorePowerBerth[];
handleGoBack: () => void;
yearData: any[];
monthData: any[];
dayData: any[];
}
const props = defineProps<Props>()
@ -381,23 +385,46 @@ const deviceDataMap = computed(() => {
return map;
});
const handGetDeviceData = (deviceId: number | number[]) => {
const handGetDeviceData = (deviceId: number | number[], range: 'realtime' | 'day' | 'month' | 'year') => {
let totalValue = 0;
let rangData: RealtimeDeviceData[] = []
if (range === 'realtime') {
rangData = props.realtimeDeviceData
} else if (range === 'day') {
rangData = props.dayData
} else if (range === 'month') {
rangData = props.monthData
} else if (range === 'year') {
rangData = props.yearData
}
if (Array.isArray(deviceId)) {
// If deviceId is an array, sum all the measure values
// If deviceId is an array, sum all the measure values and subtract start values
deviceId.forEach(id => {
const deviceData = deviceDataMap.value.get(id);
const startData = rangData.find(item => item.deviceId === id)
if (deviceData?.measureValue) {
totalValue += deviceData.measureValue;
}
// For non-realtime ranges, subtract the start value
if (range !== 'realtime' && startData?.measureValue) {
totalValue -= startData.measureValue;
}
});
} else {
// If deviceId is a single number, get its measure value
const deviceData = deviceDataMap.value.get(deviceId);
const startData = rangData.find(item => item.deviceId === deviceId)
console.log(deviceData, startData)
if (deviceData?.measureValue) {
totalValue = deviceData.measureValue;
}
if (range !== 'realtime' && startData?.measureValue) {
totalValue -= startData.measureValue
}
}
return totalValue.toFixed(2);
@ -589,8 +616,8 @@ onMounted(() => {
}
.company-berth-count {
font-size: 16px;
color: #aaa;
font-size: 18px;
color: #eee;
margin-top: 4px;
}
@ -605,7 +632,7 @@ onMounted(() => {
.data-update-time {
margin-left: 6px;
font-size: 16px;
font-size: 24px;
color: #aaa;
white-space: nowrap;
}

14
public/map/components/ShipShorePower.vue

@ -446,16 +446,16 @@
</el-col>
<el-col :span="12">
<el-form-item label="用电结束时供电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.overPowerSupplyOperator || '-' }}</span>
<el-form-item label="用电开始时船方操作人:">
<span>{{ selectedShip.usageRecordInfo?.beginPowerUsageOperator || '-' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用电开始时船方操作人:">
<span>{{ selectedShip.usageRecordInfo?.beginPowerUsageOperator || '-' }}</span>
<el-form-item label="用电结束时供电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.overPowerSupplyOperator || '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
@ -769,7 +769,6 @@ const formatDateTime = (timestamp: string | number | Date | undefined) => {
}
//
const showDetail = (row: ShipRespVo) => {
console.log('first')
selectedShip.value = row
detailDialogVisible.value = true
}
@ -779,7 +778,6 @@ onMounted(async () => {
berthIdAndNameList.value = await BERTH_DISTRICT()
dockIdAndNameList.value = await DOCK_DISTRICT()
harborDistrictIdAndNameList.value = await HARBOR_DISTRICT()
console.log('props.shipData', props.shipData)
localShipData.value = props.shipData
})
</script>
@ -1112,8 +1110,8 @@ onMounted(async () => {
}
.time-range-item {
margin-left: 10px;
font-size: 16px;
margin-left: 24px;
font-size: 24px;
font-weight: 600;
font-weight: bold;
color: rgba(255, 255, 255, 0.7);

144
public/map/components/ShorePowerUsage.vue

@ -135,15 +135,30 @@
<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 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" />
<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')">
@ -213,9 +228,8 @@
</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>
<span class="show-value-label">区间量</span>
<div class="show-value-value">{{ historyData[card.value] }}</div>
</div>
</div>
<div class="card-content">
@ -288,6 +302,11 @@ interface CardInfo {
value: string;
}
interface RawItem {
measureValue: number;
measureTime: Date;
}
interface ShorePowerUsageData {
totalPower: number; //
fuelReduction: number; //
@ -348,6 +367,8 @@ export interface deviceData {
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')
@ -395,23 +416,25 @@ const startTimeDisplay = computed(() => {
//
const selectedDateRangeDisplay = computed(() => {
if (!dateRange.value || dateRange.value.length !== 2) {
/* if (!dateRange.value || dateRange.value.length !== 2) {
return '';
}
} */
const [startDate, endDate] = dateRange.value;
// const [startDate, endDate] = dateRange.value;
// const startDate = startDate.value
// const endDate = endDate.value
//
switch (historyTimeGranularity.value) {
case 'year':
// : YYYY
return `${startDate} - ${endDate}`;
return `${startDate.value} - ${endDate.value}`;
case 'month':
// : YYYY-MM
return `${startDate} - ${endDate}`;
return `${startDate.value} - ${endDate.value}`;
default:
// : YYYY-MM-DD
return `${startDate} - ${endDate}`;
return `${startDate.value} - ${endDate.value}`;
}
});
@ -540,6 +563,64 @@ const calculateTotalDiff = (data) => {
}, 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) {
//
@ -952,10 +1033,10 @@ onMounted(() => {
handleGetRealTimeAllData(props.realtimeDeviceData)
//
cardSelectedHandler = (event: CustomEvent) => {
/* cardSelectedHandler = (event: CustomEvent) => {
selectedCard.value = event.detail
}
window.addEventListener('cardSelected', cardSelectedHandler)
window.addEventListener('cardSelected', cardSelectedHandler) */
})
// activeHeadGroup resize
@ -1077,13 +1158,34 @@ onBeforeUnmount(() => {
}
.time-range-item {
font-size: 16px;
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 */

223
public/map/components/ShorePowerUsageSingleData.vue

@ -12,8 +12,7 @@
<span class="title-text">{{ card.title }}</span>
</div>
<div class="show-value">
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
<div class="time-range-item">{{ startTimeDisplay }} {{ 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>
@ -34,9 +33,14 @@
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">年环比</span>
<span class="growth-rate"
:class="{ 'growth-up': getYearlyData[0].growthRate >= 0, 'growth-down': getYearlyData[0].growthRate < 0 }">
<span class="growth-icon">{{ getYearlyData[0].growthRate >= 0 ? '↑' : '↓' }}</span>
{{ (getYearlyData[0].growthRate * 100).toFixed(1) }}%
</span>
</div>
<div class="card-content">
<ComparisonBarChart :chartData="yearlyData" title="年度用电量对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
<ComparisonBarChart :chartData="getYearlyData" title="年度用电量对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
previousColor="#FF6384" />
</div>
</div>
@ -45,10 +49,15 @@
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">月环比</span>
<span class="growth-rate"
:class="{ 'growth-up': getMonthlyData[0].growthRate >= 0, 'growth-down': getMonthlyData[0].growthRate < 0 }">
<span class="growth-icon">{{ getMonthlyData[0].growthRate >= 0 ? '↑' : '↓' }}</span>
{{ (getMonthlyData[0].growthRate * 100).toFixed(1) }}%
</span>
</div>
<div class="card-content">
<ComparisonBarChart :chartData="monthlyData" title="月度用电量对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
previousColor="#FF6384" />
<ComparisonBarChart :chartData="getMonthlyData" title="月度用电量对比" yAxisName="用电量(kWh)"
currentColor="#36A2EB" previousColor="#FF6384" />
</div>
</div>
<div class="card digital-twin-card--deep-blue">
@ -56,9 +65,14 @@
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">日环比</span>
<span class="growth-rate"
:class="{ 'growth-up': getDailyData[0].growthRate >= 0, 'growth-down': getDailyData[0].growthRate < 0 }">
<span class="growth-icon">{{ getDailyData[0].growthRate >= 0 ? '↑' : '↓' }}</span>
{{ (getDailyData[0].growthRate * 100).toFixed(1) }}%
</span>
</div>
<div class="card-content">
<ComparisonBarChart :chartData="dailyData" title="日对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
<ComparisonBarChart :chartData="getDailyData" title="日对比" yAxisName="用电量(kWh)" currentColor="#36A2EB"
previousColor="#FF6384" />
</div>
</div>
@ -252,7 +266,7 @@ import WaveLineChart from './charts/WaveLineChart.vue'
import { MapApi } from "@/api/shorepower/map";
import dayjs from 'dayjs';
import ComparisonBarChart from './charts/ComparisonBarChart.vue'
import { RealtimeDeviceData } from '@/types/shorepower';
import { ComparativeData, RealtimeDeviceData } from '@/types/shorepower';
import { formatTimestamp, parseRangeToTimestamp } from './utils';
@ -261,8 +275,11 @@ interface Props {
activeHeadGroup?: number;
handleGoBack: () => void;
realtimeDeviceDataTime: string;
comparativeData: ComparativeData;
}
const props = defineProps<Props>()
//
interface ChartDataItem {
@ -346,23 +363,38 @@ const pm25Reduction = ref<string>('0')
const noxReduction = ref<string>('0')
const so2Reduction = ref<string>('0')
// ( vs )
const yearlyData = [
{ name: '本期上期对比', currentValue: 1200, previousValue: 1100 },
// { name: '2025', currentValue: 1300, previousValue: 1250 },
]
// ( vs )
const monthlyData = [
{ name: '本期上期对比', currentValue: 450, previousValue: 420 },
// { name: '', currentValue: 480, previousValue: 460 },
]
// ( vs )
const dailyData = [
{ name: '本期上期对比', currentValue: 20, previousValue: 18 },
// { name: '', currentValue: 15, previousValue: 14 },
]
// const yearlyData = ref<ComparativeData['year']>({})
const getDailyData = computed(() => {
return [{
name: '本期上期对比',
growthRate: props.comparativeData.day.growthRate,
currentValue: convertPowerUsage(props.comparativeData.day.current.value, selectedCard.value),
previousValue: convertPowerUsage(props.comparativeData.day.previous.value, selectedCard.value),
currentPeriod: props.comparativeData.day.current.period,
previousPeriod: props.comparativeData.day.previous.period,
}]
})
const getMonthlyData = computed(() => {
return [{
name: '本期上期对比',
growthRate: props.comparativeData.month.growthRate,
currentValue: convertPowerUsage(props.comparativeData.month.current.value, selectedCard.value),
previousValue: convertPowerUsage(props.comparativeData.month.previous.value, selectedCard.value),
currentPeriod: props.comparativeData.month.current.period,
previousPeriod: props.comparativeData.month.previous.period,
}]
})
const getYearlyData = computed(() => {
return [{
name: '本期上期对比',
growthRate: props.comparativeData.year.growthRate,
currentValue: convertPowerUsage(props.comparativeData.year.current.value, selectedCard.value),
previousValue: convertPowerUsage(props.comparativeData.year.previous.value, selectedCard.value),
currentPeriod: props.comparativeData.year.current.period,
previousPeriod: props.comparativeData.year.previous.period,
}]
})
//
const startTimeDisplay = computed(() => {
@ -617,27 +649,27 @@ const handleSelectDate = async (range: string[] | null) => {
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)), //
totalPower: convertPowerUsage(totalDiff, 'totalPower'),
fuel: convertPowerUsage(totalDiff, 'fuel'),
co2: convertPowerUsage(totalDiff, 'co2'),
pm25: convertPowerUsage(totalDiff, 'pm25'),
nox: convertPowerUsage(totalDiff, 'nox'),
so2: convertPowerUsage(totalDiff, 'so2'),
}
const chartData = aggregateByIndex(array);
const fuelChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 0.22 / 1).toFixed(2)) }))
const fuelChartData = chartData.map(item => ({ name: item.name, value: convertPowerUsage(item.value, 'fuel') }))
h_fuelReductionChartData.value = fuelChartData;
const co2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 670 / 1000).toFixed(2)) }))
const co2ChartData = chartData.map(item => ({ name: item.name, value: convertPowerUsage(item.value, 'co2') }))
h_co2ReductionChartData.value = co2ChartData;
const pm25ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 1.46 / 1000).toFixed(2)) }))
const pm25ChartData = chartData.map(item => ({ name: item.name, value: convertPowerUsage(item.value, 'pm25') }))
h_pm25ReductionChartData.value = pm25ChartData;
const noxChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 18.1 / 1000).toFixed(2)) }))
const noxChartData = chartData.map(item => ({ name: item.name, value: convertPowerUsage(item.value, 'nox') }))
h_noxReductionChartData.value = noxChartData;
const so2ChartData = chartData.map(item => ({ name: item.name, value: Number((item.value * 10.5 / 1000).toFixed(2)) }))
const so2ChartData = chartData.map(item => ({ name: item.name, value: convertPowerUsage(item.value, 'so2') }))
h_so2ReductionChartData.value = so2ChartData;
};
@ -726,8 +758,31 @@ const handleDateRangeConfirm = () => {
// API
}
//
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));
};
//
const cards = ref<CardInfo[]>([
{
id: 'totalPower',
title: '用电量(千瓦时)',
type: 'chart',
unit: 'kWh',
color: '#1890FF', // 绿 -
value: 'totalPower'
},
{
id: 'fuel',
title: '减少燃油(吨)',
@ -852,46 +907,46 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
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)), //
totalPower: convertPowerUsage(realTimeSum, 'totalPower'),
fuel: convertPowerUsage(realTimeSum, 'fuel'),
co2: convertPowerUsage(realTimeSum, 'co2'),
pm25: convertPowerUsage(realTimeSum, 'pm25'),
nox: convertPowerUsage(realTimeSum, 'nox'),
so2: convertPowerUsage(realTimeSum, 'so2'),
}
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)), //
totalPower: convertPowerUsage(daySum, 'totalPower'),
fuel: convertPowerUsage(daySum, 'fuel'),
co2: convertPowerUsage(daySum, 'co2'),
pm25: convertPowerUsage(daySum, 'pm25'),
nox: convertPowerUsage(daySum, 'nox'),
so2: convertPowerUsage(daySum, 'so2'),
}
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)), //
totalPower: convertPowerUsage(weekSum, 'totalPower'),
fuel: convertPowerUsage(weekSum, 'fuel'),
co2: convertPowerUsage(weekSum, 'co2'),
pm25: convertPowerUsage(weekSum, 'pm25'),
nox: convertPowerUsage(weekSum, 'nox'),
so2: convertPowerUsage(weekSum, 'so2'),
}
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)), //
totalPower: convertPowerUsage(monthSum, 'totalPower'),
fuel: convertPowerUsage(monthSum, 'fuel'),
co2: convertPowerUsage(monthSum, 'co2'),
pm25: convertPowerUsage(monthSum, 'pm25'),
nox: convertPowerUsage(monthSum, 'nox'),
so2: convertPowerUsage(monthSum, 'so2'),
}
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)), //
totalPower: convertPowerUsage(yearSum, 'totalPower'),
fuel: convertPowerUsage(yearSum, 'fuel'),
co2: convertPowerUsage(yearSum, 'co2'),
pm25: convertPowerUsage(yearSum, 'pm25'),
nox: convertPowerUsage(yearSum, 'nox'),
so2: convertPowerUsage(yearSum, 'so2'),
}
@ -900,12 +955,12 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
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)) //
const totalPowerValue = convertPowerUsage(incrementRealTimeSum, 'totalPower')
const co2Value = convertPowerUsage(incrementRealTimeSum, 'co2')
const so2Value = convertPowerUsage(incrementRealTimeSum, 'so2')
const noxValue = convertPowerUsage(incrementRealTimeSum, 'nox')
const pm25Value = convertPowerUsage(incrementRealTimeSum, 'pm25')
const fuelValue = convertPowerUsage(incrementRealTimeSum, 'fuel')
//
totalPowerChartData.value.push({ name: timestamp, value: totalPowerValue })
@ -1022,7 +1077,7 @@ onBeforeUnmount(() => {
.big {
height: 100%;
width: calc(100% - 500px);
width: calc(55%);
.card {
height: 100%;
@ -1038,7 +1093,7 @@ onBeforeUnmount(() => {
}
.right-row {
width: 500px;
width: calc(45% - 8px);
height: 100%;
display: flex;
flex-direction: column;
@ -1097,13 +1152,33 @@ onBeforeUnmount(() => {
}
.time-range-item {
font-size: 16px;
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;
}
.right {
/* gap: 0 */

141
public/map/components/ShowData.vue

@ -1,7 +1,8 @@
<template>
<div class="show-data">
<div class="left" v-if="showShipList" style="width:500px">
<div class="card digital-twin-card--deep-blue ">
<div class="left" v-if="showShipList">
<!-- <div style="display: flex; align-items: center; justify-content: space-between;"> -->
<div class="card digital-twin-card--deep-blue " style=" display: inline-block; width: 420px;">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
@ -49,6 +50,114 @@
</div>
</div>
</div>
<div v-if="shipSelectedItem" class="card digital-twin-card--deep-blue"
style=" display: inline-block; width: 420px;">
<div class="card-title">
<el-button class="close-btn" size="small" type="text" @click="handleCloseShipDetail">×</el-button>
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">船舶详情</span>
</div>
<div class="card-content">
<div class="ship-detail">
<div class="detail-item">
<span class="label">名称:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.name + '-' +
shipSelectedItem.shipBasicInfo.nameEn
}}</span>
</div>
<!-- <div class="detail-item">
<span class="label">英文船名:</span>
<span class="value">{{ selectedItem.shipBasicInfo.nameEn }}</span>
</div> -->
<!-- <div class="detail-item">
<span class="label">船舶呼号:</span>
<span class="value">{{ selectedItem.shipBasicInfo.callSign }}</span>
</div> -->
<div class="detail-item">
<span class="label">长度:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.length }} </span>
</div>
<div class="detail-item">
<span class="label">宽度:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.width }} </span>
</div>
<div class="detail-item">
<span class="label">吨位:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.tonnage }} </span>
</div>
<div class="detail-item">
<span class="label">满载吃水深度:</span>
<span class="value">{{ shipSelectedItem.shipBasicInfo.fullLoadDraft }} </span>
</div>
<div class="detail-item">
<span class="label">电压:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
shipSelectedItem.shorePower.voltageDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">电流:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
shipSelectedItem.shorePower.currentDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">频率:</span>
<span class="value">{{ getValueById(realtimeDeviceData,
shipSelectedItem.shorePower.frequencyDeviceId,
'measureValue') }}</span>
</div>
<div class="detail-item">
<span class="label">靠泊状态:</span>
<span class="value">{{ getOperationTypeLabel(shipSelectedItem.shorePowerAndShip.status,
SHORE_POWER_STATUS,
) }}</span>
</div>
<div class="detail-item">
<span class="label">靠泊类型:</span>
<span class="value">{{ getOperationTypeLabel(shipSelectedItem.shorePowerAndShip.type,
BERTH_TYPE)
}}</span>
</div>
<div class="detail-item">
<span class="label">靠泊时间:</span>
<span class="value">{{
formatTimestamp(shipSelectedItem?.usageRecordInfo?.actualBerthTime) }}</span>
</div>
<div class="detail-item">
<span class="label">当前状态:</span>
<span class="value">{{ shipSelectedItem.shipStatus }}<span v-if="shipSelectedItem.StatusReason">({{
shipSelectedItem.StatusReason
}})</span></span>
</div>
<div v-if="shipSelectedItem.applyInfo.reason === 0" class="detail-item">
<span class="label">岸电使用时长</span>
<span class="value">{{ showStatus(shipSelectedItem, realtimeDeviceData)?.useTime }}</span>
</div>
<div v-if="shipSelectedItem.applyInfo.reason === 0" class="detail-item">
<span class="label">岸电使用用量:</span>
<span class="value">{{ showStatus(shipSelectedItem, realtimeDeviceData)?.useValue }}</span>
</div>
<div v-if="shipSelectedItem.applyInfo.reason != 0" class="detail-item">
<span class="label">未使用岸电原因:</span>
<span class="value">{{ getOperationTypeLabel(shipSelectedItem?.applyInfo?.reason,
UNUSED_SHORE_POWER_REASON) }}</span>
</div>
<div class="detail-item">
<span class="label">岸电联系人:</span>
<span class="value">{{ shipSelectedItem?.shipBasicInfo?.shorePowerContact
}}</span>
</div>
<div class="detail-item">
<span class="label">联系方式:</span>
<span class="value">{{ shipSelectedItem?.shipBasicInfo?.shorePowerContactPhone
}}</span>
</div>
</div>
</div>
</div>
<!-- </div> -->
</div>
<div class="right" style="width: 900px">
<div class="card digital-twin-card--deep-blue">
@ -64,7 +173,7 @@
<div class="module-header">
<div style="display: flex; align-items: center; gap: 20px;">
<h3 class="module-title" @click="handleGoToModule(1)">港区岸电使用情况</h3>
<h3 class="module-title" @click="handleGoToModule(1)">港区岸电用电量统计</h3>
<div class="module-time">
<div>{{ portStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
@ -79,7 +188,7 @@
</div>
<div class="port-overview">
<div class="first-row">
<div class="benefit-card" @click="handleCardClick('overview')">
<div class="benefit-card" @click="handleCardClick('totalPower')">
<div class="benefit-value">{{ chartData[portActivePeriod].totalPower }}</div>
<div class="benefit-unit">累计用电 (千瓦时)</div>
</div>
@ -113,7 +222,7 @@
<div class="module-section">
<div class="module-header">
<div style="display: flex; align-items: center; gap: 20px;">
<h3 class="module-title" @click="handleGoToModule(2)">口港区企业岸电使</h3>
<h3 class="module-title" @click="handleGoToModule(2)">港区企业岸电用电量统计</h3>
<div class="module-time">
<div>{{ enterpriseStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
@ -169,7 +278,7 @@
<div class="module-section">
<div class="module-header">
<div style="display: flex; align-items: center; gap: 20px;">
<h3 class="module-title" @click="handleGoToModule(3)">港区船舶岸电使用情况</h3>
<h3 class="module-title" @click="handleGoToModule(3)">港区船舶岸电用电量统计</h3>
<div class="module-time">
<div>{{ shipStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
@ -259,6 +368,8 @@ import { CompanyShorePowerBuildDataItem, RealtimeDeviceData, ShipRespVo, TimeRan
import { ref, computed } from 'vue'
import { MapApi } from "@/api/shorepower/map";
import ShipHistoryDialog from './ShipHistoryDialog.vue'
import { formatTimestamp, getValueById, showStatus } from './utils';
import { BERTH_TYPE, getOperationTypeLabel, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './dictionaryTable';
interface Props {
handleGoToModule: (moduleType: number) => void
realtimeDeviceDataTime: string
@ -607,8 +718,14 @@ const handleShowShipList = (type: string) => {
//
const handleCloseShipList = () => {
handleCloseShipDetail()
showShipList.value = false
}
//
const handleCloseShipDetail = () => {
shipSelectedItem.value = null
}
</script>
<style scoped lang="scss">
@ -620,13 +737,19 @@ const handleCloseShipList = () => {
.left {
// height: 100%;
// fle width: 300px;
// flex: 1;
display: flex;
flex-direction: row;
width: auto;
// flex-direction: column;
gap: 10px;
.card {
// width: 200px;
padding: 12px;
padding-top: 24px;
padding-bottom: 24px;
}
}
@ -697,7 +820,7 @@ const handleCloseShipList = () => {
}
.module-title {
font-size: 24px;
font-size: 22px;
font-weight: bold;
color: #409eff;
text-decoration: underline;
@ -719,7 +842,7 @@ const handleCloseShipList = () => {
}
.module-time {
font-size: 14px;
font-size: 18px;
color: #67c23a;
}

445
public/map/components/cesiumMap.vue

@ -316,7 +316,6 @@ onMounted(async () => {
});
const dataInfo = history.value ? history.value : await MapApi.getAllData()
const shipData = props.shipDataList
console.log('shipData', shipData)
const shorePowerList = props.shorePowerList
// const unitedData =
@ -329,322 +328,6 @@ onMounted(async () => {
arr4: [],
arr5: []
}
// const addMarkersFromDataInfo = (dataInfo) => {
// //
// if (!dataInfo || !Array.isArray(dataInfo) || dataInfo.length === 0) {
// console.log('No valid dataInfo array provided');
// return [];
// }
// //
// const dataWithModelsArray = [];
// console.log(`Processing ${dataInfo.length} items`);
// dataInfo.forEach((item, index) => {
// //
// if (!item) {
// console.warn(`Item at index ${index} is null or undefined`);
// return;
// }
// try {
// //
// let itemWithModel = { ...item };
// // dataJSON
// let dataObj;
// if (typeof item.data === 'string') {
// try {
// dataObj = JSON.parse(item.data);
// } catch (parseError) {
// console.warn(`Failed to parse data for item ${item.id || index}:`, parseError);
// dataWithModelsArray.push(itemWithModel); //
// return;
// }
// } else {
// dataObj = item.data;
// }
// // dataObj
// if (!dataObj) {
// console.warn(`No data object found for item ${item.id || index}`);
// dataWithModelsArray.push(itemWithModel); //
// return;
// }
// // -
// let longitude, latitude;
// let wgsLon, wgsLat;
// if (dataObj.xy && Array.isArray(dataObj.xy) && dataObj.xy.length >= 2) {
// const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
// wgsLon = wgsCoords[0];
// wgsLat = wgsCoords[1];
// //
// latitude = wgsLat;
// longitude = wgsLon;
// //
// if (isNaN(latitude) || isNaN(longitude)) {
// console.warn(`Invalid coordinates for item ${item.id || index}:`, dataObj.xy);
// dataWithModelsArray.push(itemWithModel); //
// return;
// }
// //
// if (Math.abs(latitude) > 90 || Math.abs(longitude) > 180) {
// console.warn(`Coordinates out of range for item ${item.id || index}:`, dataObj.xy[1], dataObj.xy[0]);
// dataWithModelsArray.push(itemWithModel); //
// return;
// }
// } else {
// console.warn(':', item);
// dataWithModelsArray.push(itemWithModel); //
// return;
// }
// console.log('dataObj.icon', dataObj.icon)
// if (dataObj.icon === 'ship_green' || dataObj.icon === 'ship_red') {
// const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
// if (!itemShipInfo) {
// return;
// }
// const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 15);
// const statusPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
// const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35);
// //
// const shipModel = viewer.entities.add({
// name: 'Cargo Ship Model',
// position: position,
// orientation: Cesium.Transforms.headingPitchRollQuaternion(
// position,
// new Cesium.HeadingPitchRoll(
// Cesium.Math.toRadians(dataObj.rotationAngle), // (Z)
// Cesium.Math.toRadians(0), // (Y)
// Cesium.Math.toRadians(0) // (X)
// )
// ),
// model: {
// uri: '/model/cargo_ship_07.glb',
// scale: 1.5, //
// // minimumPixelSize: 100, // 50
// // 使
// enableDepthTest: true,
// //
// backFaceCulling: true
// }
// });
// //
// itemWithModel.modelInstance = shipModel;
// itemWithModel.modelType = 'ship';
// itemWithModel = { ...itemWithModel, ...itemShipInfo };
// viewer.entities.add({
// position: labelPosition, // 10
// label: {
// text: itemShipInfo.shipBasicInfo.name || `Marker-${item.id || index}`,
// font: '20px sans-serif',
// fillColor: Cesium.Color.LIME,
// outlineColor: Cesium.Color.BLACK,
// outlineWidth: 2,
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// pixelOffset: new Cesium.Cartesian2(0, -30), //
// disableDepthTestDistance: Number.POSITIVE_INFINITY //
// }
// });
// //
// // 50%25%25%线
// const rand = Math.random();
// let statusImage = null;
// if (rand < 1) {
// // 50%
// statusImage = null;
// } else if (rand < 0.9) {
// // 25%
// statusImage = '/img/.png';
// } else {
// // 25%线
// statusImage = '/img/线.png';
// }
// //
// if (statusImage) {
// const overlayBillboard = viewer.entities.add({
// position: statusPosition,
// billboard: {
// image: statusImage,
// horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
// verticalOrigin: Cesium.VerticalOrigin.CENTER,
// disableDepthTestDistance: Number.POSITIVE_INFINITY,
// scale: new Cesium.CallbackProperty(function (time, result) {
// // t
// const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
// const pulse = 0.4 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2)
// return pulse;
// }, false)
// }
// });
// }
// }
// if (dataObj.type === 'text') {
// const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35);
// viewer.entities.add({
// position: labelPosition, // 10
// label: {
// text: dataObj.name || `Marker-${item.id || index}`,
// font: '20px sans-serif',
// fillColor: Cesium.Color.WHITE,
// outlineColor: Cesium.Color.BLACK,
// outlineWidth: 2,
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// pixelOffset: new Cesium.Cartesian2(0, -30), //
// disableDepthTestDistance: Number.POSITIVE_INFINITY //
// }
// });
// }
// if (dataObj.type === 'icon' && (dataObj.icon === 'interface_blue' || dataObj.icon === 'interface_red')) {
// const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
// const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
// const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 5);
// //
// const electricalBoxModel = viewer.entities.add({
// name: '',
// position: position,
// //
// properties: {
// modelType: 'electrical_box',
// data: itemWithModel
// },
// orientation: Cesium.Transforms.headingPitchRollQuaternion(
// position,
// new Cesium.HeadingPitchRoll(
// Cesium.Math.toRadians(dataObj.rotationAngle), // (Z)
// Cesium.Math.toRadians(0), // (Y)
// Cesium.Math.toRadians(0) // (X)
// )
// ),
// model: {
// uri: '/model/electrical_box.glb',
// scale: 1.5, //
// // minimumPixelSize: 10, // 50
// // 使
// enableDepthTest: true,
// //
// backFaceCulling: true
// },
// });
// console.log(item)
// if (item.name.includes('-206-') || item.name.includes('-207-')) {
// arr.push(electricalBoxModel)
// }
// //
// itemWithModel.modelInstance = electricalBoxModel;
// itemWithModel.modelType = 'electrical_box';
// itemWithModel = { ...itemWithModel, ...itemShipInfo };
// viewer.entities.add({
// // pickable: false,
// position: labelPosition, // 10
// properties: {
// modelType: 'electrical_box',
// data: itemWithModel
// },
// label: {
// text: '' || `Marker-${item.id || index}`,
// font: '16px sans-serif',
// fillColor: Cesium.Color.WHITE,
// outlineColor: Cesium.Color.BLACK,
// outlineWidth: 2,
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// pixelOffset: new Cesium.Cartesian2(0, -30), //
// disableDepthTestDistance: Number.POSITIVE_INFINITY //
// }
// });
// //
// // 50%25%25%线
// console.log(shorePowerList, itemShipInfo)
// const x = itemShipInfo.shorePower.id;
// const itemObj = shorePowerList.find(obj => obj.id === x);
// const rand = Math.random();
// let statusImage = null;
// if ([3, 4, 6].includes(itemObj.status)) {
// // 50%
// statusImage = null;
// } else if ([1, 2, 8].includes(itemObj.status)) {
// // 25%
// statusImage = '/img/.png';
// } else {
// // 25%线
// statusImage = '/img/线.png';
// }
// //
// if (statusImage) {
// const overlayBillboard = viewer.entities.add({
// position: position,
// // pickable: false,
// properties: {
// modelType: 'electrical_box',
// data: itemWithModel
// },
// billboard: {
// image: statusImage,
// horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
// verticalOrigin: Cesium.VerticalOrigin.CENTER,
// disableDepthTestDistance: Number.POSITIVE_INFINITY,
// scale: new Cesium.CallbackProperty(function (time, result) {
// // t
// const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
// const pulse = 0.2 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2)
// return pulse;
// }, false)
// }
// });
// }
// }
// //
// /* const entity = viewer.entities.add({
// name: item.name || `Marker-${item.id || index}`,
// position: Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1),
// point: {
// pixelSize: 10,
// color: Cesium.Color.RED,
// outlineColor: Cesium.Color.WHITE,
// outlineWidth: 2
// },
// //
// label: {
// text: item.name || `Marker-${item.id || index}`,
// font: '14px sans-serif',
// fillColor: Cesium.Color.WHITE,
// outlineColor: Cesium.Color.BLACK,
// outlineWidth: 2,
// style: Cesium.LabelStyle.FILL_AND_OUTLINE,
// verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
// pixelOffset: new Cesium.Cartesian2(0, -15)
// }
// });
// //
// itemWithModel.modelInstance = entity;
// itemWithModel.modelType = 'point';
// */
// //
// dataWithModelsArray.push(itemWithModel);
// } catch (error) {
// console.error('Error processing item:', item, error);
// //
// dataWithModelsArray.push({ ...item });
// }
// });
// return dataWithModelsArray;
// };
// 3: Viewer
viewer.imageryLayers.addImageryProvider(gaodeImage);
@ -704,29 +387,29 @@ onMounted(async () => {
backFaceCulling: true
},
});
console.log(item.name)
console.log('111111111')
if (['国投-206#泊位-高压', '国投-207#泊位-高压', '国投-208#泊位-高压'].includes(item.name)) {
console.log('2222222222')
arr.arr1.push(xelectricalBoxModel)
const status = item.storePowerStatus == '在⽤' ? 1 : 0
arr.arr1.push({ xelectricalBoxModel, status })
}
if (['国投-209#泊位-高压', '国投-210#泊位-高压'].includes(item.name)) {
arr.arr2.push(xelectricalBoxModel)
const status = item.storePowerStatus == '在⽤' ? 1 : 0
arr.arr2.push({ xelectricalBoxModel, status })
}
if (['国投-203#泊位-高压', '国投-204#泊位-高压', '国投-205#泊位-高压'].includes(item.name)) {
arr.arr3.push(xelectricalBoxModel)
const status = item.storePowerStatus == '在⽤' ? 1 : 0
arr.arr3.push({ xelectricalBoxModel, status })
}
if (['华电-806#泊位-高压', '华电-807#泊位-高压'].includes(item.name)) {
arr.arr4.push(xelectricalBoxModel)
const status = item.storePowerStatus == '在⽤' ? 1 : 0
arr.arr4.push({ xelectricalBoxModel, status })
}
if (['华电-808#泊位-高压', '华电-809#泊位-高压', '华电-810#泊位-高压'].includes(item.name)) {
arr.arr5.push(xelectricalBoxModel)
const status = item.storePowerStatus == '在⽤' ? 1 : 0
arr.arr5.push({ xelectricalBoxModel, status })
}
modelInstance.push({
model: xelectricalBoxModel,
type: 'shorepower_box',
@ -797,10 +480,8 @@ onMounted(async () => {
labelInstance.push(xelectricalBoxLabel)
})
shipData.forEach(item => {
// console.log(dataInfo)
const itemShipInfo = dataInfo.filter(shipItem => (item.shorePower.id === shipItem.parentId) && shipItem.type === 5)
// console.log(itemShipInfo)
const result = itemShipInfo
.filter(item => {
try {
@ -813,7 +494,6 @@ onMounted(async () => {
.map(item => JSON.parse(item.data));
if (!result || result.length === 0) return
const dataObj = result[0]
console.log(dataObj)
let longitude, latitude;
let wgsLon, wgsLat;
const wgsCoords = coordtransform.wgs84togcj02(dataObj.xy[1], dataObj.xy[0]);
@ -858,12 +538,12 @@ onMounted(async () => {
statusImage = '/img/故障.png';
labelColor = Cesium.Color.YELLOW; //
} else {
statusImage = '/img/未连接.png';
labelColor = Cesium.Color.RED; // 线
statusImage = '/img/故障.png';
labelColor = Cesium.Color.YELLOW; // 线
}
if (item.applyInfo.reason !== 0) {
statusImage = '/img/故障.png';
labelColor = Cesium.Color.YELLOW;
statusImage = '/img/未连接.png';
labelColor = Cesium.Color.RED;
}
//
if (statusImage) {
@ -916,22 +596,10 @@ onMounted(async () => {
} : createPulsingLabel(item.shipBasicInfo.name, labelColor)
});
labelInstance.push(xelectricalBoxLabel)
/* if (!itemShipInfo) {
return;
} */
/* if (!itemShipInfo || itemShipInfo.length === 0) return
const itemShipInfoObj = itemShipInfo[0]
console.log('itemShipInfoObj', itemShipInfoObj)
console.log('dataInfo', dataInfo)
console.log('item', item) */
})
//
dataInfo.forEach(item => {
// console.log(item)
if (['华能曹妃甸码头名字', '华电储运码头名字', '国投曹妃甸码头名字'].includes(item.name)) {
const dataObj = JSON.parse(item.data)
let longitude, latitude;
@ -960,7 +628,6 @@ onMounted(async () => {
}
})
dataInfo.forEach(item => {
// console.log(item)
if (['港区'].includes(item.name)) {
const dataObj = JSON.parse(item.data)
let longitude, latitude;
@ -1007,12 +674,13 @@ onMounted(async () => {
]
// 线
function createFlowMaterial() {
function createFlowMaterial(status) {
console.log(status)
return new PolylineFlowMaterialProperty({
color: Cesium.Color.YELLOW,
color: status ? Cesium.Color.LIME : Cesium.Color.YELLOW,
duration: 5000, // ()
percent: 0.6, // 线
gradient: 0.3 //
percent: 1, // 线
gradient: 1 //
});
}
function tempCreate(item) {
@ -1090,7 +758,9 @@ onMounted(async () => {
}
});
// 线xelectricalBoxModelarr
arr[`arr${index + 1}`].forEach((entity, idx) => {
arr[`arr${index + 1}`].forEach((entityItem, idx) => {
console.log(entityItem)
const entity = entityItem.xelectricalBoxModel
// 线ID
const lineEntity = viewer.entities.add({
name: `connection_line_${idx}`,
@ -1105,12 +775,11 @@ onMounted(async () => {
return [mainPosition, targetPosition];
}, false),
width: 3,
material: createFlowMaterial(),
material: createFlowMaterial(entityItem.status),
// 使线
enableDepthTest: true
}
});
console.log('lineEntity', lineEntity)
});
})
@ -1121,11 +790,6 @@ onMounted(async () => {
viewer.camera.flyTo(createView(presetViews.overview));
//
const SHOW_HEIGHT = 30000; // > 5000m
// const targets = []; // entities push
/* const targets = titleInstance
.filter(item => item.modelInstance)
.map(item => toRaw(item.modelInstance)); */
// console.log('targets', targets)
viewer.camera.changed.addEventListener(() => {
//
const height = viewer.camera.positionCartographic.height;
@ -1154,69 +818,6 @@ onMounted(async () => {
}
});
//
/* const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
console.log('地图点击事件触发');
//
const pickedObject = viewer.scene.pick(movement.position);
if (pickedObject && pickedObject.id && pickedObject.id.properties) {
//
const properties = pickedObject.id.properties;
if (properties.modelType && properties.modelType.getValue() === 'shorepower_box') {
//
const electricalBoxData = properties.data.getValue();
console.log('岸电箱模型被点击:', electricalBoxData);
//
if (typeof onElectricalBoxClick === 'function') {
onElectricalBoxClick(electricalBoxData);
}
//
return;
}
}
//
//
const ray = viewer.camera.getPickRay(movement.position);
// 1:
let cartesian = viewer.scene.globe.pick(ray, viewer.scene);
//
if (!cartesian) {
cartesian = viewer.scene.pickPosition(movement.position);
console.log('使用场景拾取方法');
}
// 使
if (!cartesian) {
//
console.log('无法从点击位置获取精确坐标');
return;
}
if (cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
if (cartographic) {
const longitude = Cesium.Math.toDegrees(cartographic.longitude);
const latitude = Cesium.Math.toDegrees(cartographic.latitude);
const gcj02 = coordtransform.gcj02towgs84(longitude, latitude);
console.log('点击位置经纬度:', {
longitude: gcj02[0],
latitude: gcj02[1]
});
} else {
console.log('无法从笛卡尔坐标转换为大地坐标');
}
} else {
console.log('未能获取到地球表面的点击位置');
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); */
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
const pickedObject = viewer.scene.pick(movement.position);

226
public/map/components/charts/ComparisonBarChart.vue

@ -12,205 +12,119 @@ interface Props {
name: string
currentValue: number
previousValue: number
growthRate?: number
currentPeriod?: string
previousPeriod?: string
}>
title?: string
currentColor?: string
previousColor?: string
showYAxis?: boolean
yAxisName?: string
currentLabel?: string
previousLabel?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
currentColor: '#1296db',
previousColor: '#ff6b6b',
currentColor: '#1890FF',
previousColor: '#A9A9A9',
showYAxis: true,
yAxisName: '数值'
yAxisName: '数值',
currentLabel: '本期',
previousLabel: '上期'
})
//
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
const option = {
title: {
text: props.title,
textStyle: {
color: '#fff',
fontSize: 14
},
left: 'center'
},
//
const createBarOption = (currentLabel: string, previousLabel: string, currentValue: number, previousValue: number) => {
return {
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff'
},
formatter: function (params: any) {
const currentData = params[0]
const previousData = params[1]
const growthRate = previousData.value > 0
? ((currentData.value - previousData.value) / previousData.value * 100).toFixed(2)
: '∞'
return `${currentData.name}<br/>
${currentData.seriesName}: ${currentData.value}<br/>
${previousData.seriesName}: ${previousData.value}<br/>
增长率: ${growthRate}%`
}
},
legend: {
data: ['本期', '上期'],
textStyle: {
color: '#fff'
},
top: 30
axisPointer: { type: 'shadow' }
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '25%',
containLabel: true
left: '15%',
right: '10%',
bottom: '20%',
top: '15%'
},
xAxis: {
type: 'category',
data: props.chartData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
data: [previousLabel, currentLabel],
axisTick: { show: false },
axisLine: { show: false },
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
rotate: 0,
interval: 0,
margin: 15
fontSize: 22
}
},
yAxis: {
type: 'value',
show: props.showYAxis,
name: props.yAxisName,
nameTextStyle: {
axisLabel: {
formatter: '{value}',
color: 'rgba(255, 255, 255, 0.7)'
},
axisLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
show: props.showYAxis,
color: 'rgba(255, 255, 255, 0.7)'
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
}
},
series: [
{
name: '当前期',
type: 'bar',
data: props.chartData.map(item => item.currentValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.currentColor
},
{
offset: 1,
color: props.currentColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.currentColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
}
},
{
name: '对比期',
type: 'bar',
data: props.chartData.map(item => item.previousValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.previousColor
},
{
offset: 1,
color: props.previousColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.previousColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
series: [{
name: '用量',
type: 'bar',
barWidth: '60%',
data: [
{ value: previousValue, itemStyle: { color: props.previousColor } },
{ value: currentValue, itemStyle: { color: props.currentColor } }
],
label: {
show: true,
position: 'top',
fontWeight: 'bold',
color: '#fff',
formatter: (params: any) => {
return params.value > 0 ? params.value.toFixed(0) : '';
}
}
]
}]
};
}
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
const dataItem = props.chartData[0]
// 使period使
const currentLabel = dataItem.currentPeriod || props.currentLabel
const previousLabel = dataItem.previousPeriod || props.previousLabel
const option = createBarOption(
currentLabel,
previousLabel,
dataItem.currentValue,
dataItem.previousValue
)
chartInstance.setOption(option)
}

162
public/map/index.vue

@ -45,13 +45,14 @@
<!-- 港口岸电使用情况单数据 -->
<ShorePowerUsageSingleData v-if="activeHeadGroup === 5 && dataLoad"
:realtime-device-data-time="realtimeDeviceDataTime" :realtime-device-data="realtimeDeviceData"
:active-head-group="activeHeadGroup" :handleGoBack="handleGoBack" />
:active-head-group="activeHeadGroup" :handleGoBack="handleGoBack" :comparative-data="comparativeData" />
<!-- 港口企业岸电使用 -->
<!-- <template v-if="activeHeadGroup === 2"> -->
<CompanyShorePower v-show="activeHeadGroup === 2" :realtime-device-data="realtimeDeviceData"
:shore-power-list="ShorePowerList" :handleGoBack="handleGoBack"
:realtime-device-data-time="realtimeDeviceDataTime" v-if="dataLoad" />
:shore-power-list="ShorePowerList" :handleGoBack="handleGoBack" :year-data="yearDataRes"
:month-data="monthDataRes" :day-data="dayDataRes" :realtime-device-data-time="realtimeDeviceDataTime"
v-if="dataLoad" />
<!-- </template> -->
<!-- 船舶岸电使用情况 -->
@ -260,7 +261,7 @@ import dayjs from 'dayjs'
import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
import { Loading } from '@element-plus/icons-vue'
import { MapApi } from "@/api/shorepower/map";
import { CompanyShorePowerBuildDataItem, RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'
import { CompanyShorePowerBuildDataItem, ComparativeData, RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './components/dictionaryTable'
import { formatDuration, formatTimestamp, getValueById, showStatus } from './components/utils'
defineOptions({ name: 'PublicMap' })
@ -324,6 +325,8 @@ const yearDataRes = ref<any>({})
const monthDataRes = ref<any>({})
const dayDataRes = ref<any>({})
const comparativeData = ref<ComparativeData>({})
//
@ -627,6 +630,9 @@ const handleGetStorePower = async () => {
}
const getShipStatus = (ship: ShipRespVo) => {
if (ship.applyInfo.reason != 0) {
return '异常'
}
if (ship.shorePowerAndShip.status === 9) {
return '正常'
} else {
@ -646,7 +652,6 @@ const getShipStatus = (ship: ShipRespVo) => {
let capacity = 0
try {
capacity = Number(ship.shorePowerEquipment.capacity)
console.log('capacity', capacity, Vobj.measureValue, Cobj?.measureValue)
} catch (error) {
return '获取岸电容量失败'
}
@ -666,16 +671,37 @@ const handleGetShipData = async () => {
const buildData = shipData.map(item => ({
...item,
shipStatus: getShipStatus(item),
StatusReason: getShipStatus(item) === '故障' ? '跳闸' : '',
}))
shipDataList.value = buildData
return buildData
}
/* 本日实时用量 */
const todayEnergyUsing = computed(() => {
const kwhData = realtimeDeviceData.value.filter(item => item.deviceCode.includes('Kwh')).reduce((sum, item) => sum + item.measureValue, 0)
const dayData = dayDataRes.value.reduce((sum, item) => sum + item.measureValue, 0)
return kwhData - dayData
})
/* 本月实时用量 */
const monthEnergyUsing = computed(() => {
const kwhData = realtimeDeviceData.value.filter(item => item.deviceCode.includes('Kwh')).reduce((sum, item) => sum + item.measureValue, 0)
const monthData = monthDataRes.value.reduce((sum, item) => sum + item.measureValue, 0)
return kwhData - monthData
})
/* 本年实时用量 */
const yearEnergyUsing = computed(() => {
const kwhData = realtimeDeviceData.value.filter(item => item.deviceCode.includes('Kwh')).reduce((sum, item) => sum + item.measureValue, 0)
const yearData = yearDataRes.value.reduce((sum, item) => sum + item.measureValue, 0)
return kwhData - yearData
})
//
onMounted(async () => {
dockList.value = await MapApi.getDockIdAndNameList()
berthList.value = await MapApi.getBerthIdAndNameList()
// DOM
const firstData = await handleGetRealtimeAllData()
const res: RealtimeDeviceData[] = firstData.filter(item => item.deviceCode.includes('Kwh'));
@ -687,9 +713,15 @@ onMounted(async () => {
yearDataRes.value = await MapApi.getYearDataByIdList(params);
monthDataRes.value = await MapApi.getMonthDataByIdList(params);
dayDataRes.value = await MapApi.getDayDataByIdList(params);
const applyShipCount = await MapApi.getApplyShipCount()
console.log('applyShipCount', applyShipCount)
handleGetlRangeData(firstData)
handleBuildCompanyShorePower(firstData)
const buildComparativeData = await handleBuildTimeComparison()
console.log('buildComparativeData', buildComparativeData)
comparativeData.value = buildComparativeData
await handleGetStorePower()
const tempBuildShipData = await handleGetShipData()
handleBuildCompanyShorePowerYear(tempBuildShipData)
@ -1034,6 +1066,120 @@ const handleBuildCompanyShorePowerYear = (shipData: any) => {
}
}
/**
* 构建日年三个维度的环比对比数据
* - 日环比今日 00:00 至当前 vs 昨日 00:00 至昨日此时
* - 月环比本月 01 日至当前 vs 上月 01 日至上月同日对齐天数
* - 年环比本年 01-01 至当前 vs 去年 01-01 至去年同日对齐天数
*/
const handleBuildTimeComparison = async (): Promise<ComparativeData> => {
const now = dayjs();
/**
* 计算每个分组的周期用量last.measureValue - first.measureValue 的总和
*/
const calculatePeriodUsage = (apiResponse: Record<string, { measureValue: number }[]>): number => {
const dataArrays = Object.values(apiResponse).filter(Array.isArray) as { measureValue: number }[][];
return dataArrays.reduce((total, row) => {
if (row.length === 0) return total;
const firstValue = row[0].measureValue;
const lastValue = row[row.length - 1].measureValue;
return total + (lastValue - firstValue);
}, 0);
};
/**
* 安全计算环比增长率(current - previous) / previous
* - previous 0 current 也为 0 返回 0
* - previous 0 current > 0 返回 null表示新增
*/
const calculateGrowthRate = (current: number, previous: number): number | null => {
if (previous === 0) {
return current === 0 ? 0 : null; // null 0
}
return (current - previous) / previous;
};
// ===== =====
const todayStart = now.startOf('day').valueOf();
const todayEnd = now.valueOf();
const yesterday = now.subtract(1, 'day');
const yesterdayStart = yesterday.startOf('day').valueOf();
const yesterdayEnd = yesterday.valueOf();
// ===== =====
const thisMonthStart = now.startOf('month').valueOf();
const thisMonthEnd = now.valueOf();
const lastMonth = now.subtract(1, 'month');
const lastMonthStart = lastMonth.startOf('month').valueOf();
// 3130
const daysInLastMonth = lastMonth.daysInMonth();
const todayDate = now.date();
const alignedDay = Math.min(todayDate, daysInLastMonth);
const lastMonthEnd = lastMonth.date(alignedDay).endOf('day').valueOf();
// ===== =====
const thisYearStart = now.startOf('year').valueOf();
const thisYearEnd = now.valueOf();
const lastYear = now.subtract(1, 'year');
const lastYearStart = lastYear.startOf('year').valueOf();
// / 2 29
let lastYearEnd: number;
try {
//
lastYearEnd = lastYear.month(now.month()).date(now.date()).endOf('day').valueOf();
} catch {
// 2/29
lastYearEnd = lastYear.month(now.month()).endOf('month').valueOf();
}
// ===== 6 =====
const [
todayRes,
yesterdayRes,
thisMonthRes,
lastMonthRes,
thisYearRes,
lastYearRes
] = await Promise.all([
MapApi.getByStartAndEndTimeAndTimeType({ start: todayStart, end: todayEnd, timeType: 2 }),
MapApi.getByStartAndEndTimeAndTimeType({ start: yesterdayStart, end: yesterdayEnd, timeType: 2 }),
MapApi.getByStartAndEndTimeAndTimeType({ start: thisMonthStart, end: thisMonthEnd, timeType: 3 }),
MapApi.getByStartAndEndTimeAndTimeType({ start: lastMonthStart, end: lastMonthEnd, timeType: 3 }),
MapApi.getByStartAndEndTimeAndTimeType({ start: thisYearStart, end: thisYearEnd, timeType: 4 }),
MapApi.getByStartAndEndTimeAndTimeType({ start: lastYearStart, end: lastYearEnd, timeType: 4 })
]);
// ===== =====
const todayUsage = calculatePeriodUsage(todayRes);
const yesterdayUsage = calculatePeriodUsage(yesterdayRes);
const thisMonthUsage = calculatePeriodUsage(thisMonthRes);
const lastMonthUsage = calculatePeriodUsage(lastMonthRes);
const thisYearUsage = calculatePeriodUsage(thisYearRes);
const lastYearUsage = calculatePeriodUsage(lastYearRes);
// ===== =====
return {
day: {
growthRate: calculateGrowthRate(todayUsage, yesterdayUsage),
current: { period: now.format('YYYY-MM-DD'), value: todayUsage },
previous: { period: yesterday.format('YYYY-MM-DD'), value: yesterdayUsage }
},
month: {
growthRate: calculateGrowthRate(thisMonthUsage, lastMonthUsage),
current: { period: now.format('YYYY-MM'), value: thisMonthUsage },
previous: { period: lastMonth.format('YYYY-MM'), value: lastMonthUsage }
},
year: {
growthRate: calculateGrowthRate(thisYearUsage, lastYearUsage),
current: { period: now.format('YYYY'), value: thisYearUsage },
previous: { period: lastYear.format('YYYY'), value: lastYearUsage }
}
};
};
</script>
<style lang="scss">
.cesium-viewer-bottom {
@ -1508,9 +1654,9 @@ const handleBuildCompanyShorePowerYear = (shipData: any) => {
/* 故障状态 */
.status-fault {
background-color: #F44336;
background-color: #FF9800;
/* 红色 */
box-shadow: 0 0 5px rgba(244, 67, 54, 0.5);
box-shadow: 0 0 5px rgba(255, 152, 0, 0.5);
}
.status-abnormal {

8
src/api/shorepower/map/index.ts

@ -63,7 +63,6 @@ export const MapApi = {
? `?parentId=${parentId}`
: ''
const data = await request.get({ url: `/shorepower/map/expand/selectAll` + params })
console.log('data:', data)
return data
},
// 查询船舶信息
@ -179,5 +178,12 @@ export const MapApi = {
url: `/shorepower/shore-power-and-ship/expand/getHistoryPage`,
params
})
},
getApplyShipCount: async (): Promise<apiResponse<any>> => {
return await request.get({
url: `shorepower/apply/getShipCount`
// params
})
}
}

36
src/types/shorepower.d.ts

@ -493,3 +493,39 @@ type CompanyShorePowerBuildDataItem = {
}[]
}
}
interface ComparativeData {
day: {
growthRate: number
current: {
period: string
value: number
}
previous: {
period: string
value: number
}
}
month: {
growthRate: number
current: {
period: string
value: number
}
previous: {
period: string
value: number
}
}
year: {
growthRate: number
current: {
period: string
value: number
}
previous: {
period: string
value: number
}
}
}

Loading…
Cancel
Save