|
|
|
@ -2,7 +2,7 @@ |
|
|
|
<div> |
|
|
|
<PublicMapComponents ref="mapComponentRef" class="map-base" :shore-power-list="ShorePowerList" |
|
|
|
:ship-data-list="shipDataList" v-if="dataLoad" :on-instance-click="handleElectricalBoxClick" |
|
|
|
:distribution-data-list="distributionDataList" /> |
|
|
|
:distribution-data-list="distributionDataList" :get-device-status="getDeviceStatus" /> |
|
|
|
<div v-else class="loading"> |
|
|
|
<div class="loading-container"> |
|
|
|
<el-space direction="vertical" size="large"> |
|
|
|
@ -240,7 +240,11 @@ |
|
|
|
</div> |
|
|
|
<div class="detail-item"> |
|
|
|
<span class="label">当前状态:</span> |
|
|
|
<span class="value">{{ selectedElectricalBox.data?.storePowerStatus }}</span> |
|
|
|
<span class="value">{{ getOperationTypeLabel(shorePowerStatus, |
|
|
|
SHORE_POWER_FIRST_STATUS, |
|
|
|
) }}({{ getOperationTypeLabel(shorePowerStatus, |
|
|
|
SHORE_POWER_SECOND_STATUS_MAP, |
|
|
|
) }})</span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -305,7 +309,7 @@ import { onMounted, onUnmounted, ref, computed, watch } from 'vue' |
|
|
|
import { Loading } from '@element-plus/icons-vue' |
|
|
|
import { MapApi } from "@/api/shorepower/map"; |
|
|
|
import { CompanyShorePowerBuildDataItem, CompanyShorePowerData, ComparativeData, RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth, TimeRange } from '@/types/shorepower' |
|
|
|
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './components/dictionaryTable' |
|
|
|
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_FIRST_STATUS, SHORE_POWER_SECOND_STATUS_MAP, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './components/dictionaryTable' |
|
|
|
import { formatDuration, formatTimestamp, getValueById, showStatus } from './components/utils' |
|
|
|
defineOptions({ name: 'PublicMap' }) |
|
|
|
let getRealtimeIntervalId: NodeJS.Timeout | null = null |
|
|
|
@ -451,6 +455,26 @@ const updateTime = () => { |
|
|
|
currentTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss') |
|
|
|
} |
|
|
|
|
|
|
|
const shorePowerStatus = computed(() => { |
|
|
|
if (!selectedElectricalBox.value.data?.totalPowerDeviceId) { |
|
|
|
return '' |
|
|
|
} |
|
|
|
const status = getDeviceStatus(selectedElectricalBox.value.data?.totalPowerDeviceId) |
|
|
|
if (status === null) { |
|
|
|
return '' |
|
|
|
} |
|
|
|
return status |
|
|
|
}) |
|
|
|
|
|
|
|
/* 用户实时设备状态 */ |
|
|
|
const getDeviceStatus = (deviceId: number) => { |
|
|
|
const data = realtimeDeviceData.value.find(item => item.deviceId === deviceId) |
|
|
|
if (!data) { |
|
|
|
return null |
|
|
|
} |
|
|
|
return data.deviceStatus |
|
|
|
} |
|
|
|
|
|
|
|
const selectedShip = ref(null) |
|
|
|
const selectedElectricalBox = ref({ |
|
|
|
type: '', |
|
|
|
@ -776,7 +800,7 @@ onMounted(async () => { |
|
|
|
handleBuildCompanyShorePower(firstData) |
|
|
|
|
|
|
|
const buildComparativeData = await handleBuildTimeComparison() |
|
|
|
console.log('buildComparativeData', buildComparativeData) |
|
|
|
// console.log('buildComparativeData', buildComparativeData) |
|
|
|
comparativeData.value = buildComparativeData |
|
|
|
|
|
|
|
await handleGetStorePower() |
|
|
|
@ -784,6 +808,8 @@ onMounted(async () => { |
|
|
|
handleBuildCompanyShorePowerYear(tempBuildShipData) |
|
|
|
getDistributionBoxDataList() |
|
|
|
await handleGetBuildData() |
|
|
|
buildShorePowerUsageRatio() |
|
|
|
|
|
|
|
dataLoad.value = true |
|
|
|
getRealtimeIntervalId = setInterval(async () => { |
|
|
|
const data = await handleGetRealtimeAllData() |
|
|
|
@ -1338,6 +1364,147 @@ const handleBuildTimeComparison = async (): Promise<ComparativeData> => { |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
/** |
|
|
|
* 构建岸电使用率环比和同比 |
|
|
|
*/ |
|
|
|
const buildShorePowerUsageRatio = () => { |
|
|
|
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 | null, previous: number | null): number => { |
|
|
|
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 lastYearToday = now.subtract(1, 'year'); |
|
|
|
const lastYearTodayStart = lastYearToday.startOf('day').valueOf(); |
|
|
|
const lastYearTodayEnd = lastYearToday.valueOf(); |
|
|
|
|
|
|
|
// ===== 构建月环比参数(对齐天数)===== |
|
|
|
const thisMonthStart = now.startOf('month').valueOf(); |
|
|
|
const thisMonthEnd = now.valueOf(); |
|
|
|
|
|
|
|
const lastMonth = now.subtract(1, 'month'); |
|
|
|
const lastMonthStart = lastMonth.startOf('month').valueOf(); |
|
|
|
// 上月可能没有"今天"这个日期(如今天31号,上月只有30天),取最小值避免跨月 |
|
|
|
const daysInLastMonth = lastMonth.daysInMonth(); |
|
|
|
const todayDate = now.date(); |
|
|
|
const alignedDay = Math.min(todayDate, daysInLastMonth); |
|
|
|
const lastMonthEnd = lastMonth.date(alignedDay).endOf('day').valueOf(); |
|
|
|
|
|
|
|
// ===== 构建月同比参数(对齐天数)===== |
|
|
|
const lastYearMonth = now.subtract(1, 'year'); |
|
|
|
const lastYearMonthStart = lastYearMonth.startOf('month').valueOf(); |
|
|
|
// 同样处理闰年/年末问题 |
|
|
|
const daysInLastYearMonth = lastYearMonth.daysInMonth(); |
|
|
|
const lastYearAlignedDay = Math.min(todayDate, daysInLastYearMonth); |
|
|
|
const lastYearMonthEnd = lastYearMonth.date(lastYearAlignedDay).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(); |
|
|
|
} |
|
|
|
|
|
|
|
// ===== 并发请求所有8个时间段的数据 ===== |
|
|
|
const [ |
|
|
|
todayRes, |
|
|
|
yesterdayRes, |
|
|
|
lastYearTodayRes, |
|
|
|
thisMonthRes, |
|
|
|
lastMonthRes, |
|
|
|
lastYearMonthRes, |
|
|
|
thisYearRes, |
|
|
|
lastYearRes |
|
|
|
] = await Promise.all([ |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: todayStart, end: todayEnd, timeType: 2 }), |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: yesterdayStart, end: yesterdayEnd, timeType: 2 }), |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: lastYearTodayStart, end: lastYearTodayEnd, timeType: 2 }), |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: thisMonthStart, end: thisMonthEnd, timeType: 3 }), |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: lastMonthStart, end: lastMonthEnd, timeType: 3 }), |
|
|
|
MapApi.getByStartAndEndTimeAndTimeType({ start: lastYearMonthStart, end: lastYearMonthEnd, 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 lastYearTodayUsage = calculatePeriodUsage(lastYearTodayRes); |
|
|
|
const thisMonthUsage = calculatePeriodUsage(thisMonthRes); |
|
|
|
const lastMonthUsage = calculatePeriodUsage(lastMonthRes); |
|
|
|
const lastYearMonthUsage = calculatePeriodUsage(lastYearMonthRes); |
|
|
|
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 } |
|
|
|
}, |
|
|
|
dayYearOnYear: { |
|
|
|
growthRate: calculateGrowthRate(todayUsage, lastYearTodayUsage), |
|
|
|
current: { period: now.format('YYYY-MM-DD'), value: todayUsage }, |
|
|
|
previous: { period: lastYearToday.format('YYYY-MM-DD'), value: lastYearTodayUsage } |
|
|
|
}, |
|
|
|
month: { |
|
|
|
growthRate: calculateGrowthRate(thisMonthUsage, lastMonthUsage), |
|
|
|
current: { period: now.format('YYYY-MM'), value: thisMonthUsage }, |
|
|
|
previous: { period: lastMonth.format('YYYY-MM'), value: lastMonthUsage } |
|
|
|
}, |
|
|
|
monthYearOnYear: { |
|
|
|
growthRate: calculateGrowthRate(thisMonthUsage, lastYearMonthUsage), |
|
|
|
current: { period: now.format('YYYY-MM'), value: thisMonthUsage }, |
|
|
|
previous: { period: lastYearMonth.format('YYYY-MM'), value: lastYearMonthUsage } |
|
|
|
}, |
|
|
|
year: { |
|
|
|
growthRate: calculateGrowthRate(thisYearUsage, lastYearUsage), |
|
|
|
current: { period: now.format('YYYY'), value: thisYearUsage }, |
|
|
|
previous: { period: lastYear.format('YYYY'), value: lastYearUsage } |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
</script> |
|
|
|
<style lang="scss"> |
|
|
|
|