|
|
|
@ -1,53 +1,107 @@ |
|
|
|
<template> |
|
|
|
<div class="show-data"> |
|
|
|
<div class="right" style="width: 800px"> |
|
|
|
<div class="left" v-if="showShipList" style="width:500px"> |
|
|
|
<div class="card digital-twin-card--deep-blue "> |
|
|
|
<div style="display: flex; align-items: center; justify-content: space-between;"> |
|
|
|
<div class="card-title"> |
|
|
|
<div class="vertical-line"></div> |
|
|
|
<img src="@/assets/svgs/data.svg" class="title-icon" /> |
|
|
|
<span class="title-text">船舶状态</span> |
|
|
|
</div> |
|
|
|
<div 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> |
|
|
|
</div> |
|
|
|
<div class="right" style="width: 900px"> |
|
|
|
<div class="card digital-twin-card--deep-blue"> |
|
|
|
<div class="card-title"> |
|
|
|
<div class="vertical-line"></div> |
|
|
|
<img src="@/assets/svgs/data.svg" class="title-icon" /> |
|
|
|
<!-- <div class="vertical-line"></div> |
|
|
|
<img src="@/assets/svgs/data.svg" class="title-icon" /> --> |
|
|
|
<!-- <span class="title-text">岸电管理大屏系统</span> --> |
|
|
|
<span class="title-date">{{ currentDate }}</span> |
|
|
|
<span class="update-time">更新时间: {{ realtimeDeviceDataTime }}</span> |
|
|
|
<!-- <span class="title-date">{{ currentDate }}</span> --> |
|
|
|
</div> |
|
|
|
<div class="card-content"> |
|
|
|
<!-- 港口岸电使用情况 --> |
|
|
|
<div class="module-section"> |
|
|
|
<div class="module-time"> |
|
|
|
<div>起始时间:{{ portStartTime }}</div> |
|
|
|
<div>更新时间:{{ realtimeDeviceDataTime }}</div> |
|
|
|
</div> |
|
|
|
<div class="module-header"> |
|
|
|
<h3 class="module-title" @click="handleGoToModule(1)">港口岸电使用情况</h3> |
|
|
|
<div class="period-filter"> |
|
|
|
<div v-for="period in periodOptions" :key="period" |
|
|
|
:class="['period-option', { active: activePeriod === period }]" @click="switchPeriod(period)"> |
|
|
|
{{ period }} |
|
|
|
<div v-for="period in periodOptions" :key="period.value" |
|
|
|
:class="['period-option', { active: portActivePeriod === period.value }]" |
|
|
|
@click="switchPortPeriod(period.value)"> |
|
|
|
{{ period.label }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="port-overview"> |
|
|
|
<div class="first-row"> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">835380.87</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('overview')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].totalPower }}</div> |
|
|
|
<div class="benefit-unit">累计用电 (千瓦时)</div> |
|
|
|
</div> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">559705.19</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('co2')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].co2 }}</div> |
|
|
|
<div class="benefit-unit">减少二氧化碳排放 (千克)</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="second-row"> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">105.25</div> |
|
|
|
<div class="benefit-unit">减少燃油 (吨)</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('fuel')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].fuel }}</div> |
|
|
|
<div class="benefit-unit">减少燃油<br /> (吨)</div> |
|
|
|
</div> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">1219.66</div> |
|
|
|
<div class="benefit-unit">减少PM2.5排放 (千克)</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('pm25')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].pm25 }}</div> |
|
|
|
<div class="benefit-unit">减少PM2.5排放<br /> (千克)</div> |
|
|
|
</div> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">15120.39</div> |
|
|
|
<div class="benefit-unit">减少氮氧化物 (千克)</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('nox')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].nox }}</div> |
|
|
|
<div class="benefit-unit">减少氮氧化物<br /> (千克)</div> |
|
|
|
</div> |
|
|
|
<div class="benefit-card"> |
|
|
|
<div class="benefit-value">8771.51</div> |
|
|
|
<div class="benefit-unit">减少二氧化硫 (千克)</div> |
|
|
|
<div class="benefit-card" @click="handleCardClick('so2')"> |
|
|
|
<div class="benefit-value">{{ chartData[portActivePeriod].so2 }}</div> |
|
|
|
<div class="benefit-unit">减少二氧化硫<br /> (千克)</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -55,25 +109,33 @@ |
|
|
|
|
|
|
|
<!-- 港口企业岸电使用 --> |
|
|
|
<div class="module-section"> |
|
|
|
<div class="module-time"> |
|
|
|
<div>起始时间:{{ enterpriseStartTime }}</div> |
|
|
|
<div>更新时间:{{ realtimeDeviceDataTime }}</div> |
|
|
|
</div> |
|
|
|
<div class="module-header"> |
|
|
|
<h3 class="module-title" @click="handleGoToModule(2)">港口企业岸电使用</h3> |
|
|
|
<div class="period-filter"> |
|
|
|
<div v-for="period in periodOptions" :key="period" |
|
|
|
:class="['period-option', { active: activePeriod === period }]" @click="switchPeriod(period)"> |
|
|
|
{{ period }} |
|
|
|
<div v-for="period in periodOptions" :key="period.value" |
|
|
|
:class="['period-option', { active: enterpriseActivePeriod === period.value }]" |
|
|
|
@click="switchEnterprisePeriod(period.value)"> |
|
|
|
{{ period.label }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="enterprise-usage"> |
|
|
|
<div class="usage-bars"> |
|
|
|
<div class="bar-item"> |
|
|
|
<div class="bar-item" v-for="item in companyShorePowerBuildData[enterpriseActivePeriod]" |
|
|
|
:key="item.harborDistrictId"> |
|
|
|
<div class="bar-header"> |
|
|
|
<span class="bar-name">华能码头</span> |
|
|
|
<span class="bar-value">232235.88 kWH</span> |
|
|
|
<span class="bar-name">{{ item.name }}</span> |
|
|
|
<span class="bar-value">{{ item.totalPower.toFixed(2) }} kWH</span> |
|
|
|
</div> |
|
|
|
<el-progress :percentage="65" :show-text="false" /> |
|
|
|
<el-progress |
|
|
|
:percentage="calculatePercentage(item.totalPower, companyShorePowerBuildData[enterpriseActivePeriod])" |
|
|
|
:show-text="false" :stroke-width="10" /> |
|
|
|
</div> |
|
|
|
<div class="bar-item"> |
|
|
|
<!-- <div class="bar-item"> |
|
|
|
<div class="bar-header"> |
|
|
|
<span class="bar-name">国投码头</span> |
|
|
|
<span class="bar-value">413513.53 kWH</span> |
|
|
|
@ -86,7 +148,7 @@ |
|
|
|
<span class="bar-value">189631.46 kWH</span> |
|
|
|
</div> |
|
|
|
<el-progress :percentage="55" :show-text="false" /> |
|
|
|
</div> |
|
|
|
</div> --> |
|
|
|
</div> |
|
|
|
<!-- <div class="current-usage"> |
|
|
|
<div class="usage-detail"> |
|
|
|
@ -102,31 +164,37 @@ |
|
|
|
|
|
|
|
<!-- 船舶岸电使用情况 --> |
|
|
|
<div class="module-section"> |
|
|
|
<div class="module-time"> |
|
|
|
<div>起始时间:{{ shipStartTime }}</div> |
|
|
|
<div>更新时间:{{ realtimeDeviceDataTime }}</div> |
|
|
|
</div> |
|
|
|
<div class="module-header"> |
|
|
|
<h3 class="module-title" @click="handleGoToModule(3)">船舶岸电使用情况</h3> |
|
|
|
<!-- <span>(实时)</span> --> |
|
|
|
<div class="period-filter"> |
|
|
|
<div v-for="period in periodOptions" :key="period" |
|
|
|
:class="['period-option', { active: activePeriod === period }]" @click="switchPeriod(period)"> |
|
|
|
{{ period }} |
|
|
|
<div v-for="period in periodOptions" :key="period.value" |
|
|
|
:class="['period-option', { active: shipActivePeriod === period.value }]" |
|
|
|
@click="switchShipPeriod(period.value)"> |
|
|
|
{{ period.label }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="ship-status"> |
|
|
|
<div class="usage-rate"> |
|
|
|
<div class="rate-value">23.08%</div> |
|
|
|
<div class="rate-value">{{ calculateShorePowerUsageRate() }}%</div> |
|
|
|
<div class="rate-label">岸电使用率</div> |
|
|
|
</div> |
|
|
|
<div class="status-grid"> |
|
|
|
<div class="status-card"> |
|
|
|
<div class="status-value">13</div> |
|
|
|
<div class="status-card" @click="handleShowShipList('all')"> |
|
|
|
<div class="status-value">{{ shipTotalData.berthingShips }}</div> |
|
|
|
<div class="status-label">在港船舶数量</div> |
|
|
|
</div> |
|
|
|
<div class="status-card"> |
|
|
|
<div class="status-value">3</div> |
|
|
|
<div class="status-card" @click="handleShowShipList('using')"> |
|
|
|
<div class="status-value">{{ shipTotalData.shorePowerShips }}</div> |
|
|
|
<div class="status-label">使用岸电船舶数量</div> |
|
|
|
</div> |
|
|
|
<div class="status-card"> |
|
|
|
<div class="status-value">10</div> |
|
|
|
<div class="status-card" @click="handleShowShipList('notUsing')"> |
|
|
|
<div class="status-value">{{ shipTotalData.noShorePowerShips }}</div> |
|
|
|
<div class="status-label">未使用岸电船舶数量</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
@ -173,18 +241,43 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<ShipHistoryDialog v-model="shipHistoryVisible.visible" :ship-param="shipHistoryVisible.searchParams" |
|
|
|
:realtime-device-data="realtimeDeviceData" /> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script setup lang="ts"> |
|
|
|
import { CompanyShorePowerBuildDataItem, RealtimeDeviceData, ShipRespVo, TimeRange } from '@/types/shorepower'; |
|
|
|
import { ref, computed } from 'vue' |
|
|
|
|
|
|
|
import { MapApi } from "@/api/shorepower/map"; |
|
|
|
import ShipHistoryDialog from './ShipHistoryDialog.vue' |
|
|
|
interface Props { |
|
|
|
handleGoToModule: (moduleType: number) => void |
|
|
|
realtimeDeviceDataTime: string |
|
|
|
} |
|
|
|
|
|
|
|
defineProps<Props>() |
|
|
|
realtimeDeviceData: RealtimeDeviceData[] |
|
|
|
companyShorePowerBuildData: CompanyShorePowerBuildDataItem |
|
|
|
chartData: { |
|
|
|
[key in 'day' | 'month' | 'year' | 'realtime']: { |
|
|
|
'totalPower': number, |
|
|
|
'fuel': number, |
|
|
|
'co2': number, |
|
|
|
'pm25': number, |
|
|
|
'nox': number, |
|
|
|
'so2': number, |
|
|
|
} |
|
|
|
}; |
|
|
|
shipTotalData: { |
|
|
|
'berthingShips': number, |
|
|
|
'shorePowerShips': number, |
|
|
|
'noShorePowerShips': number, |
|
|
|
} |
|
|
|
shipDataList: ShipRespVo[] |
|
|
|
} |
|
|
|
|
|
|
|
const props = defineProps<Props>() |
|
|
|
const currentShip = ref<ShipRespVo | null>(null) |
|
|
|
const showShipList = ref(false) |
|
|
|
const searchKeyword = ref('') |
|
|
|
// 获取当前日期 |
|
|
|
const currentDate = computed(() => { |
|
|
|
const now = new Date() |
|
|
|
@ -203,9 +296,29 @@ const updateTime = computed(() => { |
|
|
|
return `${hours}:${minutes}:${seconds}` |
|
|
|
}) |
|
|
|
|
|
|
|
// 时间段筛选相关 |
|
|
|
const activePeriod = ref('今日') |
|
|
|
const periodOptions = ['今日', '本月', '本年', '全部'] |
|
|
|
// 时间段筛选相关 - 为每个模块创建独立的状态 |
|
|
|
const portActivePeriod = ref('day') // 港口岸电使用情况模块的时间粒度 |
|
|
|
const enterpriseActivePeriod = ref('day') // 港口企业岸电使用模块的时间粒度 |
|
|
|
const shipActivePeriod = ref('day') // 船舶岸电使用情况模块的时间粒度 |
|
|
|
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) |
|
|
|
|
|
|
|
const periodOptions = [ |
|
|
|
{ label: '今日', value: 'day' }, |
|
|
|
{ label: '本月', value: 'month' }, |
|
|
|
{ label: '本年', value: 'year' }, |
|
|
|
{ label: '全部', value: 'realtime' }, |
|
|
|
] |
|
|
|
|
|
|
|
// 模拟不同时间段的数据 |
|
|
|
const rankingData = { |
|
|
|
@ -231,25 +344,289 @@ const rankingData = { |
|
|
|
] |
|
|
|
} |
|
|
|
|
|
|
|
// 根据选择的时间段动态生成起始时间 - 为每个模块创建独立的计算属性 |
|
|
|
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}` |
|
|
|
}) |
|
|
|
|
|
|
|
// 获取当前选中时间段的数据 |
|
|
|
const currentRankingData = computed(() => { |
|
|
|
return rankingData[activePeriod.value as keyof typeof rankingData] || [] |
|
|
|
}) |
|
|
|
|
|
|
|
// 切换时间段 |
|
|
|
const switchPeriod = (period: string) => { |
|
|
|
activePeriod.value = period |
|
|
|
// 计算岸电使用率 |
|
|
|
const calculateShorePowerUsageRate = (): number => { |
|
|
|
if (!props.shipTotalData || props.shipTotalData.berthingShips === 0) return 0 |
|
|
|
|
|
|
|
// 计算使用岸电船舶数量占总在港船舶数量的比例 |
|
|
|
const usageRate = (props.shipTotalData.shorePowerShips / props.shipTotalData.berthingShips) * 100 |
|
|
|
return Number(usageRate.toFixed(2)) |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 计算进度条百分比 |
|
|
|
const calculatePercentage = (value: number, dataArray: typeof props.companyShorePowerBuildData[TimeRange]): number => { |
|
|
|
if (!dataArray || dataArray.length === 0) return 0 |
|
|
|
|
|
|
|
// 找到数组中的最大值 |
|
|
|
const maxValue = Math.max(...dataArray.map(item => item.totalPower)) |
|
|
|
|
|
|
|
// 如果最大值为0,返回0以避免除以0 |
|
|
|
if (maxValue === 0) return 0 |
|
|
|
|
|
|
|
// 计算百分比,最小值设为5%以确保即使是最小的值也能在视觉上显示 |
|
|
|
const percentage = (value / maxValue) * 100 |
|
|
|
return Math.max(percentage, 5) |
|
|
|
} |
|
|
|
|
|
|
|
// 切换时间段 - 为每个模块创建独立的切换方法 |
|
|
|
const switchPortPeriod = (period: string) => { |
|
|
|
portActivePeriod.value = period |
|
|
|
} |
|
|
|
|
|
|
|
const switchEnterprisePeriod = (period: string) => { |
|
|
|
enterpriseActivePeriod.value = period |
|
|
|
} |
|
|
|
|
|
|
|
const switchShipPeriod = (period: string) => { |
|
|
|
shipActivePeriod.value = period |
|
|
|
} |
|
|
|
|
|
|
|
// 处理卡片点击事件,跳转到详细页面并设置对应的卡片 |
|
|
|
const handleCardClick = (cardType: string) => { |
|
|
|
// 先跳转到港口岸电使用情况模块(activeHeadGroup = 1) |
|
|
|
props.handleGoToModule(1) |
|
|
|
// 使用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' |
|
|
|
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') { |
|
|
|
filterData = props.shipDataList.filter(ship => ['正常', '超容'].includes(ship.shipStatus || '')) |
|
|
|
} else if (filterType.value === 'notUsing') { |
|
|
|
filterData = props.shipDataList.filter(ship => !['正常', '超容'].includes(ship.shipStatus || '')) |
|
|
|
} 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) => { |
|
|
|
filterType.value = type |
|
|
|
showShipList.value = true |
|
|
|
} |
|
|
|
|
|
|
|
// 关闭船舶列表面板 |
|
|
|
const handleCloseShipList = () => { |
|
|
|
showShipList.value = false |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
<style scoped lang="scss"> |
|
|
|
.show-data { |
|
|
|
display: flex; |
|
|
|
gap: 10px; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.left { |
|
|
|
// height: 100%; |
|
|
|
// fle width: 300px; |
|
|
|
|
|
|
|
.card { |
|
|
|
// width: 200px; |
|
|
|
|
|
|
|
padding: 12px; |
|
|
|
padding-top: 24px; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.right { |
|
|
|
top: 0; |
|
|
|
height: 100vh; |
|
|
|
} |
|
|
|
|
|
|
|
.card { |
|
|
|
padding-top: 0px; |
|
|
|
padding-right: 0px; |
|
|
|
padding-left: 12px; |
|
|
|
} |
|
|
|
@ -257,8 +634,8 @@ const switchPeriod = (period: string) => { |
|
|
|
.card-content { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 12px; |
|
|
|
padding: 12px; |
|
|
|
gap: 16px; |
|
|
|
padding: 16px; |
|
|
|
} |
|
|
|
|
|
|
|
.card-title { |
|
|
|
@ -283,6 +660,9 @@ const switchPeriod = (period: string) => { |
|
|
|
color: #67c23a; |
|
|
|
margin-left: 10px; |
|
|
|
padding-right: 20px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
gap: 8px; |
|
|
|
} |
|
|
|
|
|
|
|
.section-title { |
|
|
|
@ -295,14 +675,14 @@ const switchPeriod = (period: string) => { |
|
|
|
} |
|
|
|
|
|
|
|
.module-section { |
|
|
|
margin-bottom: 0px; |
|
|
|
margin-bottom: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.module-header { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding-bottom: 8px; |
|
|
|
padding-bottom: 12px; |
|
|
|
/* border-bottom: 2px solid rgba(64, 158, 255, 0.3); */ |
|
|
|
} |
|
|
|
|
|
|
|
@ -328,6 +708,17 @@ const switchPeriod = (period: string) => { |
|
|
|
text-decoration: none; |
|
|
|
} |
|
|
|
|
|
|
|
.module-time { |
|
|
|
font-size: 14px; |
|
|
|
color: #67c23a; |
|
|
|
margin-bottom: 12px; |
|
|
|
padding-left: 4px; |
|
|
|
} |
|
|
|
|
|
|
|
.module-time div { |
|
|
|
line-height: 1.5; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 港口岸电使用情况 */ |
|
|
|
@ -338,14 +729,14 @@ const switchPeriod = (period: string) => { |
|
|
|
.first-row { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(2, 1fr); |
|
|
|
gap: 8px; |
|
|
|
margin-bottom: 8px; |
|
|
|
gap: 12px; |
|
|
|
margin-bottom: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.second-row { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(4, 1fr); |
|
|
|
gap: 8px; |
|
|
|
gap: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.total-electricity { |
|
|
|
@ -357,7 +748,7 @@ const switchPeriod = (period: string) => { |
|
|
|
} |
|
|
|
|
|
|
|
.total-value { |
|
|
|
font-size: 48px; |
|
|
|
font-size: 36px; |
|
|
|
font-weight: bold; |
|
|
|
color: #FFF; |
|
|
|
margin-bottom: 5px; |
|
|
|
@ -371,26 +762,34 @@ const switchPeriod = (period: string) => { |
|
|
|
.benefits-grid { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(2, 1fr); |
|
|
|
gap: 15px; |
|
|
|
gap: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.benefit-card { |
|
|
|
background: rgba(30, 120, 255, 0.1); |
|
|
|
border: 1px solid rgba(64, 158, 255, 0.3); |
|
|
|
background: rgba(30, 120, 255, 0.15); |
|
|
|
border: 1px solid rgba(64, 158, 255, 0.4); |
|
|
|
border-radius: 8px; |
|
|
|
padding: 15px; |
|
|
|
padding: 20px 8px; |
|
|
|
text-align: center; |
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
.benefit-value { |
|
|
|
font-size: 28px; |
|
|
|
font-size: 32px; |
|
|
|
font-weight: bold; |
|
|
|
color: #FFF; |
|
|
|
margin-bottom: 5px; |
|
|
|
} |
|
|
|
|
|
|
|
.benefit-unit { |
|
|
|
font-size: 14px; |
|
|
|
font-size: 18px; |
|
|
|
color: #a0cfff; |
|
|
|
} |
|
|
|
|
|
|
|
@ -400,7 +799,7 @@ const switchPeriod = (period: string) => { |
|
|
|
} |
|
|
|
|
|
|
|
.usage-bars { |
|
|
|
margin-bottom: 15px; |
|
|
|
margin-bottom: 20px; |
|
|
|
} |
|
|
|
|
|
|
|
.bar-item { |
|
|
|
@ -416,7 +815,7 @@ const switchPeriod = (period: string) => { |
|
|
|
|
|
|
|
.bar-name { |
|
|
|
font-size: 16px; |
|
|
|
color: #a0cfff; |
|
|
|
color: #cce6ff; |
|
|
|
} |
|
|
|
|
|
|
|
.bar-value { |
|
|
|
@ -426,7 +825,7 @@ const switchPeriod = (period: string) => { |
|
|
|
} |
|
|
|
|
|
|
|
:deep(.el-progress-bar__outer) { |
|
|
|
background: rgba(30, 120, 255, 0.2); |
|
|
|
background: rgba(30, 120, 255, 0.3); |
|
|
|
border-radius: 10px; |
|
|
|
overflow: hidden; |
|
|
|
} |
|
|
|
@ -459,47 +858,56 @@ const switchPeriod = (period: string) => { |
|
|
|
.usage-rate { |
|
|
|
text-align: center; |
|
|
|
padding: 20px; |
|
|
|
background: rgba(30, 120, 255, 0.1); |
|
|
|
background: rgba(30, 120, 255, 0.15); |
|
|
|
border-radius: 10px; |
|
|
|
margin-bottom: 8px; |
|
|
|
margin-bottom: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.rate-value { |
|
|
|
font-size: 48px; |
|
|
|
font-size: 36px; |
|
|
|
font-weight: bold; |
|
|
|
color: #FFF; |
|
|
|
margin-bottom: 5px; |
|
|
|
} |
|
|
|
|
|
|
|
.rate-label { |
|
|
|
font-size: 16px; |
|
|
|
font-size: 24px; |
|
|
|
color: #a0cfff; |
|
|
|
} |
|
|
|
|
|
|
|
.status-grid { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: repeat(3, 1fr); |
|
|
|
gap: 8px; |
|
|
|
margin-bottom: 8px; |
|
|
|
gap: 12px; |
|
|
|
margin-bottom: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.status-card { |
|
|
|
background: rgba(30, 120, 255, 0.1); |
|
|
|
border: 1px solid rgba(64, 158, 255, 0.3); |
|
|
|
background: rgba(30, 120, 255, 0.15); |
|
|
|
border: 1px solid rgba(64, 158, 255, 0.4); |
|
|
|
border-radius: 8px; |
|
|
|
padding: 20px; |
|
|
|
padding: 16px; |
|
|
|
text-align: center; |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
.status-card:hover { |
|
|
|
background: rgba(30, 120, 255, 0.25); |
|
|
|
border: 1px solid rgba(64, 158, 255, 0.6); |
|
|
|
transform: translateY(-2px); |
|
|
|
} |
|
|
|
|
|
|
|
.status-value { |
|
|
|
font-size: 36px; |
|
|
|
font-size: 42px; |
|
|
|
font-weight: bold; |
|
|
|
color: #FFF; |
|
|
|
margin-bottom: 5px; |
|
|
|
} |
|
|
|
|
|
|
|
.status-label { |
|
|
|
font-size: 16px; |
|
|
|
font-size: 24px; |
|
|
|
font-weight: 500; |
|
|
|
color: #a0cfff; |
|
|
|
} |
|
|
|
|
|
|
|
@ -526,7 +934,7 @@ const switchPeriod = (period: string) => { |
|
|
|
font-size: 16px; |
|
|
|
color: #67c23a; |
|
|
|
font-weight: bold; |
|
|
|
width: 120px; |
|
|
|
width: 180px; |
|
|
|
} |
|
|
|
|
|
|
|
.ship-dock { |
|
|
|
@ -634,7 +1042,7 @@ const switchPeriod = (period: string) => { |
|
|
|
.branch-name { |
|
|
|
flex: 1; |
|
|
|
font-size: 20px; |
|
|
|
color: #e6f2ff; |
|
|
|
color: #ffffff; |
|
|
|
} |
|
|
|
|
|
|
|
.branch-value { |
|
|
|
@ -645,4 +1053,36 @@ const switchPeriod = (period: string) => { |
|
|
|
text-align: right; |
|
|
|
margin-right: 12px; |
|
|
|
} |
|
|
|
|
|
|
|
.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; |
|
|
|
} |
|
|
|
</style> |