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.
1380 lines
42 KiB
1380 lines
42 KiB
|
1 month ago
|
<template>
|
||
|
|
<div>
|
||
|
|
<PublicMapComponents ref="mapComponentRef" class="map-base" :defCenter="[38.8417433306111, 118.43845367431642]"
|
||
|
|
:defZoom="12" />
|
||
|
|
<!-- <div class="show-data"> -->
|
||
|
|
<div class="head">
|
||
|
|
<div class="head-title">
|
||
|
|
<span>曹妃甸港区船舶岸电监管平台</span>
|
||
|
|
</div>
|
||
|
|
<el-button class="view-btn" size="small" type="success" @click="mapComponentRef.switchView('overview')"
|
||
|
|
style="margin-right: 10px;">概览视角</el-button>
|
||
|
|
|
||
|
|
<div class="head-group">
|
||
|
|
<div v-for="item in headGroupList" :key="item.value" class="head-group-item"
|
||
|
|
:class="{ 'active': activeHeadGroup === item.value }" @click="selectHeadGroup(item.value)">
|
||
|
|
{{ item.label }}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="head-time">{{ currentTime }}</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- 港区概览 -->
|
||
|
|
<template v-if="activeHeadGroup === 0">
|
||
|
|
<div class="left">
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">船舶状态</span>
|
||
|
|
</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>
|
||
|
|
<div class="ship-table-body">
|
||
|
|
<div v-for="ship in shipStatusData" :key="ship.id" class="ship-table-row" @click="handleSwitch(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('正常')">
|
||
|
|
正常
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="right">
|
||
|
|
<div v-if="selectedShip" class="card digital-twin-card--deep-blue" style="flex: 2;">
|
||
|
|
<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 class="card-content">
|
||
|
|
<div class="ship-detail">
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">船名:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.name }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">英文船名:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.nameEn }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">船舶呼号:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.callSign }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">船长:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.length }} 米</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">船宽:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.width }} 米</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">满载吃水深度:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.fullLoadDraft }} 米</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">吨位:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.tonnage }} 吨</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">航运单位:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.shippingCompany }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">岸电联系人:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.shorePowerContact }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">联系方式:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.shorePowerContactPhone }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">船检登记号:</span>
|
||
|
|
<span class="value">{{ selectedShip.shipBasicInfo.inspectionNo }}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">创建时间:</span>
|
||
|
|
<span class="value">{{ new Date(selectedShip.shipBasicInfo.createTime).toLocaleString() }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-if="selectedShip && selectedShip.shorePowerAndShip" class="card digital-twin-card--deep-blue"
|
||
|
|
style="flex: 1;">
|
||
|
|
<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 class="card-content">
|
||
|
|
<div class="ship-detail">
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">停泊状态:</span>
|
||
|
|
<span class="value">{{ selectedShip.shorePowerAndShip.type === 'left' ? '左舷停泊' :
|
||
|
|
selectedShip.shorePowerAndShip.type === 'right' ? '右舷停泊' : selectedShip.shorePowerAndShip.type
|
||
|
|
}}</span>
|
||
|
|
</div>
|
||
|
|
<div class="detail-item">
|
||
|
|
<span class="label">靠泊状态:</span>
|
||
|
|
<span class="value">{{ getShorePowerStatusText(selectedShip.shorePowerAndShip.status) }}</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-if="selectedShip && !selectedShip.shorePowerAndShip" 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" />
|
||
|
|
<span class="title-text">岸电信息</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<div class="no-selection">暂无岸电信息</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div v-if="!selectedShip" 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" />
|
||
|
|
<span class="title-text">船舶详情</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<div class="no-selection">请选择一艘船舶以查看详细信息</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<!-- 港口岸电使用情况 -->
|
||
|
|
<template v-if="activeHeadGroup === 1">
|
||
|
|
<div class="left">
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">总览</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<div class="overview-grid">
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ totalPower }}</div>
|
||
|
|
<div class="overview-label">累计用电(千瓦时)</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ fuelReduction }}</div>
|
||
|
|
<div class="overview-label">减少燃油(吨)</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ co2Reduction }}</div>
|
||
|
|
<div class="overview-label">减少二氧化碳排放(千克)</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ pm25Reduction }}</div>
|
||
|
|
<div class="overview-label">减少PM2.5排放(千克)</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ noxReduction }}</div>
|
||
|
|
<div class="overview-label">减少氮氧化物(千克)</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ so2Reduction }}</div>
|
||
|
|
<div class="overview-label">减少二氧化硫(千克)</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">减少燃油(吨)</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<LineChart :chart-data="fuelReductionData" title="减少燃油趋势" color="#4CAF50" style="height: 200px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">减少二氧化碳排放(千克)</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<LineChart :chart-data="co2ReductionData" title="减少CO₂排放趋势" color="#F44336" style="height: 200px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="right">
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">减少PM2.5排放(千克)</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<LineChart :chart-data="pm25ReductionData" title="减少PM2.5排放趋势" color="#FF9800" style="height: 200px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">减少氮氧化物(千克)</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<LineChart :chart-data="noxReductionData" title="减少NOₓ排放趋势" color="#9C27B0" style="height: 200px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">减少二氧化硫(千克)</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<LineChart :chart-data="so2ReductionData" title="减少SO₂排放趋势" color="#00BCD4" style="height: 200px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<!-- 港口企业岸电使用 -->
|
||
|
|
<template v-if="activeHeadGroup === 2">
|
||
|
|
<div class="left">
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">港口企业岸电使用</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<BarChart :chart-data="companyComparisonData" title="企业岸电使用对比" color="#4CAF50" style="height: 300px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<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" />
|
||
|
|
<span class="title-text">码头详情</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-content">
|
||
|
|
<!-- 下拉框和饼图组件 -->
|
||
|
|
<div class="company-selector">
|
||
|
|
<el-select v-model="selectedCompany" placeholder="请选择公司" @change="handleCompanyChange"
|
||
|
|
style="width: 100%; margin-bottom: 20px;">
|
||
|
|
<el-option v-for="item in companyComparisonData" :key="item.name" :label="item.name"
|
||
|
|
:value="item.name" />
|
||
|
|
</el-select>
|
||
|
|
|
||
|
|
<PieChart :chart-data="pieChartData" :title="`${selectedCompany}子项占比`" style="height: 300px;" />
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<!-- 船舶岸电使用情况 -->
|
||
|
|
<template v-if="activeHeadGroup === 3">
|
||
|
|
<div class="left" style="width: 800px;">
|
||
|
|
<div class="card digital-twin-card--deep-blue " style="flex: 1;">
|
||
|
|
<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 class="card-content">
|
||
|
|
<div class="overview-grid">
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ berthingShips }}</div>
|
||
|
|
<div class="overview-label">在泊船舶数量</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ shorePowerShips }}</div>
|
||
|
|
<div class="overview-label">使用岸电船舶数量</div>
|
||
|
|
</div>
|
||
|
|
<div class="overview-item">
|
||
|
|
<div class="overview-value">{{ noShorePowerShips }}</div>
|
||
|
|
<div class="overview-label">未使用岸电船舶数量</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<div class="card digital-twin-card--deep-blue " style="flex: 3;">
|
||
|
|
<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 class="card-content">
|
||
|
|
<div class="ship-data-table-container" ref="scrollContainerRef">
|
||
|
|
<div class="ship-data-table">
|
||
|
|
<div v-for="(ship, index) in shipData" :key="index" class="ship-data-row">
|
||
|
|
<div class="ship-data-cell ship-name">{{ ship.name }}</div>
|
||
|
|
<div class="ship-data-cell wharf-name">{{ ship.wharf }}</div>
|
||
|
|
<div class="ship-data-cell berth-number">{{ ship.berth }}</div>
|
||
|
|
<div class="ship-data-cell shore-power-status" :class="ship.statusClass">{{ ship.status }}</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
<!-- <div class="right" style="width: 600px;">
|
||
|
|
</div> -->
|
||
|
|
</template>
|
||
|
|
|
||
|
|
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<script setup lang="ts">
|
||
|
|
|
||
|
|
import PublicMapComponents from './components/index.vue'
|
||
|
|
import LineChart from './components/LineChart.vue'
|
||
|
|
import BarChart from './components/BarChart.vue'
|
||
|
|
import PieChart from './components/PieChart.vue'
|
||
|
|
import dayjs from 'dayjs'
|
||
|
|
import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
|
||
|
|
import { MapApi } from "@/api/shorepower/map";
|
||
|
|
defineOptions({ name: 'PublicMap' })
|
||
|
|
|
||
|
|
const headGroupList = ref<{ value: number, label: string }[]>([
|
||
|
|
{ value: 0, label: '港区概览' },
|
||
|
|
{ value: 1, label: '港口岸电使用情况' },
|
||
|
|
{ value: 2, label: '港口企业岸电使用' },
|
||
|
|
{ value: 3, label: '船舶岸电使用情况' },
|
||
|
|
])
|
||
|
|
|
||
|
|
const activeHeadGroup = ref<number>(-1)
|
||
|
|
|
||
|
|
// 定义子组件类型
|
||
|
|
interface PublicMapComponentsType {
|
||
|
|
switchView: (viewName: string) => void
|
||
|
|
dataWithModels: any[]
|
||
|
|
switchModelView: () => void
|
||
|
|
}
|
||
|
|
|
||
|
|
const mapComponentRef = ref<PublicMapComponentsType | null>(null)
|
||
|
|
|
||
|
|
const selectHeadGroup = async (value: number) => {
|
||
|
|
if (value === activeHeadGroup.value) {
|
||
|
|
activeHeadGroup.value = -1;
|
||
|
|
return
|
||
|
|
}
|
||
|
|
activeHeadGroup.value = value
|
||
|
|
if (value === 1) {
|
||
|
|
const deviceStatus = await MapApi.getDeviceStatusByIds(totalPowerDeviceId.value)
|
||
|
|
console.log('deviceStatus', deviceStatus)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 实时时间显示
|
||
|
|
const currentTime = ref<string>(dayjs().format('YYYY-MM-DD HH:mm:ss'))
|
||
|
|
|
||
|
|
// 总览数据
|
||
|
|
const totalPower = ref<string>('857525.45')
|
||
|
|
const fuelReduction = ref<string>('108.04')
|
||
|
|
const co2Reduction = ref<string>('574542.06')
|
||
|
|
const pm25Reduction = ref<string>('1251.99')
|
||
|
|
const noxReduction = ref<string>('15521.21')
|
||
|
|
const so2Reduction = ref<string>('9004.03')
|
||
|
|
|
||
|
|
// 船舶岸电使用情况数据
|
||
|
|
const berthingShips = ref<string>('13')
|
||
|
|
const shorePowerShips = ref<string>('3')
|
||
|
|
const noShorePowerShips = ref<string>('10')
|
||
|
|
|
||
|
|
// 船舶数据列表
|
||
|
|
const shipData = ref([
|
||
|
|
{ name: '华元503', wharf: '华能码头', berth: '3泊位', status: '使用岸电21小时45分钟,2479kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '信洋新征程', wharf: '国投码头', berth: '202#泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '华海航2', wharf: '华电码头(储运)', berth: '806泊位', status: '船电设施损坏,预计2025-11-30恢复', statusClass: 'status-damaged' },
|
||
|
|
{ name: '东亿603', wharf: '国投码头', berth: '205#泊位', status: '电缆长度不匹配', statusClass: 'status-cable' },
|
||
|
|
{ name: '恒远88', wharf: '华能码头', berth: '5泊位', status: '使用岸电12小时30分钟,1850kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '海昌10', wharf: '国投码头', berth: '103#泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '长航99', wharf: '华电码头(储运)', berth: '702泊位', status: '使用岸电8小时15分钟,1200kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '中远之星', wharf: '华能码头', berth: '1泊位', status: '电缆接口不匹配', statusClass: 'status-cable' },
|
||
|
|
{ name: '渤海1号', wharf: '国投码头', berth: '301#泊位', status: '使用岸电15小时20分钟,2100kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '津港101', wharf: '华电码头(储运)', berth: '608泊位', status: '船电设施维护中', statusClass: 'status-damaged' },
|
||
|
|
{ name: '胜利888', wharf: '华能码头', berth: '4泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '东方明珠', wharf: '国投码头', berth: '208#泊位', status: '使用岸电20小时10分钟,2750kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '海洋之星', wharf: '华电码头(储运)', berth: '901泊位', status: '电缆长度不足', statusClass: 'status-cable' },
|
||
|
|
{ name: '中远海1', wharf: '华能码头', berth: '2泊位', status: '使用岸电18小时40分钟,2500kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '大连先锋', wharf: '国投码头', berth: '105#泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '青岛港9', wharf: '华电码头(储运)', berth: '505泊位', status: '船电设施故障', statusClass: 'status-damaged' },
|
||
|
|
{ name: '上海号', wharf: '华能码头', berth: '6泊位', status: '使用岸电14小时30分钟,1950kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '广州先锋', wharf: '国投码头', berth: '305#泊位', status: '电缆接口问题', statusClass: 'status-cable' },
|
||
|
|
{ name: '深圳港7', wharf: '华电码头(储运)', berth: '801泊位', status: '使用岸电9小时25分钟,1350kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '宁波号', wharf: '华能码头', berth: '7泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '厦门先锋', wharf: '国投码头', berth: '210#泊位', status: '使用岸电16小时15分钟,2200kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '福州港8', wharf: '华电码头(储运)', berth: '705泊位', status: '船电设施维护', statusClass: 'status-damaged' },
|
||
|
|
{ name: '天津号', wharf: '华能码头', berth: '8泊位', status: '使用岸电11小时50分钟,1650kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '秦皇岛先锋', wharf: '国投码头', berth: '109#泊位', status: '电缆长度不匹配', statusClass: 'status-cable' },
|
||
|
|
{ name: '唐山港6', wharf: '华电码头(储运)', berth: '603泊位', status: '使用岸电13小时25分钟,1850kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '黄骅港5', wharf: '华能码头', berth: '9泊位', status: '无受电设备', statusClass: 'status-no-equipment' },
|
||
|
|
{ name: '曹妃甸1', wharf: '国投码头', berth: '312#泊位', status: '使用岸电19小时30分钟,2600kWH', statusClass: 'status-using' },
|
||
|
|
{ name: '京唐港4', wharf: '华电码头(储运)', berth: '508泊位', status: '船电设施故障', statusClass: 'status-damaged' },
|
||
|
|
{ name: '营口港3', wharf: '华能码头', berth: '10泊位', status: '使用岸电10小时45分钟,1500kWH', statusClass: 'status-using' }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 自动滚动相关变量
|
||
|
|
const scrollContainerRef = ref<HTMLElement | null>(null)
|
||
|
|
let scrollTimer: NodeJS.Timeout | null = null
|
||
|
|
let scrollTimeout: NodeJS.Timeout | null = null
|
||
|
|
let userScrollHandler: ((event: Event) => void) | null = null
|
||
|
|
const scrollSpeed = ref<number>(20) // 滚动速度,越小越快
|
||
|
|
|
||
|
|
// 更新时间的函数
|
||
|
|
const updateTime = () => {
|
||
|
|
currentTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||
|
|
}
|
||
|
|
|
||
|
|
const selectedShip = ref(null)
|
||
|
|
|
||
|
|
const handleSwitch = (item) => {
|
||
|
|
console.log(item)
|
||
|
|
selectedShip.value = item
|
||
|
|
mapComponentRef.value?.switchModelView(item.modelInstance)
|
||
|
|
}
|
||
|
|
|
||
|
|
const shorePowerStatusMap = {
|
||
|
|
1: '待靠泊',
|
||
|
|
2: '靠泊中',
|
||
|
|
3: '岸电接入中',
|
||
|
|
4: '用电中',
|
||
|
|
5: '岸电卸载中',
|
||
|
|
6: '岸电卸载完成',
|
||
|
|
7: '离泊',
|
||
|
|
9: '未使用岸电'
|
||
|
|
}
|
||
|
|
|
||
|
|
// 状态转换函数
|
||
|
|
const getShorePowerStatusText = (status: number | string) => {
|
||
|
|
const statusNum = Number(status);
|
||
|
|
console.log(status)
|
||
|
|
return shorePowerStatusMap[statusNum] || status;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 启动定时器更新时间
|
||
|
|
let timer: NodeJS.Timeout
|
||
|
|
onMounted(() => {
|
||
|
|
timer = setInterval(updateTime, 1000)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 组件卸载时清除所有资源
|
||
|
|
onUnmounted(() => {
|
||
|
|
console.log('Unmounting component, cleaning up resources')
|
||
|
|
|
||
|
|
// 清除时间更新定时器
|
||
|
|
if (timer) {
|
||
|
|
clearInterval(timer)
|
||
|
|
timer = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 清除自动滚动定时器
|
||
|
|
if (scrollTimer) {
|
||
|
|
clearInterval(scrollTimer)
|
||
|
|
scrollTimer = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 清除用户滚动超时
|
||
|
|
if (scrollTimeout) {
|
||
|
|
clearTimeout(scrollTimeout)
|
||
|
|
scrollTimeout = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 移除用户滚动事件监听器
|
||
|
|
if (scrollContainerRef.value && userScrollHandler) {
|
||
|
|
scrollContainerRef.value.removeEventListener('scroll', userScrollHandler)
|
||
|
|
userScrollHandler = null
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('All resources cleaned up')
|
||
|
|
})
|
||
|
|
|
||
|
|
// 自动滚动功能
|
||
|
|
const startAutoScroll = () => {
|
||
|
|
if (!scrollContainerRef.value) {
|
||
|
|
console.log('scrollContainerRef is null')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const container = scrollContainerRef.value
|
||
|
|
const content = container.querySelector('.ship-data-table')
|
||
|
|
if (!content) {
|
||
|
|
console.log('ship-data-table not found')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
const contentHeight = content.offsetHeight
|
||
|
|
const containerHeight = container.offsetHeight
|
||
|
|
|
||
|
|
console.log('Container height:', containerHeight)
|
||
|
|
console.log('Content height:', contentHeight)
|
||
|
|
|
||
|
|
// 如果内容高度小于等于容器高度,不需要滚动
|
||
|
|
if (contentHeight <= containerHeight) {
|
||
|
|
console.log('Content height is less than container height, no need to scroll')
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// 清除之前的定时器
|
||
|
|
if (scrollTimer) {
|
||
|
|
clearInterval(scrollTimer)
|
||
|
|
scrollTimer = null
|
||
|
|
}
|
||
|
|
|
||
|
|
if (scrollTimeout) {
|
||
|
|
clearTimeout(scrollTimeout)
|
||
|
|
scrollTimeout = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 移除之前的事件监听器
|
||
|
|
if (userScrollHandler) {
|
||
|
|
container.removeEventListener('scroll', userScrollHandler)
|
||
|
|
userScrollHandler = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 监听滚动事件,当用户手动滚动时暂停自动滚动
|
||
|
|
let isUserScrolling = false
|
||
|
|
|
||
|
|
userScrollHandler = (event) => {
|
||
|
|
console.log('User scrolling detected')
|
||
|
|
isUserScrolling = true
|
||
|
|
|
||
|
|
// 清除自动滚动定时器
|
||
|
|
if (scrollTimer) {
|
||
|
|
clearInterval(scrollTimer)
|
||
|
|
scrollTimer = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 清除之前的超时
|
||
|
|
if (scrollTimeout) {
|
||
|
|
clearTimeout(scrollTimeout)
|
||
|
|
scrollTimeout = null
|
||
|
|
}
|
||
|
|
|
||
|
|
// 3秒后恢复自动滚动
|
||
|
|
scrollTimeout = setTimeout(() => {
|
||
|
|
console.log('Resuming auto scroll after user interaction')
|
||
|
|
isUserScrolling = false
|
||
|
|
startAutoScroll()
|
||
|
|
}, 3000)
|
||
|
|
}
|
||
|
|
|
||
|
|
// 添加事件监听器
|
||
|
|
container.addEventListener('scroll', userScrollHandler)
|
||
|
|
|
||
|
|
// 启动自动滚动
|
||
|
|
scrollTimer = setInterval(() => {
|
||
|
|
if (isUserScrolling) return
|
||
|
|
|
||
|
|
// 计算当前滚动位置
|
||
|
|
const currentScrollTop = container.scrollTop
|
||
|
|
const maxScrollTop = contentHeight - containerHeight
|
||
|
|
|
||
|
|
// 检查内容是否有变化
|
||
|
|
if (content.offsetHeight !== contentHeight) {
|
||
|
|
console.log('Content height changed, restarting scroll')
|
||
|
|
startAutoScroll()
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('Auto scrolling - Current scroll:', currentScrollTop, 'Max scroll:', maxScrollTop)
|
||
|
|
|
||
|
|
if (currentScrollTop >= maxScrollTop) {
|
||
|
|
// 滚动到顶部
|
||
|
|
console.log('Reached bottom, scrolling to top')
|
||
|
|
container.scrollTop = 0
|
||
|
|
} else {
|
||
|
|
// 平滑滚动
|
||
|
|
container.scrollTop += 1
|
||
|
|
}
|
||
|
|
}, scrollSpeed.value)
|
||
|
|
|
||
|
|
console.log('Auto scroll started')
|
||
|
|
}
|
||
|
|
|
||
|
|
// 监听组件挂载
|
||
|
|
onMounted(() => {
|
||
|
|
timer = setInterval(updateTime, 1000)
|
||
|
|
// 延迟启动滚动,确保DOM已渲染
|
||
|
|
setTimeout(() => {
|
||
|
|
startAutoScroll()
|
||
|
|
}, 500)
|
||
|
|
})
|
||
|
|
|
||
|
|
// 监听活跃头部组变化,当切换到第3组时重新启动滚动
|
||
|
|
watch(
|
||
|
|
() => activeHeadGroup,
|
||
|
|
(newValue) => {
|
||
|
|
if (newValue === 3) {
|
||
|
|
// 延迟启动滚动,确保DOM已重新渲染
|
||
|
|
setTimeout(() => {
|
||
|
|
startAutoScroll()
|
||
|
|
}, 300)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
// 监听子组件数据变化
|
||
|
|
watch(
|
||
|
|
() => mapComponentRef.value?.dataWithModels,
|
||
|
|
(newData) => {
|
||
|
|
console.log('子组件数据变化:', newData)
|
||
|
|
},
|
||
|
|
{ deep: true }
|
||
|
|
)
|
||
|
|
|
||
|
|
// 模拟折线图数据
|
||
|
|
const lineChartData = ref([
|
||
|
|
{ name: '1月', value: 120 },
|
||
|
|
{ name: '2月', value: 132 },
|
||
|
|
{ name: '3月', value: 101 },
|
||
|
|
{ name: '4月', value: 134 },
|
||
|
|
{ name: '5月', value: 90 },
|
||
|
|
{ name: '6月', value: 230 },
|
||
|
|
{ name: '7月', value: 210 },
|
||
|
|
{ name: '8月', value: 150 },
|
||
|
|
{ name: '9月', value: 180 },
|
||
|
|
{ name: '10月', value: 200 },
|
||
|
|
{ name: '11月', value: 190 },
|
||
|
|
{ name: '12月', value: 220 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 减少燃油(吨)数据
|
||
|
|
const fuelReductionData = ref([
|
||
|
|
{ name: '1月', value: 80 },
|
||
|
|
{ name: '2月', value: 85 },
|
||
|
|
{ name: '3月', value: 70 },
|
||
|
|
{ name: '4月', value: 90 },
|
||
|
|
{ name: '5月', value: 60 },
|
||
|
|
{ name: '6月', value: 150 },
|
||
|
|
{ name: '7月', value: 140 },
|
||
|
|
{ name: '8月', value: 100 },
|
||
|
|
{ name: '9月', value: 120 },
|
||
|
|
{ name: '10月', value: 130 },
|
||
|
|
{ name: '11月', value: 125 },
|
||
|
|
{ name: '12月', value: 145 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 减少二氧化碳排放(千克)数据
|
||
|
|
const co2ReductionData = ref([
|
||
|
|
{ name: '1月', value: 200 },
|
||
|
|
{ name: '2月', value: 220 },
|
||
|
|
{ name: '3月', value: 180 },
|
||
|
|
{ name: '4月', value: 240 },
|
||
|
|
{ name: '5月', value: 160 },
|
||
|
|
{ name: '6月', value: 380 },
|
||
|
|
{ name: '7月', value: 350 },
|
||
|
|
{ name: '8月', value: 260 },
|
||
|
|
{ name: '9月', value: 300 },
|
||
|
|
{ name: '10月', value: 320 },
|
||
|
|
{ name: '11月', value: 310 },
|
||
|
|
{ name: '12月', value: 360 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 减少PM2.5排放(千克)数据
|
||
|
|
const pm25ReductionData = ref([
|
||
|
|
{ name: '1月', value: 15 },
|
||
|
|
{ name: '2月', value: 18 },
|
||
|
|
{ name: '3月', value: 12 },
|
||
|
|
{ name: '4月', value: 20 },
|
||
|
|
{ name: '5月', value: 10 },
|
||
|
|
{ name: '6月', value: 35 },
|
||
|
|
{ name: '7月', value: 32 },
|
||
|
|
{ name: '8月', value: 22 },
|
||
|
|
{ name: '9月', value: 28 },
|
||
|
|
{ name: '10月', value: 30 },
|
||
|
|
{ name: '11月', value: 29 },
|
||
|
|
{ name: '12月', value: 33 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 减少氮氧化物(千克)数据
|
||
|
|
const noxReductionData = ref([
|
||
|
|
{ name: '1月', value: 25 },
|
||
|
|
{ name: '2月', value: 28 },
|
||
|
|
{ name: '3月', value: 22 },
|
||
|
|
{ name: '4月', value: 30 },
|
||
|
|
{ name: '5月', value: 18 },
|
||
|
|
{ name: '6月', value: 45 },
|
||
|
|
{ name: '7月', value: 42 },
|
||
|
|
{ name: '8月', value: 32 },
|
||
|
|
{ name: '9月', value: 38 },
|
||
|
|
{ name: '10月', value: 40 },
|
||
|
|
{ name: '11月', value: 39 },
|
||
|
|
{ name: '12月', value: 43 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 减少二氧化硫(千克)数据
|
||
|
|
const so2ReductionData = ref([
|
||
|
|
{ name: '1月', value: 20 },
|
||
|
|
{ name: '2月', value: 22 },
|
||
|
|
{ name: '3月', value: 18 },
|
||
|
|
{ name: '4月', value: 25 },
|
||
|
|
{ name: '5月', value: 15 },
|
||
|
|
{ name: '6月', value: 38 },
|
||
|
|
{ name: '7月', value: 35 },
|
||
|
|
{ name: '8月', value: 28 },
|
||
|
|
{ name: '9月', value: 32 },
|
||
|
|
{ name: '10月', value: 34 },
|
||
|
|
{ name: '11月', value: 33 },
|
||
|
|
{ name: '12月', value: 37 }
|
||
|
|
])
|
||
|
|
|
||
|
|
// 三个公司的岸电使用对比数据
|
||
|
|
const companyComparisonData = ref([
|
||
|
|
{
|
||
|
|
name: '华能码头',
|
||
|
|
value: 234277.33,
|
||
|
|
children: [
|
||
|
|
{ name: '1泊位', value: 80000 },
|
||
|
|
{ name: '2泊位', value: 60000 },
|
||
|
|
{ name: '3泊位', value: 40000 },
|
||
|
|
{ name: '4泊位', value: 30000 },
|
||
|
|
{ name: '5泊位', value: 24277.33 }
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: '国投码头',
|
||
|
|
value: 417203.39,
|
||
|
|
children: [
|
||
|
|
{ name: '101泊位', value: 150000 },
|
||
|
|
{ name: '102泊位', value: 100000 },
|
||
|
|
{ name: '103泊位', value: 80000 },
|
||
|
|
{ name: '104泊位', value: 50000 },
|
||
|
|
{ name: '105泊位', value: 37203.39 },
|
||
|
|
{ name: '106泊位', value: 5433 },
|
||
|
|
{ name: '107泊位', value: 34567 },
|
||
|
|
{ name: '108泊位', value: 50000 },
|
||
|
|
{ name: '109泊位', value: 50000 },
|
||
|
|
{ name: '110泊位', value: 50000 },
|
||
|
|
|
||
|
|
]
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: '华电码头(储运)',
|
||
|
|
value: 191340.14,
|
||
|
|
children: [
|
||
|
|
{ name: '11泊位', value: 80000 },
|
||
|
|
{ name: '12泊位', value: 60000 },
|
||
|
|
{ name: '13泊位', value: 40000 },
|
||
|
|
{ name: '14泊位', value: 30000 },
|
||
|
|
{ name: '15泊位', value: 24277.33 }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
])
|
||
|
|
|
||
|
|
// 下拉框选中的公司
|
||
|
|
const selectedCompany = ref('华能码头')
|
||
|
|
|
||
|
|
// 饼图数据
|
||
|
|
const pieChartData = ref<Array<{ name: string; value: number }>>([])
|
||
|
|
|
||
|
|
// 处理公司选择变化
|
||
|
|
const handleCompanyChange = (companyName: string) => {
|
||
|
|
const selectedCompanyData = companyComparisonData.value.find(company => company.name === companyName)
|
||
|
|
if (selectedCompanyData && selectedCompanyData.children) {
|
||
|
|
pieChartData.value = selectedCompanyData.children
|
||
|
|
} else {
|
||
|
|
pieChartData.value = []
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 初始化饼图数据
|
||
|
|
onMounted(async () => {
|
||
|
|
// 默认选中华能码头的数据
|
||
|
|
const defaultCompany = companyComparisonData.value.find(company => company.name === selectedCompany.value)
|
||
|
|
if (defaultCompany && defaultCompany.children) {
|
||
|
|
pieChartData.value = defaultCompany.children
|
||
|
|
}
|
||
|
|
/* console.log('totalPowerDeviceId', totalPowerDeviceId.value)
|
||
|
|
const deviceStatus = await MapApi.getDeviceStatusByIds(totalPowerDeviceId.value)
|
||
|
|
console.log('deviceStatus', deviceStatus) */
|
||
|
|
})
|
||
|
|
|
||
|
|
const totalPowerDeviceId = computed(() => {
|
||
|
|
if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
const ids = mapComponentRef.value.dataWithModels
|
||
|
|
.filter(item => item.modelType === 'ship')
|
||
|
|
.map(item => item.shorePower?.totalPowerDeviceId)
|
||
|
|
.filter(id => id !== undefined && id !== null)
|
||
|
|
// 去重处理
|
||
|
|
return [...new Set(ids)]
|
||
|
|
})
|
||
|
|
|
||
|
|
// 从子组件获取的船舶数据
|
||
|
|
const shipStatusData = computed(() => {
|
||
|
|
if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
// 过滤出轮船类的数据
|
||
|
|
return mapComponentRef.value.dataWithModels
|
||
|
|
.filter(item => item.modelType === 'ship')
|
||
|
|
.map((item, index) => {
|
||
|
|
// 解析数据中的状态信息
|
||
|
|
let status = ['正常'] // 默认状态
|
||
|
|
try {
|
||
|
|
if (item.data && typeof item.data === 'string') {
|
||
|
|
const dataObj = JSON.parse(item.data)
|
||
|
|
// 根据实际数据结构提取状态信息
|
||
|
|
// 这里假设dataObj中有status字段,如果没有则使用默认值
|
||
|
|
if (dataObj.status) {
|
||
|
|
status = Array.isArray(dataObj.status) ? dataObj.status : [dataObj.status]
|
||
|
|
}
|
||
|
|
} else if (item.data && item.data.status) {
|
||
|
|
status = Array.isArray(item.data.status) ? item.data.status : [item.data.status]
|
||
|
|
}
|
||
|
|
} catch (error) {
|
||
|
|
console.error('解析船舶状态失败:', error)
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
id: item.id || index + 1,
|
||
|
|
...item
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
|
||
|
|
// 获取状态对应的样式类
|
||
|
|
const getStatusClass = (status: string) => {
|
||
|
|
switch (status) {
|
||
|
|
case '正常':
|
||
|
|
return 'status-normal'
|
||
|
|
case '异常':
|
||
|
|
return 'status-abnormal'
|
||
|
|
case '维修中':
|
||
|
|
return 'status-maintenance'
|
||
|
|
case '岸电使用中':
|
||
|
|
return 'status-shorepower'
|
||
|
|
case '岸电故障':
|
||
|
|
return 'status-fault'
|
||
|
|
default:
|
||
|
|
return 'status-default'
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
</script>
|
||
|
|
<style lang="scss">
|
||
|
|
.cesium-viewer-bottom {
|
||
|
|
display: none !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.digital-twin-card--deep-blue {
|
||
|
|
/* 深空蓝背景 */
|
||
|
|
background: rgba(8, 15, 30, 0.7);
|
||
|
|
/* 接近纯黑但带蓝调 */
|
||
|
|
backdrop-filter: blur(10px);
|
||
|
|
-webkit-backdrop-filter: blur(10px);
|
||
|
|
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 2px;
|
||
|
|
position: relative;
|
||
|
|
overflow: hidden;
|
||
|
|
|
||
|
|
/* 外边框:霓虹蓝 */
|
||
|
|
border: 1px solid rgba(30, 120, 255, 0.4);
|
||
|
|
box-shadow:
|
||
|
|
0 4px 20px rgba(0, 0, 0, 0.5),
|
||
|
|
inset 0 0 0 1px rgba(30, 120, 255, 0.1);
|
||
|
|
|
||
|
|
/* 文字颜色:冷白/青白 */
|
||
|
|
color: #e6f2ff;
|
||
|
|
font-family: 'Segoe UI', 'Helvetica Neue', 'IBM Plex Mono', monospace;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 内发光效果 */
|
||
|
|
.digital-twin-card--deep-blue::before {
|
||
|
|
content: '';
|
||
|
|
position: absolute;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
bottom: 0;
|
||
|
|
border-radius: 16px;
|
||
|
|
box-shadow: inset 0 0 16px rgba(30, 120, 255, 0.25);
|
||
|
|
pointer-events: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 悬停:增强光效 + 轻微上浮 */
|
||
|
|
.digital-twin-card--deep-blue:hover {
|
||
|
|
border-color: rgba(64, 158, 255, 0.8);
|
||
|
|
background: rgba(10, 20, 40, 0.85);
|
||
|
|
transition: all 0.35s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 可选:顶部高光条(增强“面板”感) */
|
||
|
|
.digital-twin-card--deep-blue::after {
|
||
|
|
content: '';
|
||
|
|
position: absolute;
|
||
|
|
top: 0;
|
||
|
|
left: 0;
|
||
|
|
right: 0;
|
||
|
|
height: 1px;
|
||
|
|
background: linear-gradient(90deg, #00b0ff, #0066ff);
|
||
|
|
opacity: 0.7;
|
||
|
|
border-radius: 16px 16px 0 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card {
|
||
|
|
// background-color: rgba(10, 130, 170, 0.1);
|
||
|
|
// padding: 12px;
|
||
|
|
// border-radius: 4px;
|
||
|
|
// border: 1px solid rgb(10, 130, 170);
|
||
|
|
flex: 1;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
|
||
|
|
.card-title {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #FFF;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 8px;
|
||
|
|
|
||
|
|
.vertical-line {
|
||
|
|
width: 2px;
|
||
|
|
height: 20px;
|
||
|
|
background-color: #1296db;
|
||
|
|
margin-right: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title-icon {
|
||
|
|
width: 20px;
|
||
|
|
height: 20px;
|
||
|
|
margin-right: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.title-text {
|
||
|
|
font-size: 16px;
|
||
|
|
color: #FFF;
|
||
|
|
font-weight: 500;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-content {
|
||
|
|
flex: 1;
|
||
|
|
min-height: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.overview-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 6px;
|
||
|
|
padding: 2px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.overview-item {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
padding: 4px;
|
||
|
|
background-color: rgba(0, 0, 0, 0.2);
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.overview-value {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: bold;
|
||
|
|
color: #1296db;
|
||
|
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||
|
|
margin-bottom: 5px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.overview-label {
|
||
|
|
text-align: center;
|
||
|
|
font-size: 12px;
|
||
|
|
color: #ccc;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 船舶数据表格样式 */
|
||
|
|
.card-content {
|
||
|
|
height: 100%;
|
||
|
|
padding: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-table-container {
|
||
|
|
height: 100%;
|
||
|
|
overflow-y: auto;
|
||
|
|
position: relative;
|
||
|
|
scroll-behavior: smooth;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 自定义滚动条样式 */
|
||
|
|
.ship-data-table-container::-webkit-scrollbar {
|
||
|
|
width: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-table-container::-webkit-scrollbar-track {
|
||
|
|
background: rgba(255, 255, 255, 0.1);
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-table-container::-webkit-scrollbar-thumb {
|
||
|
|
background: rgba(17, 138, 237, 0.7);
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-table-container::-webkit-scrollbar-thumb:hover {
|
||
|
|
background: rgba(17, 138, 237, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-table {
|
||
|
|
width: 100%;
|
||
|
|
padding: 5px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-row {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
padding: 8px 12px;
|
||
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
|
|
transition: background-color 0.3s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-row:hover {
|
||
|
|
background-color: rgba(17, 138, 237, 0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-data-cell {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #fff;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
white-space: nowrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-name {
|
||
|
|
width: 20%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.wharf-name {
|
||
|
|
width: 25%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.berth-number {
|
||
|
|
width: 20%;
|
||
|
|
}
|
||
|
|
|
||
|
|
.shore-power-status {
|
||
|
|
width: 35%;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 状态样式 */
|
||
|
|
.status-using {
|
||
|
|
color: #00ff00;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-no-equipment {
|
||
|
|
color: #ff6600;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-damaged {
|
||
|
|
color: #ff3300;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-cable {
|
||
|
|
color: #ffcc00;
|
||
|
|
}
|
||
|
|
|
||
|
|
.head {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
position: absolute;
|
||
|
|
width: 100vw;
|
||
|
|
height: 72px;
|
||
|
|
top: 0px;
|
||
|
|
right: 0px;
|
||
|
|
|
||
|
|
padding: 12px 24px;
|
||
|
|
|
||
|
|
.head-title {
|
||
|
|
width: 420px;
|
||
|
|
font-size: 28px;
|
||
|
|
color: rgba(20, 200, 255, 1);
|
||
|
|
text-align: center;
|
||
|
|
font-weight: bold;
|
||
|
|
text-align: left;
|
||
|
|
}
|
||
|
|
|
||
|
|
.head-group {
|
||
|
|
display: flex;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
gap: 10px;
|
||
|
|
|
||
|
|
.head-group-item {
|
||
|
|
padding: 8px 16px;
|
||
|
|
background-color: rgba(50, 50, 50, 0.5);
|
||
|
|
color: #ccc;
|
||
|
|
border: 1px solid rgb(122, 122, 122);
|
||
|
|
border-radius: 4px;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all 0.3s ease;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
.head-group-item.active,
|
||
|
|
.head-group-item:hover {
|
||
|
|
color: #FFF;
|
||
|
|
background-color: rgba(17, 138, 237, 0.4);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.head-time {
|
||
|
|
width: 420px;
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: bold;
|
||
|
|
color: #FFF;
|
||
|
|
text-align: right;
|
||
|
|
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
||
|
|
margin-top: 10px;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
.left {
|
||
|
|
width: 420px;
|
||
|
|
padding: 12px;
|
||
|
|
height: calc(100vh - 72px);
|
||
|
|
position: absolute;
|
||
|
|
top: 72px;
|
||
|
|
left: 0px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 20px;
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
.right {
|
||
|
|
width: 420px;
|
||
|
|
padding: 12px;
|
||
|
|
height: calc(100vh - 72px);
|
||
|
|
position: absolute;
|
||
|
|
top: 72px;
|
||
|
|
right: 0px;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 20px;
|
||
|
|
overflow-y: auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 船舶状态表格样式 */
|
||
|
|
.ship-table {
|
||
|
|
width: 100%;
|
||
|
|
height: 100%;
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
background-color: rgba(0, 0, 0, 0.2);
|
||
|
|
border-radius: 8px;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 船舶详情样式 */
|
||
|
|
.ship-detail {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 1px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.detail-item {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: 4px 0;
|
||
|
|
border-bottom: 1px solid rgba(30, 120, 255, 0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.detail-item:last-child {
|
||
|
|
border-bottom: none;
|
||
|
|
}
|
||
|
|
|
||
|
|
.detail-item .label {
|
||
|
|
font-weight: 500;
|
||
|
|
color: #ccc;
|
||
|
|
flex: 0 0 120px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.detail-item .value {
|
||
|
|
flex: 1;
|
||
|
|
text-align: right;
|
||
|
|
color: #e6f2ff;
|
||
|
|
word-break: break-all;
|
||
|
|
}
|
||
|
|
|
||
|
|
.no-selection {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
height: 100%;
|
||
|
|
color: #ccc;
|
||
|
|
font-size: 16px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-header {
|
||
|
|
display: flex;
|
||
|
|
padding: 10px 15px;
|
||
|
|
background-color: rgba(17, 138, 237, 0.3);
|
||
|
|
border-bottom: 1px solid rgba(30, 120, 255, 0.4);
|
||
|
|
font-weight: bold;
|
||
|
|
font-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-body {
|
||
|
|
flex: 1;
|
||
|
|
overflow-y: auto;
|
||
|
|
padding: 5px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-row {
|
||
|
|
display: flex;
|
||
|
|
cursor: pointer;
|
||
|
|
padding: 3px 6px;
|
||
|
|
border-bottom: 1px solid rgba(30, 120, 255, 0.2);
|
||
|
|
transition: background-color 0.2s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-row:hover {
|
||
|
|
background-color: rgba(30, 120, 255, 0.1);
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-column {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-name-header {
|
||
|
|
width: 180px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-status-header {
|
||
|
|
flex: 1;
|
||
|
|
justify-content: flex-start;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-name {
|
||
|
|
width: 180px;
|
||
|
|
gap: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-icon {
|
||
|
|
font-size: 20px;
|
||
|
|
flex-shrink: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-name-text {
|
||
|
|
font-size: 14px;
|
||
|
|
color: #e6f2ff;
|
||
|
|
white-space: nowrap;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-status {
|
||
|
|
flex: 1;
|
||
|
|
gap: 8px;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
justify-content: flex-start;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-tag {
|
||
|
|
padding: 2px 4px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 12px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: #fff;
|
||
|
|
text-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-normal {
|
||
|
|
background-color: #4CAF50;
|
||
|
|
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-abnormal {
|
||
|
|
background-color: #F44336;
|
||
|
|
box-shadow: 0 0 5px rgba(244, 67, 54, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-maintenance {
|
||
|
|
background-color: #FF9800;
|
||
|
|
box-shadow: 0 0 5px rgba(255, 152, 0, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-shorepower {
|
||
|
|
background-color: #2196F3;
|
||
|
|
box-shadow: 0 0 5px rgba(33, 150, 243, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-fault {
|
||
|
|
background-color: #9C27B0;
|
||
|
|
box-shadow: 0 0 5px rgba(156, 39, 176, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-default {
|
||
|
|
background-color: #607D8B;
|
||
|
|
box-shadow: 0 0 5px rgba(96, 125, 139, 0.5);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 滚动条样式 */
|
||
|
|
.ship-table-body::-webkit-scrollbar {
|
||
|
|
width: 6px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-body::-webkit-scrollbar-track {
|
||
|
|
background: rgba(0, 0, 0, 0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-body::-webkit-scrollbar-thumb {
|
||
|
|
background: rgba(30, 120, 255, 0.5);
|
||
|
|
border-radius: 3px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.ship-table-body::-webkit-scrollbar-thumb:hover {
|
||
|
|
background: rgba(30, 120, 255, 0.7);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* 淡入淡出过渡效果 */
|
||
|
|
.fade-left-enter-active,
|
||
|
|
.fade-left-leave-active,
|
||
|
|
.fade-right-enter-active,
|
||
|
|
.fade-right-leave-active {
|
||
|
|
transition: opacity 0.5s ease, transform 0.5s ease;
|
||
|
|
}
|
||
|
|
|
||
|
|
.fade-left-enter-from,
|
||
|
|
.fade-left-leave-to {
|
||
|
|
opacity: 0;
|
||
|
|
transform: translateX(-20px);
|
||
|
|
}
|
||
|
|
|
||
|
|
.fade-right-enter-from,
|
||
|
|
.fade-right-leave-to {
|
||
|
|
opacity: 0;
|
||
|
|
transform: translateX(20px);
|
||
|
|
}
|
||
|
|
</style>
|