Browse Source

update

feature
jiangAB 3 weeks ago
parent
commit
369a6dd341
  1. 509
      public/map/components/CompanyShorePower.vue
  2. 38
      public/map/components/ShipShorePower.vue
  3. 1
      public/map/components/ShorePowerUsage.vue
  4. 1110
      public/map/components/ShorePowerUsageSingleData.vue
  5. 21
      public/map/components/ShowData.vue
  6. 257
      public/map/components/charts/ComparisonBarChart.vue
  7. 13
      public/map/index.vue

509
public/map/components/CompanyShorePower.vue

@ -2,6 +2,50 @@
<div class="company-shore-power">
<div class="left" style="width: 800px;" :style="{ width: selectedShorePowerItem ? '800px' : '420px' }">
<div style="display: flex; gap: 12px; height: 100%;">
<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>
<input class="search-container" type="text" placeholder="搜索岸电设备" v-model="storeSearchKeyword"
@input="handlStoreSearch" />
</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="shorepower in filteredShorePowerList" :key="shorepower.id" class="ship-table-row"
@click="handleSelectShorePower(shorepower)">
<div class="ship-table-column ship-name">
<div class="ship-icon"></div>
<span class="ship-name-text">{{ shorepower.name }}</span>
</div>
<div class="ship-table-column ship-status">
<div class="status-tag" :class="getStatusClass(shorepower.storePowerStatus)">
{{ shorepower.storePowerStatus }}
</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="showShorePowerHistory(shorepower)">查看</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="selectedShorePowerItem" class="card digital-twin-card--deep-blue">
<div style="display: flex; align-items: center; justify-content: space-between;">
<div class="card-title">
@ -65,50 +109,7 @@
</div>
</div>
</div>
<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>
<input class="search-container" type="text" placeholder="搜索岸电设备" v-model="storeSearchKeyword"
@input="handlStoreSearch" />
</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="shorepower in filteredShorePowerList" :key="shorepower.id" class="ship-table-row"
@click="handleSelectShorePower(shorepower)">
<div class="ship-table-column ship-name">
<div class="ship-icon"></div>
<span class="ship-name-text">{{ shorepower.name }}</span>
</div>
<div class="ship-table-column ship-status">
<div class="status-tag" :class="getStatusClass(shorepower.storePowerStatus)">
{{ shorepower.storePowerStatus }}
</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="showShorePowerHistory(shorepower)">查看</el-button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="right" style="width: 1000px;">
@ -137,7 +138,7 @@
<div class="company-header">
<div class="company-info">
<div class="company-name">{{ company.name }}</div>
<div class="company-berth-count">泊位数: {{ company.children?.length || 0 }}</div>
<div class="company-berth-count">泊位数: {{ company.shorePowerCount || 0 }}</div>
</div>
<div class="time-range-container">
<button v-for="option in timeRangeOptions" :key="option.value"
@ -145,19 +146,27 @@
@click.stop="() => handleTimeRangeChange(company.id, option.value)">
{{ option.label }}
</button>
<div class="company-total">总量: {{ calculateTotal(company.children).toFixed(2) }}</div>
<div class="company-total">总量: {{ handGetDeviceData(company.shorePowerIds) }}</div>
</div>
</div>
<div class="overview-grid">
<div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item"
@click="handleClickBerthItem(berth)">
<div class="overview-value">{{ berth.measureValue?.toFixed(2)
|| 0 }}</div>
<div class="shorepower-container">
<div class="diste-box" v-for="(disteItem, index) in (company.distributionList || [])" :key="index">
<div class="diste-name">{{ disteItem.name }}</div>
<div class="overview-label">{{ berth.name }}</div>
<div class="shorepower-box">
<div class="shorepower-item" v-for="(shorePower, shorePowerIndex) in (disteItem.shorePowerList || [])"
:key="shorePowerIndex">
<div class="shorepower-value" @click="handleClickhorePowerValue(shorePower)">{{
handGetDeviceData(shorePower.totalPowerDeviceId) || 0 }}</div>
<div class="shorepower-label" @click="handleClickShorePowerName(shorePower)">{{ shorePower.name }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -169,18 +178,23 @@
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { ref, computed } from 'vue'
import { MapApi } from "@/api/shorepower/map";
import ShipHistoryDialog from './ShipHistoryDialog.vue';
import { RealtimeDeviceData, ShipRespVo, ShorePowerBerth } from '@/types/shorepower';
import { getValueById } from './utils';
//
interface ChartDataItem {
interface companyData {
id: number;
name: string;
value?: number;
measureValue?: number;
children?: ChartDataItem[];
harborDistrictId: number;
name?: string;
shorePowerCount: number; //
shorePowerIds: number[]; // ID
distributionList: {
id: number;
name: string;
shorePowerList: any[];
}[];
}
const historyVisible = ref({
@ -329,39 +343,24 @@ const handleTimeRangeChange = (companyId: number, range: 'realtime' | 'day' | 'm
//
}
//
const calculateTotal = (children?: ChartDataItem[]) => {
if (!children || children.length === 0) return 0;
return children.reduce((total, item) => {
return total + (item.measureValue || 0);
}, 0);
};
/* 点击泊位box */
const handleClickBerthItem = (berth: ChartDataItem) => {
console.log(props.shorePowerList)
console.log(berth)
const shorePower = props.shorePowerList.find(item => item.id === berth.id)
const handleClickShorePowerName = (data: companyData) => {
const shorePower = props.shorePowerList.find(item => item.id === data.id)
selectedShorePowerItem.value = shorePower
console.log(shorePower)
}
const handleOpenHistory = () => {
if (selectedShorePowerItem.value) {
showShorePowerHistory(selectedShorePowerItem.value)
const handleClickhorePowerValue = (data: companyData) => {
const shorePower = props.shorePowerList.find(item => item.id === data.id)
if (!shorePower) {
return
}
showShorePowerHistory(shorePower)
/* selectedShorePowerItem.value = shorePower
console.log(shorePower) */
}
//
const showShorePowerHistory = (berth: ChartDataItem) => {
console.log(berth)
// console.log('shorepower', shorepower)
// currentShorePower.value = shorepower
/* shorePowerHistoryVisible.value = {
visible: true,
shorePowerId: shorepower.id || 0
} */
const showShorePowerHistory = (berth: ShorePowerBerth) => {
historyVisible.value = {
visible: true,
searchParams: {
@ -371,197 +370,80 @@ const showShorePowerHistory = (berth: ChartDataItem) => {
}
}
}
const companyComparisonData = ref<ChartDataItem[]>([])
const companyComparisonData = ref<companyData[]>([])
//
const deviceDataMap = computed(() => {
const map = new Map<number, RealtimeDeviceData>();
props.realtimeDeviceData.forEach(item => {
map.set(item.deviceId, item);
});
return map;
});
const handGetDeviceData = (deviceId: number | number[]) => {
let totalValue = 0;
if (Array.isArray(deviceId)) {
// If deviceId is an array, sum all the measure values
deviceId.forEach(id => {
const deviceData = deviceDataMap.value.get(id);
if (deviceData?.measureValue) {
totalValue += deviceData.measureValue;
}
});
} else {
// If deviceId is a single number, get its measure value
const deviceData = deviceDataMap.value.get(deviceId);
if (deviceData?.measureValue) {
totalValue = deviceData.measureValue;
}
}
return totalValue.toFixed(2);
}
const handleGetBuildData = async () => {
const dockList = await MapApi.getDockIdAndNameList()
const berthList = await MapApi.getBerthIdAndNameList()
const buildData = dockList.map(dock => ({
...dock,
children: berthList.filter(berth => berth.dockId === dock.id).map(berth => ({
...berth,
...props.realtimeDeviceData.find(item => (item.id === berth.id) && (item.deviceCode.includes('Kwh')))
}))
}))
companyComparisonData.value = buildData
companyComparisonData.value.push(
{
"id": 4,
"harborDistrictId": 1,
"name": "模拟码头公司4",
"children": [
{
"id": 6,
"dockId": 3,
"districtId": 1,
"name": "模拟-201#泊位",
"deviceId": 6,
"deviceName": "模拟-1号岸电-201泊位",
"deviceCode": "CFD_GT_201_0.4KV.Kwh",
"deviceStatus": 3,
"measureValue": 3344627.0265,
"incrementValue": 17.846733086563358,
"incrementCost": 17.846733086563358,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 7,
"dockId": 3,
"districtId": 1,
"name": "模拟-202#泊位",
"deviceId": 7,
"deviceName": "模拟-1号岸电-202泊位",
"deviceCode": "CFD_GT_202_0.4KV.Kwh",
"deviceStatus": 3,
"measureValue": 5338783.7211,
"incrementValue": 20.846911166236122,
"incrementCost": 20.846911166236122,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 8,
"dockId": 3,
"districtId": 1,
"name": "模拟-203#泊位",
"deviceId": 8,
"deviceName": "模拟-2号岸电-203泊位",
"deviceCode": "CFD_GT_203_6/6.6KV.Kwh",
"deviceStatus": 3,
"measureValue": 10921413.801,
"incrementValue": 19.737663764627705,
"incrementCost": 19.737663764627705,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 9,
"dockId": 3,
"districtId": 2,
"name": "模拟-204#泊位",
"deviceId": 9,
"deviceName": "模拟-2号岸电-204泊位",
"deviceCode": "CFD_GT_204_6/6.6KV.Kwh",
"deviceStatus": 3,
"measureValue": 2680281.7026,
"incrementValue": 21.264192979883447,
"incrementCost": 21.264192979883447,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 10,
"dockId": 3,
"districtId": 2,
"name": "模拟-205#泊位",
"deviceId": 10,
"deviceName": "模拟-2号岸电-205泊位",
"deviceCode": "CFD_GT_205_6/6.6KV.Kwh",
"deviceStatus": 3,
"measureValue": 4975354.5181,
"incrementValue": 21.75554591307556,
"incrementCost": 21.75554591307556,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 11,
"dockId": 3,
"districtId": null,
"name": "模拟-206#泊位",
"deviceId": 11,
"deviceName": "模拟-3号岸电-206泊位",
"deviceCode": "CFD_GT_206_6/6.6KV.Kwh",
"deviceStatus": 3,
"measureValue": 4900877.9997,
"incrementValue": 17.841940952939577,
"incrementCost": 17.841940952939577,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 12,
"dockId": 3,
"districtId": null,
"name": "国投-207#泊位",
"deviceId": 12,
"deviceName": "模拟-3号岸电-207泊位",
"deviceCode": "CFD_GT_207_6/6.6KV.Kwh",
"deviceStatus": 3,
"measureValue": 5093718.3972,
"incrementValue": 21.524539074803123,
"incrementCost": 21.524539074803123,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 13,
"dockId": 3,
"districtId": null,
"name": "模拟-208#泊位",
"deviceId": 13,
"deviceName": "模拟-3号岸电-208泊位",
"deviceCode": "CFD_GT_208_6/6.6KV.Kwh",
"deviceStatus": 4,
"measureValue": 7700103.7496,
"incrementValue": 0,
"incrementCost": 0,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 14,
"dockId": 3,
"districtId": null,
"name": "模拟-209#泊位",
"deviceId": 14,
"deviceName": "模拟-4号岸电-209泊位",
"deviceCode": "CFD_GT_209_6/6.6KV.Kwh",
"deviceStatus": 4,
"measureValue": 3845558.9787,
"incrementValue": 0,
"incrementCost": 0,
"measureTime": 1766479519223,
"createTime": null
},
{
"id": 15,
"dockId": 3,
"districtId": null,
"name": "模拟-210#泊位",
"deviceId": 15,
"deviceName": "模拟-4号岸电-210泊位",
"deviceCode": "CFD_GT_210_6/6.6KV.Kwh",
"deviceStatus": 4,
"measureValue": 3649994.7502,
"incrementValue": 0,
"incrementCost": 0,
"measureTime": 1766479519223,
"createTime": null
}
]
// const berthList = await MapApi.getBerthIdAndNameList()
const distributionList = groupByShorePowerEquipmentId(props.shorePowerList)
const buildData = dockList.map(dock => {
const filterData = distributionList.filter(item => item.dockId === dock.id)
const shorePowerCount = filterData.reduce((total, item) => {
return total + item.shorePowerList.length
}, 0)
return {
...dock,
shorePowerCount: shorePowerCount, //
shorePowerIds: filterData.map(item => item.shorePowerList.map(shorePower => shorePower.id)).flat(),
distributionList: filterData
}
)
/* MapApi.getBerthIdAndNameList().then(res => {
console.log(res)
const buildData = res.map(item => ({
...item,
children: props.realtimeDeviceData.filter(item => item.id === item.id)
}))
})
companyComparisonData.value = buildData
}) */
/* 转换函数 */
function groupByShorePowerEquipmentId(data) {
const map = new Map();
for (const item of data) {
const { shorePowerEquipmentInfo, ...rest } = item;
const id = shorePowerEquipmentInfo.id;
if (!map.has(id)) {
map.set(id, {
...shorePowerEquipmentInfo,
shorePowerList: []
});
}
map.get(id).shorePowerList.push(rest);
}
return Array.from(map.values());
}
}
watch(() => props.realtimeDeviceData, (newVal) => {
handleGetBuildData()
})
onMounted(() => {
// console.log(props.realtimeDeviceData)
handleGetBuildData()
})
</script>
<style scoped>
<style scoped lang="scss">
.company-shore-power {
display: flex;
height: 100%;
@ -590,47 +472,88 @@ onMounted(() => {
}
/* 总览网格样式 */
.overview-grid {
/* .overview-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 8px;
padding: 4px;
height: 100%;
}
.overview-item {
width: 224px;
cursor: pointer;
} */
.shorepower-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
gap: 8px;
flex-wrap: wrap;
.diste-box {
flex-shrink: 0;
display: inline-block;
margin-bottom: 8px;
.diste-name {
height: 28px;
background-color: rgba(0, 112, 192, 0.579);
border-radius: 6px 6px 0 0;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
color: #fff;
padding: 4px;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}
}
.overview-label {
text-align: center;
font-size: 24px;
color: #ccc;
margin-bottom: 2px;
}
.shorepower-box {
display: flex;
gap: 8px;
// flex-wrap: wrap;
}
.overview-value {
font-size: 36px;
font-weight: bold;
color: #1296db;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
margin-bottom: 2px;
}
.shorepower-item {
display: inline-flex;
width: 224px;
// display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
background-color: rgba(30, 120, 255, 0.15);
border-radius: 0 0 8px 8px;
}
.shorepower-label {
text-align: center;
font-size: 24px;
color: #ccc;
margin-bottom: 2px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
//
transform: translateY(-2px);
}
}
.overview-text {
font-size: 10px;
color: #999;
text-align: center;
.shorepower-value {
font-size: 36px;
font-weight: bold;
color: #1296db;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
margin-bottom: 2px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
//
transform: translateY(-2px);
}
}
}
/* 企业区块样式 */
.company-section {
margin-bottom: 16px;

38
public/map/components/ShipShorePower.vue

@ -18,15 +18,19 @@
<div class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ shipTotalData.berthingShips }}</div>
<div class="overview-label">在港船舶数量</div>
<div class="overview-label">在港船舶</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ shipTotalData.berthingShips }}</div>
<div class="overview-label">在泊船舶</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ shipTotalData.shorePowerShips }}</div>
<div class="overview-label">使用岸电船舶数量</div>
<div class="overview-label">使用岸电船舶</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ shipTotalData.noShorePowerShips }}</div>
<div class="overview-label">未使用岸电船舶数量</div>
<div class="overview-label">未使用岸电船舶</div>
</div>
</div>
</div>
@ -84,9 +88,9 @@
<el-table-column prop="status" label="岸电状态" width="400">
<template #default="scope">
<span v-if="scope.row.applyInfo?.reason === 0"
:class="['shore-power-status', showStatus(scope.row, realtimeDeviceData)?.statusClass]">{{
:class="['shore-power-status', showStatus(scope.row, realtimeDeviceData)?.statusClass]">{{
showStatus(scope.row,
realtimeDeviceData)?.useTime }},{{
realtimeDeviceData)?.useTime }},{{
showStatus(scope.row, realtimeDeviceData)?.useValue }}
</span>
<span v-if="scope.row.applyInfo?.reason != 0" class="shore-power-status status-off">{{
@ -123,7 +127,7 @@
<div class="detail-item">
<span class="label">名称:</span>
<span class="value">{{ selectedShip.shipBasicInfo.name + '-' + selectedShip.shipBasicInfo.nameEn
}}</span>
}}</span>
</div>
<div class="detail-item">
<span class="label">长度:</span>
@ -165,7 +169,7 @@
<div class="detail-item">
<span class="label">靠泊类型:</span>
<span class="value">{{ getOperationTypeLabel(selectedShip.shorePowerAndShip.type, BERTH_TYPE)
}}</span>
}}</span>
</div>
<div class="detail-item">
<span class="label">靠泊时间:</span>
@ -201,7 +205,7 @@
</div>
</div>
<!-- 船舶详情对话框 -->
<el-dialog v-if="detailDialogVisible" v-model="detailDialogVisible" title="更多信息" width="1000px"
<el-dialog v-if="detailDialogVisible" v-model="detailDialogVisible" title="备案信息" width="1000px"
class="ship-detail-dialog">
<el-tabs v-if="selectedShip" v-model="activeTab">
<el-tab-pane label="用电申请信息" name="applyInfo">
@ -233,7 +237,7 @@
<el-form-item label="到达港:">
<span>{{ getOperationTypeLabel(selectedShip.applyInfo?.arrivalHarborDistrict,
harborDistrictIdAndNameList)
}}</span>
}}</span>
</el-form-item>
</el-col>
</el-row>
@ -288,7 +292,7 @@
<el-col :span="12">
<el-form-item label="货物类型(装货):">
<span>{{ getOperationTypeLabel(selectedShip.applyInfo?.loadingCargoCategory, CARGO_CATEGORY) || '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
@ -310,7 +314,7 @@
<el-col :span="12">
<el-form-item label="货物类型(卸货):">
<span>{{ getOperationTypeLabel(selectedShip.applyInfo?.unloadingCargoCategory, CARGO_CATEGORY) || '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
@ -339,7 +343,7 @@
<el-col :span="12" v-if="selectedShip.applyInfo?.reason !== 0">
<el-form-item label="未使用岸电原因:">
<span>{{ getOperationTypeLabel(selectedShip.applyInfo?.reason, UNUSED_SHORE_POWER_REASON) || '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
</el-row>
@ -450,12 +454,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用电开始时用电方操作人:">
<el-form-item label="用电开始时方操作人:">
<span>{{ selectedShip.usageRecordInfo?.beginPowerUsageOperator || '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用电结束时用电方操作人:">
<el-form-item label="用电结束时方操作人:">
<span>{{ selectedShip.usageRecordInfo?.overPowerUsageOperator || '-' }}</span>
</el-form-item>
</el-col>
@ -531,13 +535,13 @@
<el-col :span="12">
<el-form-item label="船舶长度:">
<span>{{ selectedShip.shipBasicInfo?.length !== undefined ? selectedShip.shipBasicInfo?.length : '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="船舶宽度:">
<span>{{ selectedShip.shipBasicInfo?.width !== undefined ? selectedShip.shipBasicInfo?.width : '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
</el-row>
@ -546,7 +550,7 @@
<el-col :span="12">
<el-form-item label="船舶吨位:">
<span>{{ selectedShip.shipBasicInfo?.tonnage !== undefined ? selectedShip.shipBasicInfo?.tonnage : '-'
}}</span>
}}</span>
</el-form-item>
</el-col>
<el-col :span="12">

1
public/map/components/ShorePowerUsage.vue

@ -804,7 +804,6 @@ const handleGetRealTimeAllData = async (realtimeDeviceData: RealtimeDeviceData[]
try {
const res: RealtimeDeviceData[] = realtimeDeviceData.filter(item => item.deviceCode.includes('Kwh'));
const ids = res.map(item => item.id);
console.log(ids);
const params = {
ids: ids.join(','),
// year: new Date().getFullYear()

1110
public/map/components/ShorePowerUsageSingleData.vue

File diff suppressed because it is too large

21
public/map/components/ShowData.vue

@ -184,22 +184,26 @@
</div>
</div>
<div class="ship-status">
<div class="usage-rate">
<div class="usage-rate shadow">
<div class="rate-value">{{ calculateShorePowerUsageRate() }}%</div>
<div class="rate-label">岸电使用率</div>
</div>
<div class="status-grid">
<div class="status-card" @click="handleShowShipList('all')">
<div class="status-value">{{ shipTotalData.berthingShips }}</div>
<div class="status-label">在港船舶数量</div>
<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>
</div>
<div class="status-card" @click="handleShowShipList('using')">
<div class="status-value">{{ shipTotalData.shorePowerShips }}</div>
<div class="status-label">使用岸电船舶数量</div>
<div class="status-label">使用岸电船舶</div>
</div>
<div class="status-card" @click="handleShowShipList('notUsing')">
<div class="status-value">{{ shipTotalData.noShorePowerShips }}</div>
<div class="status-label">未使用岸电船舶数量</div>
<div class="status-label">未使用岸电船舶</div>
</div>
</div>
<!-- <div class="ship-list">
@ -498,7 +502,7 @@ const switchShipPeriod = (period: string) => {
//
const handleCardClick = (cardType: string) => {
// 使activeHeadGroup = 1
props.handleGoToModule(1)
props.handleGoToModule(5)
// 使nextTickselectedCard
setTimeout(() => {
// 线ShorePowerUsage
@ -867,6 +871,11 @@ const handleCloseShipList = () => {
margin-bottom: 6px;
}
.usage-rate.shadow {
box-shadow: 0 0px 10px rgb(255, 255, 255);
border: 2px solid rgba(255, 255, 255, 0.2);
}
.rate-value {
font-size: 36px;
font-weight: bold;
@ -881,7 +890,7 @@ const handleCloseShipList = () => {
.status-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-columns: repeat(4, 1fr);
gap: 6px;
margin-bottom: 6px;
}

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

@ -0,0 +1,257 @@
<template>
<div ref="chartContainer" class="comparison-bar-chart-container"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watch } from 'vue'
import * as echarts from 'echarts'
//
interface Props {
chartData?: Array<{
name: string
currentValue: number
previousValue: number
}>
title?: string
currentColor?: string
previousColor?: string
showYAxis?: boolean
yAxisName?: string
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
currentColor: '#1296db',
previousColor: '#ff6b6b',
showYAxis: true,
yAxisName: '数值'
})
//
const chartContainer = ref<HTMLDivElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const initChart = () => {
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value)
updateChart()
}
}
//
const updateChart = () => {
if (!chartInstance || !props.chartData.length) return
const option = {
title: {
text: props.title,
textStyle: {
color: '#fff',
fontSize: 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
},
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff'
},
formatter: function (params: any) {
const currentData = params[0]
const previousData = params[1]
const growthRate = previousData.value > 0
? ((currentData.value - previousData.value) / previousData.value * 100).toFixed(2)
: '∞'
return `${currentData.name}<br/>
${currentData.seriesName}: ${currentData.value}<br/>
${previousData.seriesName}: ${previousData.value}<br/>
增长率: ${growthRate}%`
}
},
legend: {
data: ['本期', '上期'],
textStyle: {
color: '#fff'
},
top: 30
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '25%',
containLabel: true
},
xAxis: {
type: 'category',
data: props.chartData.map(item => item.name),
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
rotate: 0,
interval: 0,
margin: 15
}
},
yAxis: {
type: 'value',
show: props.showYAxis,
name: props.yAxisName,
nameTextStyle: {
color: 'rgba(255, 255, 255, 0.7)'
},
axisLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
show: props.showYAxis,
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
show: props.showYAxis,
color: 'rgba(255, 255, 255, 0.7)'
}
},
series: [
{
name: '当前期',
type: 'bar',
data: props.chartData.map(item => item.currentValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.currentColor
},
{
offset: 1,
color: props.currentColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.currentColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
}
},
{
name: '对比期',
type: 'bar',
data: props.chartData.map(item => item.previousValue),
barWidth: '35%',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.previousColor
},
{
offset: 1,
color: props.previousColor + '80'
}
]
},
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: props.previousColor
}
},
label: {
show: true,
position: 'top',
color: '#fff',
formatter: '{c}',
fontSize: 12
}
}
]
}
chartInstance.setOption(option)
}
//
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
//
onMounted(() => {
setTimeout(() => {
initChart()
}, 2000)
window.addEventListener('resize', handleResize)
})
//
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.comparison-bar-chart-container {
width: 100%;
height: 100%;
}
</style>

13
public/map/index.vue

@ -37,18 +37,21 @@
:ship-data-list="shipDataList" @item-click="handleSwitch" :realtime-device-data="realtimeDeviceData"
v-if="dataLoad" />
<!-- 港口岸电使用情况 -->
<!-- <template v-if="activeHeadGroup === 1"> -->
<!-- 港口岸电使用情况多数据 -->
<ShorePowerUsage v-show="activeHeadGroup === 1" :realtime-device-data-time="realtimeDeviceDataTime"
:realtime-device-data="realtimeDeviceData" :active-head-group="activeHeadGroup" v-if="dataLoad"
:handleGoBack="handleGoBack" />
<!-- </template> -->
<!-- 港口岸电使用情况单数据 -->
<ShorePowerUsageSingleData v-show="activeHeadGroup === 5" :realtime-device-data-time="realtimeDeviceDataTime"
:realtime-device-data="realtimeDeviceData" :active-head-group="activeHeadGroup" v-if="dataLoad"
:handleGoBack="handleGoBack" />
<!-- 港口企业岸电使用 -->
<!-- <template v-if="activeHeadGroup === 2"> -->
<CompanyShorePower v-show="activeHeadGroup === 2" :realtime-device-data="realtimeDeviceData"
:shore-power-list="ShorePowerList" :handleGoBack="handleGoBack"
:realtime-device-data-time="realtimeDeviceDataTime" />
:realtime-device-data-time="realtimeDeviceDataTime" v-if="dataLoad" />
<!-- </template> -->
<!-- 船舶岸电使用情况 -->
@ -249,6 +252,7 @@
import PublicMapComponents from './components/cesiumMap.vue'
// import PortOverview from './components/PortOverview.vue'
import ShorePowerUsage from './components/ShorePowerUsage.vue'
import ShorePowerUsageSingleData from './components/ShorePowerUsageSingleData.vue'
import ShowData from './components/ShowData.vue'
import CompanyShorePower from './components/CompanyShorePower.vue'
import ShipShorePower from './components/ShipShorePower.vue'
@ -830,7 +834,6 @@ const handleGetlRangeData = async (realtimeDeviceData: RealtimeDeviceData[]) =>
try {
const res: RealtimeDeviceData[] = realtimeDeviceData.filter(item => item.deviceCode.includes('Kwh'));
const ids = res.map(item => item.id);
console.log(ids);
const params = {
ids: ids.join(','),
// year: new Date().getFullYear()

Loading…
Cancel
Save