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.

1346 lines
41 KiB

3 weeks ago
<template>
<div class="show-data">
2 weeks ago
<div v-if="showShipList || shorePowerCardShow" class="left">
2 weeks ago
<!-- <div style="display: flex; align-items: center; justify-content: space-between;"> -->
2 weeks ago
<div v-if="showShipList && !shorePowerCardShow" class="card digital-twin-card--deep-blue "
style=" display: inline-block; width: 420px;">
3 weeks ago
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">船舶状态</span>
</div>
<div style="display: flex; align-items: center;gap: 4px;height: 32px;">
<input class="search-container" type="text" placeholder="搜索船舶" v-model="searchKeyword"
@input="handleSearch" />
<el-button class="close-btn" size="small" type="text" @click="handleCloseShipList">×</el-button>
</div>
</div>
<div class=" card-content">
<div class="ship-table">
<div class="ship-table-header">
<div class="ship-table-column ship-name-header">轮船名称</div>
<div class="ship-table-column ship-status-header">状态</div>
<div class="ship-table-column ship-status-header">历史</div>
</div>
<div class="ship-table-body">
<div v-for="ship in filteredShipStatusData" :key="ship.id" class="ship-table-row"
@click="handleSelectItem(ship)">
<div class="ship-table-column ship-name">
<div class="ship-icon">🚢</div>
<span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span>
</div>
<div class="ship-table-column ship-status">
<div class="status-tag" :class="getStatusClass(ship.shipStatus)">
{{ ship.shipStatus }}
</div>
<!-- <div class="status-tag" :class="getStatusClass('空闲')">
空闲
</div>
<div class="status-tag" :class="getStatusClass('故障')">
故障
</div> -->
</div>
<div class="ship-table-column ship-status-header">
<el-button type="primary" link @click.stop="showShipHistory(ship)">查看</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
2 weeks ago
<div v-if="shipSelectedItem && showShipList && !shorePowerCardShow" class="card digital-twin-card--deep-blue"
2 weeks ago
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
2 weeks ago
}}</span>
2 weeks ago
</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)
2 weeks ago
}}</span>
2 weeks ago
</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
2 weeks ago
}})</span></span>
2 weeks ago
</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
2 weeks ago
}}</span>
2 weeks ago
</div>
<div class="detail-item">
<span class="label">联系方式:</span>
<span class="value">{{ shipSelectedItem?.shipBasicInfo?.shorePowerContactPhone
2 weeks ago
}}</span>
2 weeks ago
</div>
</div>
</div>
</div>
2 weeks ago
<div v-if="shorePowerCardShow && !showShipList" style="width: 840px">
2 weeks ago
<ShorePowerUsageRate :realtimeDeviceDataTime="realtimeDeviceDataTime" :handleClose="handleCloseRate" />
</div>
<!-- <div v-if="shorePowerCardShow" class="card digital-twin-card--deep-blue"
style=" display: inline-block; width: 840px;">
<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">
12
</div>
</div> -->
2 weeks ago
<!-- </div> -->
3 weeks ago
</div>
<div class="right" style="width: 900px">
3 weeks ago
<div class="card digital-twin-card--deep-blue">
<div class="card-title">
3 weeks ago
<!-- <div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" /> -->
3 weeks ago
<!-- <span class="title-text">岸电管理大屏系统</span> -->
3 weeks ago
<!-- <span class="title-date">{{ currentDate }}</span> -->
3 weeks ago
</div>
<div class="card-content">
<!-- 港口岸电使用情况 -->
<div class="module-section">
3 weeks ago
3 weeks ago
<div class="module-header">
3 weeks ago
<div style="display: flex; align-items: center; gap: 20px;">
2 weeks ago
<h3 class="module-title" @click="handleGoToModule(1, portActivePeriod)">港区岸电用电量统计</h3>
3 weeks ago
<div class="module-time">
<div>{{ portStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
</div>
3 weeks ago
<div class="period-filter">
3 weeks ago
<div v-for="period in periodOptions" :key="period.value"
:class="['period-option', { active: portActivePeriod === period.value }]"
@click="switchPortPeriod(period.value)">
{{ period.label }}
3 weeks ago
</div>
</div>
</div>
<div class="port-overview">
<div class="first-row">
2 weeks ago
<div class="benefit-card" @click="handleCardClick('totalPower')">
3 weeks ago
<div class="benefit-value">{{ chartData[portActivePeriod].totalPower }}</div>
3 weeks ago
<div class="benefit-unit">累计用电 (千瓦时)</div>
</div>
3 weeks ago
<div class="benefit-card" @click="handleCardClick('co2')">
<div class="benefit-value">{{ chartData[portActivePeriod].co2 }}</div>
3 weeks ago
<div class="benefit-unit">减少二氧化碳排放 (千克)</div>
</div>
</div>
<div class="second-row">
3 weeks ago
<div class="benefit-card" @click="handleCardClick('fuel')">
<div class="benefit-value">{{ chartData[portActivePeriod].fuel }}</div>
<div class="benefit-unit">减少燃油<br /> ()</div>
3 weeks ago
</div>
3 weeks ago
<div class="benefit-card" @click="handleCardClick('pm25')">
<div class="benefit-value">{{ chartData[portActivePeriod].pm25 }}</div>
<div class="benefit-unit">减少PM2.5排放<br /> (千克)</div>
3 weeks ago
</div>
3 weeks ago
<div class="benefit-card" @click="handleCardClick('nox')">
<div class="benefit-value">{{ chartData[portActivePeriod].nox }}</div>
<div class="benefit-unit">减少氮氧化物<br /> (千克)</div>
3 weeks ago
</div>
3 weeks ago
<div class="benefit-card" @click="handleCardClick('so2')">
<div class="benefit-value">{{ chartData[portActivePeriod].so2 }}</div>
<div class="benefit-unit">减少二氧化硫<br /> (千克)</div>
3 weeks ago
</div>
</div>
</div>
</div>
<!-- 港口企业岸电使用 -->
<div class="module-section">
<div class="module-header">
3 weeks ago
<div style="display: flex; align-items: center; gap: 20px;">
2 weeks ago
<h3 class="module-title" @click="handleGoToModule(2)">港区企业岸电用电量统计</h3>
3 weeks ago
<div class="module-time">
<div>{{ enterpriseStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
</div>
3 weeks ago
<div class="period-filter">
3 weeks ago
<div v-for="period in periodOptions" :key="period.value"
:class="['period-option', { active: enterpriseActivePeriod === period.value }]"
@click="switchEnterprisePeriod(period.value)">
{{ period.label }}
3 weeks ago
</div>
</div>
</div>
<div class="enterprise-usage">
<div class="usage-bars">
2 weeks ago
<!-- <div class="bar-item" v-for="item in companyShorePowerBuildData[enterpriseActivePeriod]"
3 weeks ago
:key="item.harborDistrictId">
3 weeks ago
<div class="bar-header">
3 weeks ago
<span class="bar-name">{{ item.name }}</span>
<span class="bar-value">{{ item.totalPower.toFixed(2) }} kWH</span>
3 weeks ago
</div>
3 weeks ago
<el-progress
2 weeks ago
:percentage="calculatePercentageForCompany(item, companyShorePowerData)"
:show-text="false" :stroke-width="10" />
</div> -->
<div class="bar-item" v-for="item in companyShorePowerData" :key="item.id">
<div class="bar-header">
<span class="bar-name">{{ item.name }}</span>
<span class="bar-value">{{ handGetDeviceData(item.shorePowerIds,
getCompanyTimeRange(item.id)) }} kWH</span>
</div>
<el-progress :percentage="calculatePercentageForCompany(item, companyShorePowerData)"
3 weeks ago
:show-text="false" :stroke-width="10" />
3 weeks ago
</div>
3 weeks ago
<!-- <div class="bar-item">
3 weeks ago
<div class="bar-header">
<span class="bar-name">国投码头</span>
<span class="bar-value">413513.53 kWH</span>
</div>
<el-progress :percentage="95" :show-text="false" />
</div>
<div class="bar-item">
<div class="bar-header">
<span class="bar-name">华电码头 (储运)</span>
<span class="bar-value">189631.46 kWH</span>
</div>
<el-progress :percentage="55" :show-text="false" />
3 weeks ago
</div> -->
3 weeks ago
</div>
<!-- <div class="current-usage">
<div class="usage-detail">
<span>华能码头</span>
<span>3泊位</span>
<span>华元503</span>
<span>正在用电</span>
<span>2192kWH</span>
</div>
</div> -->
</div>
</div>
<!-- 船舶岸电使用情况 -->
2 weeks ago
<div class="module-section" style="flex: 1;">
3 weeks ago
<div class="module-header">
3 weeks ago
<div style="display: flex; align-items: center; gap: 20px;">
2 weeks ago
<h3 class="module-title" @click="handleGoToModule(3)">港区船舶岸电用电量统计</h3>
3 weeks ago
<div class="module-time">
<div>{{ shipStartTime }} {{ realtimeDeviceDataTime }}</div>
</div>
</div>
3 weeks ago
<!-- <span>实时</span> -->
3 weeks ago
<div class="period-filter">
3 weeks ago
<div v-for="period in periodOptions" :key="period.value"
:class="['period-option', { active: shipActivePeriod === period.value }]"
@click="switchShipPeriod(period.value)">
{{ period.label }}
3 weeks ago
</div>
</div>
</div>
<div class="ship-status">
2 weeks ago
<div class="usage-rate shadow" @click="handleOpenRate">
3 weeks ago
<div class="rate-value">{{ calculateShorePowerUsageRate() }}%</div>
3 weeks ago
<div class="rate-label">岸电使用率</div>
</div>
<div class="status-grid">
2 weeks ago
<div class="status-card">
<div class="status-value">{{ shipTotalData.allPortAreaCount }}</div>
3 weeks ago
<div class="status-label">在港船舶</div>
</div>
<div class="status-card" @click="handleShowShipList('all')">
<div class="status-value">{{ shipTotalData.berthingShips }}</div>
<div class="status-label">在泊船舶</div>
3 weeks ago
</div>
3 weeks ago
<div class="status-card" @click="handleShowShipList('using')">
<div class="status-value">{{ shipTotalData.shorePowerShips }}</div>
3 weeks ago
<div class="status-label">使用岸电船舶</div>
3 weeks ago
</div>
3 weeks ago
<div class="status-card" @click="handleShowShipList('notUsing')">
<div class="status-value">{{ shipTotalData.noShorePowerShips }}</div>
3 weeks ago
<div class="status-label">未使用岸电船舶</div>
3 weeks ago
</div>
</div>
<!-- <div class="ship-list">
<div class="ship-item">
<span class="ship-name">华元503</span>
<span class="ship-dock">华能码头</span>
<span class="ship-berth">3泊位</span>
<span class="ship-status-text">使用岸电21小时45分钟, 2479kWH</span>
</div>
<div class="ship-item">
<span class="ship-name">信洋新征程</span>
<span class="ship-dock">国投码头</span>
<span class="ship-berth">202#泊位</span>
<span class="ship-status-text">无受电设备</span>
</div>
<div class="ship-item">
<span class="ship-name">华海航2</span>
<span class="ship-dock">华电码头 (储运)</span>
<span class="ship-berth">806泊位</span>
<span class="ship-status-text">船电设施损坏, 预计2025-11-30恢复</span>
</div>
<div class="ship-item">
<span class="ship-name">东亿603</span>
<span class="ship-dock">国投码头</span>
<span class="ship-berth">205#泊位</span>
<span class="ship-status-text">电缆长度不匹配</span>
</div>
<div class="ship-item">
<span class="ship-name">东成山</span>
<span class="ship-dock">华能码头</span>
<span class="ship-berth">2泊位</span>
<span class="ship-status-text">岸电用电接口不匹配</span>
</div>
<div class="ship-item">
<span class="ship-name">中茂98</span>
<span class="ship-dock">华电码头 (储运)</span>
<span class="ship-berth">807泊位</span>
<span class="ship-status-text">岸电设施维护中</span>
</div>
</div> -->
</div>
</div>
</div>
</div>
</div>
3 weeks ago
<ShipHistoryDialog v-model="shipHistoryVisible.visible" :ship-param="shipHistoryVisible.searchParams"
:realtime-device-data="realtimeDeviceData" />
3 weeks ago
</div>
</template>
<script setup lang="ts">
2 weeks ago
import { CompanyShorePowerBuildDataItem, CompanyShorePowerData, RealtimeDeviceData, ShipRespVo, TimeRange } from '@/types/shorepower';
3 weeks ago
import { ref, computed } from 'vue'
3 weeks ago
import { MapApi } from "@/api/shorepower/map";
import ShipHistoryDialog from './ShipHistoryDialog.vue'
2 weeks ago
import ShorePowerUsageRate from './ShorePowerUsageRate.vue'
2 weeks ago
import { formatTimestamp, getValueById, showStatus } from './utils';
import { BERTH_TYPE, getOperationTypeLabel, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './dictionaryTable';
3 weeks ago
interface Props {
2 weeks ago
handleGoToModule: (moduleType: number, timeRange?: string) => void
3 weeks ago
realtimeDeviceDataTime: string
3 weeks ago
realtimeDeviceData: RealtimeDeviceData[]
2 weeks ago
companyShorePowerBuildData: CompanyShorePowerBuildDataItem[]
allPortAreaCount: number
3 weeks ago
chartData: {
[key in 'day' | 'month' | 'year' | 'realtime']: {
'totalPower': number,
'fuel': number,
'co2': number,
'pm25': number,
'nox': number,
'so2': number,
}
};
shipTotalData: {
2 weeks ago
'allPortAreaCount': number,
3 weeks ago
'berthingShips': number,
'shorePowerShips': number,
'noShorePowerShips': number,
}
shipDataList: ShipRespVo[]
2 weeks ago
companyShorePowerData: CompanyShorePowerData[]
yearData: any[];
monthData: any[];
dayData: any[];
3 weeks ago
}
const props = defineProps<Props>()
const currentShip = ref<ShipRespVo | null>(null)
const showShipList = ref(false)
const searchKeyword = ref('')
3 weeks ago
// 获取当前日期
const currentDate = computed(() => {
const now = new Date()
const year = now.getFullYear()
const month = String(now.getMonth() + 1).padStart(2, '0')
const day = String(now.getDate()).padStart(2, '0')
return `${year}-${month}-${day}`
})
// 获取更新时间(模拟数据更新时间)
const updateTime = computed(() => {
const now = new Date()
const hours = String(now.getHours()).padStart(2, '0')
const minutes = String(now.getMinutes()).padStart(2, '0')
const seconds = String(now.getSeconds()).padStart(2, '0')
return `${hours}:${minutes}:${seconds}`
})
3 weeks ago
// 时间段筛选相关 - 为每个模块创建独立的状态
2 weeks ago
const portActivePeriod = ref<'realtime' | 'day' | 'month' | 'year'>('day') // 港口岸电使用情况模块的时间粒度
const enterpriseActivePeriod = ref<'realtime' | 'day' | 'month' | 'year'>('day') // 港口企业岸电使用模块的时间粒度
const shipActivePeriod = ref<'realtime' | 'day' | 'month' | 'year'>('day') // 船舶岸电使用情况模块的时间粒度
3 weeks ago
const filterType = ref('all') // 船舶岸电使用情况模块的筛选类型
const shipHistoryVisible = ref({
visible: false,
searchParams: {
shipId: 0 as number | null,
ids: [] as number[] | null,
type: 1 as number
}
})
const shipSelectedItem = ref<ShipRespVo | null>(null)
2 weeks ago
const shorePowerCardShow = ref(false)
3 weeks ago
const periodOptions = [
3 weeks ago
{ label: '当日', value: 'day' },
{ label: '当月', value: 'month' },
{ label: '当年', value: 'year' },
{ label: '汇总', value: 'realtime' },
3 weeks ago
]
3 weeks ago
// 模拟不同时间段的数据
const rankingData = {
今日: [
{ rank: 1, name: '华能曹妃甸港口有限公司', value: '350,000', change: '+15%', isUp: true },
{ rank: 2, name: '河北华电曹妃甸储运有限公司', value: '280,000', change: '+8%', isUp: true },
{ rank: 3, name: '国投曹妃甸港口有限公司', value: '220,000', change: '-3%', isUp: false }
],
本月: [
{ rank: 1, name: '华能曹妃甸港口有限公司', value: '8,350,000', change: '+12%', isUp: true },
{ rank: 2, name: '国投曹妃甸港口有限公司', value: '7,280,000', change: '+5%', isUp: true },
{ rank: 3, name: '河北华电曹妃甸储运有限公司', value: '6,220,000', change: '-2%', isUp: false }
],
本年: [
{ rank: 1, name: '华能曹妃甸港口有限公司', value: '120,350,000', change: '+18%', isUp: true },
{ rank: 2, name: '国投曹妃甸港口有限公司', value: '95,280,000', change: '+9%', isUp: true },
{ rank: 3, name: '河北华电曹妃甸储运有限公司', value: '82,220,000', change: '+3%', isUp: true }
],
全部: [
{ rank: 1, name: '华能曹妃甸港口有限公司', value: '2,120,350,000', change: '+15%', isUp: true },
{ rank: 2, name: '国投曹妃甸港口有限公司', value: '1,095,280,000', change: '+11%', isUp: true },
{ rank: 3, name: '河北华电曹妃甸储运有限公司', value: '882,220,000', change: '+7%', isUp: true }
]
}
2 weeks ago
// 关闭岸电使用情况模块
const handleCloseRate = () => {
shorePowerCardShow.value = false
}
const handleOpenRate = () => {
handleCloseShipList()
shorePowerCardShow.value = true
2 weeks ago
2 weeks ago
}
3 weeks ago
// 根据选择的时间段动态生成起始时间 - 为每个模块创建独立的计算属性
const portStartTime = computed(() => {
const now = new Date()
let startDate: Date
switch (portActivePeriod.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':
startDate = new Date(2020, 0, 1) // 2020年1月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')
return `${year}-${month}-${day} ${hours}:${minutes}`
})
const enterpriseStartTime = computed(() => {
const now = new Date()
let startDate: Date
switch (enterpriseActivePeriod.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':
startDate = new Date(2020, 0, 1) // 2020年1月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')
return `${year}-${month}-${day} ${hours}:${minutes}`
})
const shipStartTime = computed(() => {
const now = new Date()
let startDate: Date
switch (shipActivePeriod.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':
startDate = new Date(2020, 0, 1) // 2020年1月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')
return `${year}-${month}-${day} ${hours}:${minutes}`
})
3 weeks ago
3 weeks ago
// 计算岸电使用率
const calculateShorePowerUsageRate = (): number => {
if (!props.shipTotalData || props.shipTotalData.berthingShips === 0) return 0
// 计算使用岸电船舶数量占总在港船舶数量的比例
2 weeks ago
const usageRate = (props.shipTotalData.shorePowerShips / props.shipTotalData.allPortAreaCount) * 100
3 weeks ago
return Number(usageRate.toFixed(2))
}
2 weeks ago
// 计算公司用电量的进度条百分比
const calculatePercentageForCompany = (company: CompanyShorePowerData, companyData: CompanyShorePowerData[]): number => {
if (!companyData || companyData.length === 0) return 0
3 weeks ago
2 weeks ago
// 计算当前公司的用电量
const companyPower = handGetDeviceData(company.shorePowerIds, getCompanyTimeRange(company.id))
3 weeks ago
2 weeks ago
// 计算所有公司的最大用电量
const maxPower = Math.max(...companyData.map(item => {
return parseFloat(handGetDeviceData(item.shorePowerIds, getCompanyTimeRange(item.id)))
}))
3 weeks ago
// 如果最大值为0,返回0以避免除以0
2 weeks ago
if (maxPower === 0) return 0
3 weeks ago
// 计算百分比,最小值设为5%以确保即使是最小的值也能在视觉上显示
2 weeks ago
const percentage = (parseFloat(companyPower) / maxPower) * 100
3 weeks ago
return Math.max(percentage, 5)
}
// 切换时间段 - 为每个模块创建独立的切换方法
2 weeks ago
const switchPortPeriod = (period: 'realtime' | 'day' | 'month' | 'year') => {
3 weeks ago
portActivePeriod.value = period
}
2 weeks ago
const switchEnterprisePeriod = (period: 'realtime' | 'day' | 'month' | 'year') => {
3 weeks ago
enterpriseActivePeriod.value = period
}
2 weeks ago
const switchShipPeriod = (period: 'realtime' | 'day' | 'month' | 'year') => {
3 weeks ago
shipActivePeriod.value = period
}
// 处理卡片点击事件,跳转到详细页面并设置对应的卡片
const handleCardClick = (cardType: string) => {
// 先跳转到港口岸电使用情况模块(activeHeadGroup = 1)
2 weeks ago
props.handleGoToModule(5, portActivePeriod.value)
3 weeks ago
// 使用nextTick确保组件已经渲染后再设置selectedCard
setTimeout(() => {
// 发送消息到父组件或通过事件总线通知ShorePowerUsage组件切换卡片
window.dispatchEvent(new CustomEvent('cardSelected', { detail: cardType }))
}, 100)
}
// 处理搜索输入
const handleSearch = () => {
// 输入处理逻辑(如果需要额外处理可以在这里添加)
}
// 定义事件
const emit = defineEmits<{
(e: 'switch-ship', ship: ShipRespVo): void;
(e: 'item-click', item: any): void;
// handleSelectItem(ship)
}>()
const handleSelectItem = async (item: ShipRespVo) => {
const deviceId = item.shorePower?.totalPowerDeviceId;
const res = await MapApi.getRealtimeDataByIdList({ ids: deviceId })
console.log(res)
shipSelectedItem.value = {
...item,
shorePowerEquipment: {
...item.shorePowerEquipment,
'measureValue': res[0].measureValue || 'N/A',
}
}
emit('item-click', {
type: 'ship',
item: item
})
}
const getStatusClass = (status: string | undefined) => {
switch (status) {
case '正常':
return 'status-normal'
3 weeks ago
case '在用':
return 'status-normal'
3 weeks ago
case '在线':
return 'status-normal'
case '空闲':
return 'status-idle'
case '故障':
return 'status-fault'
case '超容':
return 'status-maintenance'
case '异常':
return 'status-abnormal'
case '维修中':
return 'status-maintenance'
case '岸电使用中':
return 'status-shorepower'
// case '岸电故障':
// return 'status-fault'
default:
return 'status-default'
}
}
// 过滤后的船舶数据
const filteredShipStatusData = computed(() => {
let filterData: ShipRespVo[] = []
if (filterType.value === 'using') {
3 weeks ago
filterData = props.shipDataList.filter(ship => ['在用'].includes(ship.shipStatus || ''))
3 weeks ago
} else if (filterType.value === 'notUsing') {
3 weeks ago
filterData = props.shipDataList.filter(ship => !['在用'].includes(ship.shipStatus || ''))
3 weeks ago
} else {
filterData = props.shipDataList
}
if (!searchKeyword.value) {
return filterData
}
return filterData.filter(ship =>
ship.shipBasicInfo.name.toLowerCase().includes(searchKeyword.value.toLowerCase())
)
})
// 显示船舶历史记录
const showShipHistory = (ship: ShipRespVo) => {
console.log('ship', ship)
currentShip.value = ship
shipHistoryVisible.value = {
visible: true,
searchParams: {
shipId: ship.shipBasicInfo.id,
ids: null,
type: 1,
}
}
}
const handleShowShipList = (type: string) => {
2 weeks ago
handleCloseRate()
3 weeks ago
filterType.value = type
showShipList.value = true
}
// 关闭船舶列表面板
const handleCloseShipList = () => {
2 weeks ago
handleCloseShipDetail()
3 weeks ago
showShipList.value = false
3 weeks ago
}
2 weeks ago
// 关闭船舶详情面板
const handleCloseShipDetail = () => {
shipSelectedItem.value = null
}
2 weeks ago
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 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)
if (deviceData?.measureValue) {
totalValue = deviceData.measureValue;
}
if (range !== 'realtime' && startData?.measureValue) {
totalValue -= startData.measureValue
}
}
return totalValue.toFixed(2);
}
const getCompanyTimeRange = (companyId: number): 'realtime' | 'day' | 'month' | 'year' => {
return enterpriseActivePeriod.value
}
const deviceDataMap = computed(() => {
const map = new Map<number, RealtimeDeviceData>();
props.realtimeDeviceData.forEach(item => {
map.set(item.deviceId, item);
});
return map;
});
3 weeks ago
</script>
3 weeks ago
<style scoped lang="scss">
3 weeks ago
.show-data {
display: flex;
gap: 10px;
height: 100%;
}
3 weeks ago
.left {
// height: 100%;
2 weeks ago
// flex: 1;
display: flex;
flex-direction: row;
width: auto;
// flex-direction: column;
gap: 10px;
3 weeks ago
.card {
// width: 200px;
padding: 12px;
padding-top: 24px;
2 weeks ago
padding-bottom: 24px;
3 weeks ago
}
}
.right {
top: 0;
height: 100vh;
}
3 weeks ago
.card {
3 weeks ago
padding-top: 0px;
3 weeks ago
padding-right: 0px;
padding-left: 12px;
}
.card-content {
display: flex;
flex-direction: column;
3 weeks ago
gap: 8px;
padding: 8px;
3 weeks ago
}
.card-title {
display: flex;
align-items: center;
}
.title-text {
font-size: 28px;
font-weight: bold;
}
.title-date {
font-size: 16px;
color: #a0cfff;
margin-left: auto;
padding-right: 20px;
}
.update-time {
font-size: 14px;
color: #67c23a;
margin-left: 10px;
padding-right: 20px;
3 weeks ago
display: flex;
flex-direction: column;
gap: 8px;
3 weeks ago
}
.section-title {
font-size: 24px;
font-weight: bold;
color: #409eff;
margin: 0;
padding-bottom: 6px;
border-bottom: 1px solid rgba(64, 158, 255, 0.3);
}
.module-section {
2 weeks ago
margin-bottom: 8px;
3 weeks ago
}
.module-header {
display: flex;
justify-content: space-between;
align-items: center;
3 weeks ago
padding-bottom: 12px;
3 weeks ago
/* border-bottom: 2px solid rgba(64, 158, 255, 0.3); */
}
.module-title {
2 weeks ago
font-size: 22px;
3 weeks ago
font-weight: bold;
color: #409eff;
text-decoration: underline;
margin: 0;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
}
.module-title:hover {
transform: translateY(-2px);
color: #66b1ff;
}
.module-title:active {
transform: translateY(-4px);
color: #337ecc;
text-decoration: none;
}
3 weeks ago
.module-time {
2 weeks ago
font-size: 18px;
3 weeks ago
color: #67c23a;
}
.module-time div {
line-height: 1.5;
}
3 weeks ago
/* 港口岸电使用情况 */
.port-overview {
2 weeks ago
margin-bottom: 6px 12px;
3 weeks ago
}
.first-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
2 weeks ago
gap: 12px;
margin-bottom: 12px;
3 weeks ago
}
.second-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
2 weeks ago
gap: 12px;
3 weeks ago
}
.total-electricity {
text-align: center;
margin-bottom: 20px;
padding: 20px;
background: rgba(30, 120, 255, 0.1);
border-radius: 10px;
}
.total-value {
3 weeks ago
font-size: 36px;
3 weeks ago
font-weight: bold;
color: #FFF;
margin-bottom: 5px;
}
.total-unit {
font-size: 16px;
color: #a0cfff;
}
.benefits-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
3 weeks ago
gap: 12px;
3 weeks ago
}
.benefit-card {
3 weeks ago
background: rgba(30, 120, 255, 0.15);
border: 1px solid rgba(64, 158, 255, 0.4);
3 weeks ago
border-radius: 8px;
3 weeks ago
padding: 16px 8px;
3 weeks ago
text-align: center;
3 weeks ago
cursor: pointer;
transition: all 0.3s ease;
}
.benefit-card:hover {
background: rgba(30, 120, 255, 0.25);
border: 1px solid rgba(64, 158, 255, 0.6);
transform: translateY(-2px);
3 weeks ago
}
.benefit-value {
3 weeks ago
font-size: 32px;
3 weeks ago
font-weight: bold;
color: #FFF;
margin-bottom: 5px;
}
.benefit-unit {
3 weeks ago
font-size: 18px;
3 weeks ago
color: #a0cfff;
}
/* 港口企业岸电使用 */
.enterprise-usage {
margin-bottom: 20px;
}
.usage-bars {
3 weeks ago
margin-bottom: 20px;
3 weeks ago
}
.bar-item {
margin-bottom: 15px;
}
.bar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.bar-name {
font-size: 16px;
3 weeks ago
color: #cce6ff;
3 weeks ago
}
.bar-value {
font-size: 14px;
color: #FFF;
font-weight: bold;
}
:deep(.el-progress-bar__outer) {
3 weeks ago
background: rgba(30, 120, 255, 0.3);
3 weeks ago
border-radius: 10px;
overflow: hidden;
}
:deep(.el-progress-bar__inner) {
background: linear-gradient(90deg, #67c23a, #409eff);
border-radius: 10px;
}
.current-usage {
background: rgba(30, 120, 255, 0.1);
border: 1px solid rgba(64, 158, 255, 0.3);
border-radius: 8px;
padding: 15px;
}
.usage-detail {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 16px;
color: #a0cfff;
}
/* 船舶岸电使用情况 */
.ship-status {
margin-bottom: 8px;
2 weeks ago
height: 100%;
3 weeks ago
}
.usage-rate {
text-align: center;
2 weeks ago
height: 50%;
3 weeks ago
padding: 16px 8px;
3 weeks ago
background: rgba(30, 120, 255, 0.15);
3 weeks ago
border-radius: 10px;
3 weeks ago
margin-bottom: 6px;
2 weeks ago
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
color: #66b1ff;
}
3 weeks ago
}
3 weeks ago
.usage-rate.shadow {
box-shadow: 0 0px 10px rgb(255, 255, 255);
border: 2px solid rgba(255, 255, 255, 0.2);
}
3 weeks ago
.rate-value {
3 weeks ago
font-size: 36px;
3 weeks ago
font-weight: bold;
color: #FFF;
margin-bottom: 5px;
}
.rate-label {
3 weeks ago
font-size: 24px;
3 weeks ago
color: #a0cfff;
}
.status-grid {
2 weeks ago
height: 38%;
3 weeks ago
display: grid;
3 weeks ago
grid-template-columns: repeat(4, 1fr);
2 weeks ago
gap: 12px;
margin-bottom: 12px;
3 weeks ago
}
.status-card {
3 weeks ago
background: rgba(30, 120, 255, 0.15);
border: 1px solid rgba(64, 158, 255, 0.4);
3 weeks ago
border-radius: 8px;
3 weeks ago
padding: 16px;
3 weeks ago
text-align: center;
3 weeks ago
cursor: pointer;
transition: all 0.3s ease;
2 weeks ago
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
3 weeks ago
}
.status-card:hover {
background: rgba(30, 120, 255, 0.25);
border: 1px solid rgba(64, 158, 255, 0.6);
transform: translateY(-2px);
3 weeks ago
}
.status-value {
3 weeks ago
font-size: 42px;
3 weeks ago
font-weight: bold;
color: #FFF;
margin-bottom: 5px;
}
.status-label {
3 weeks ago
font-size: 24px;
font-weight: 500;
3 weeks ago
color: #a0cfff;
}
.ship-list {
background: rgba(30, 120, 255, 0.1);
border: 1px solid rgba(64, 158, 255, 0.3);
border-radius: 8px;
padding: 15px;
}
.ship-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid rgba(64, 158, 255, 0.2);
}
.ship-item:last-child {
border-bottom: none;
}
.ship-name {
font-size: 16px;
color: #67c23a;
font-weight: bold;
3 weeks ago
width: 180px;
3 weeks ago
}
.ship-dock {
font-size: 16px;
color: #a0cfff;
width: 120px;
}
.ship-berth {
font-size: 16px;
color: #a0cfff;
width: 100px;
}
.ship-status-text {
font-size: 14px;
color: #a0cfff;
text-align: right;
flex: 1;
}
/* 分公司排名样式 */
.period-filter {
display: flex;
gap: 10px;
margin-bottom: 0;
}
.period-option {
padding: 4px 8px;
background: rgba(30, 120, 255, 0.1);
border: 1px solid rgba(64, 158, 255, 0.3);
border-radius: 8px;
color: #a0cfff;
cursor: pointer;
transition: all 0.3s ease;
}
.period-option:hover {
background: rgba(30, 120, 255, 0.2);
}
.period-option.active {
background: #409eff;
color: white;
border-color: #409eff;
}
.ranking-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.ranking-item {
display: flex;
align-items: center;
padding: 10px 12px;
background: rgba(30, 120, 255, 0.05);
border-radius: 8px;
border: 1px solid rgba(30, 120, 255, 0.1);
}
.change {
font-size: 16px;
font-weight: bold;
}
.up {
color: #67c23a;
}
.down {
color: #f56c6c;
}
.rank-number {
width: 24px;
height: 24px;
border-radius: 50%;
background: #409eff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
margin-right: 12px;
}
.first .rank-number {
background: #ffd700;
color: #333;
}
.second .rank-number {
background: #c0c0c0;
color: #333;
}
.third .rank-number {
background: #cd7f32;
color: #333;
}
.branch-name {
flex: 1;
font-size: 20px;
3 weeks ago
color: #ffffff;
3 weeks ago
}
.branch-value {
font-size: 26px;
font-weight: bold;
color: #409eff;
min-width: 140px;
text-align: right;
margin-right: 12px;
}
3 weeks ago
.search-container {
height: 100%;
width: 200px;
border-radius: 4px;
border: 1px solid rgb(10, 130, 170);
color: #FFF;
padding: 0 10px;
margin-bottom: 8px;
background-color: rgba(255, 255, 255, 0.1);
}
.close-btn {
width: 24px;
height: 24px;
// border-radius: 50%;
// background-color: #ff4d4f;
color: white;
border: none;
font-weight: bold;
font-size: 16px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
margin-left: 10px;
margin-bottom: 8px;
}
.close-btn:hover {
color: #f5222d;
}
3 weeks ago
</style>