Browse Source

update

feature
jiangAB 3 weeks ago
parent
commit
75c2bd64ad
  1. 2
      .env.dev
  2. 2
      .env.local
  3. 2
      .env.prod
  4. 2
      .env.stage
  5. 2
      .env.test
  6. 1
      public/map/components/CompanyShorePower.vue
  7. 132
      public/map/components/ShipShorePower.vue
  8. 364
      public/map/components/ShorePowerUsage.vue
  9. 608
      public/map/components/ShowData.vue
  10. 2
      public/map/components/bk/map_index.vue
  11. 25
      public/map/components/cesiumMap.vue
  12. 356
      public/map/index.vue
  13. 15
      src/types/shorepower.d.ts

2
.env.dev

@ -22,7 +22,7 @@ VITE_DROP_CONSOLE=false
VITE_SOURCEMAP=true
# 打包路径
VITE_BASE_PATH='http://106.118.88.15:48080'
VITE_BASE_PATH='http://server.ayaojies.com.cn:48080'
# 输出路径
VITE_OUT_DIR=dist

2
.env.local

@ -4,7 +4,7 @@ NODE_ENV=development
VITE_DEV=true
# 请求路径
VITE_BASE_URL='http://106.118.88.15:48080'
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持 S3 服务
VITE_UPLOAD_TYPE=server

2
.env.prod

@ -4,7 +4,7 @@ NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://106.118.88.15:48080'
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server

2
.env.stage

@ -4,7 +4,7 @@ NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://106.118.88.15:48080'
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server

2
.env.test

@ -4,7 +4,7 @@ NODE_ENV=production
VITE_DEV=false
# 请求路径
VITE_BASE_URL='http://106.118.88.15:48080'
VITE_BASE_URL='http://server.ayaojies.com.cn:48080'
# 文件上传类型:server - 后端上传, client - 前端直连上传,仅支持S3服务
VITE_UPLOAD_TYPE=server

1
public/map/components/CompanyShorePower.vue

@ -8,6 +8,7 @@
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">码头与泊位信息</span>
<div class="data-update-time">起始时间: 2025-01-01 00:00:00</div>
<div class="data-update-time">更新时间: {{ realtimeDeviceDataTime }}</div>
</div>

132
public/map/components/ShipShorePower.vue

@ -1,12 +1,14 @@
<template>
<div class="ship-shore-power">
<div class="left" style="width: 70%;">
<div class="left" style="width: 75%;">
<div class="card digital-twin-card--deep-blue" style="flex: 1;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<div class="card-title">
<div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">概览</span>
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
</div>
<el-button type="primary" @click.stop="handleGoBack()">返回</el-button>
@ -15,15 +17,15 @@
<div class="card-content">
<div class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ berthingShips }}</div>
<div class="overview-value">{{ shipTotalData.berthingShips }}</div>
<div class="overview-label">在港船舶数量</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ shorePowerShips }}</div>
<div class="overview-value">{{ shipTotalData.shorePowerShips }}</div>
<div class="overview-label">使用岸电船舶数量</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ noShorePowerShips }}</div>
<div class="overview-value">{{ shipTotalData.noShorePowerShips }}</div>
<div class="overview-label">未使用岸电船舶数量</div>
</div>
</div>
@ -74,11 +76,19 @@
<span>{{ formatDateTime(scope.row?.usageRecordInfo?.endTime) }}</span>
</template>
</el-table-column>
<el-table-column prop="shipStatus" label="船舶状态">
<template #default="scope">
<span :class="[getStatusClass(scope.row.shipStatus)]">{{ scope.row.shipStatus }}</span>
</template>
</el-table-column>
<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]">{{
showStatus(scope.row, realtimeDeviceData)?.status }}</span>
:class="['shore-power-status', showStatus(scope.row, realtimeDeviceData)?.statusClass]">时长{{
showStatus(scope.row,
realtimeDeviceData)?.useTime }},用量{{
showStatus(scope.row, realtimeDeviceData)?.useValue }}
</span>
<span v-if="scope.row.applyInfo?.reason != 0" class="shore-power-status status-off">{{
getOperationTypeLabel(scope.row.applyInfo?.reason,
UNUSED_SHORE_POWER_REASON) }}</span>
@ -223,7 +233,7 @@
<el-form-item label="到达港:">
<span>{{ getOperationTypeLabel(selectedShip.applyInfo?.arrivalHarborDistrict,
harborDistrictIdAndNameList)
}}</span>
}}</span>
</el-form-item>
</el-col>
</el-row>
@ -329,7 +339,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>
@ -430,17 +440,18 @@
<span>{{ selectedShip.usageRecordInfo?.beginPowerSupplyOperator || '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="用电开始时用电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.beginPowerUsageOperator || '-' }}</span>
<el-form-item label="用电结束时供电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.overPowerSupplyOperator || '-' }}</span>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="用电结束时供电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.overPowerSupplyOperator || '-' }}</span>
<el-form-item label="用电开始时用电方操作人:">
<span>{{ selectedShip.usageRecordInfo?.beginPowerUsageOperator || '-' }}</span>
</el-form-item>
</el-col>
<el-col :span="12">
@ -520,13 +531,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>
@ -535,7 +546,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">
@ -604,6 +615,12 @@ import { formatTimestamp, getValueById, showStatus } from './utils';
interface Props {
shipData: ShipRespVo[];
realtimeDeviceData: RealtimeDeviceData[];
realtimeDeviceDataTime: string;
shipTotalData: {
berthingShips: number;
shorePowerShips: number;
noShorePowerShips: number;
};
handleGoBack: () => void;
}
@ -635,6 +652,38 @@ const berthingShips = computed(() => localShipData.value.length)
const shorePowerShips = computed(() => localShipData.value.filter(ship => ship.applyInfo.reason === 0).length)
const noShorePowerShips = computed(() => localShipData.value.filter(ship => ship.applyInfo.reason !== 0).length)
//
const startTimeDisplay = computed(() => {
const startOfDay = dayjs().startOf('day');
return startOfDay.format('YYYY-MM-DD HH:mm:ss');
})
// CSS
const getStatusClass = (status: string | undefined) => {
switch (status) {
case '正常':
return 'status-normal-1'
case '在线':
return 'status-normal-1'
case '空闲':
return 'status-idle-1'
case '故障':
return 'status-fault-1'
case '超容':
return 'status-maintenance-1'
case '异常':
return 'status-abnormal-1'
case '维修中':
return 'status-maintenance-1'
case '岸电使用中':
return 'status-shorepower-1'
// case '':
// return 'status-fault'
default:
return 'status-default-1'
}
}
const filteredShipData = computed(() => {
//
let statusFilteredData = localShipData.value;
@ -1057,4 +1106,57 @@ onMounted(async () => {
background-color: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.5);
}
.time-range-item {
margin-left: 10px;
font-size: 16px;
font-weight: 600;
font-weight: bold;
color: rgba(255, 255, 255, 0.7);
}
.status-normal-1 {
color: #00ff00;
font-weight: bold;
}
/* 空闲状态 */
.status-idle-1 {
color: #2196F3;
/* 蓝色 */
font-weight: bold;
}
/* 故障状态 */
.status-fault-1 {
color: #F44336;
/* 红色 */
font-weight: bold;
}
.status-abnormal-1 {
color: #F44336;
font-weight: bold;
}
.status-maintenance-1 {
color: #FF9800;
font-weight: bold;
}
.status-shorepower-1 {
color: #2196F3;
font-weight: bold;
}
/* .status-fault {
background-color: #9C27B0;
box-shadow: 0 0 5px rgba(156, 39, 176, 0.5);
} */
.status-default-1 {
color: #b4babd;
font-weight: bold;
}
</style>

364
public/map/components/ShorePowerUsage.vue

@ -1,143 +1,7 @@
<template>
<div class="shore-power-usage">
<template v-if="pageType === 'realtime'">
<!-- Average Mode -->
<template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;">
<div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<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">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @click.stop />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
<div class="right average" style="width: 40%;">
<div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<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">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker v-if="historyTimeGranularity === 'year'" type="yearrange" range-separator=""
start-placeholder="开始年" end-placeholder="结束年" format="YYYY" value-format="YYYY"
class="date-range-picker" @click.stop />
<el-date-picker v-else-if="historyTimeGranularity === 'month'" type="monthrange" range-separator=""
start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM" value-format="YYYY-MM"
class="date-range-picker" @click.stop />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" type="weekrange" range-separator=""
start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD" value-format="YYYY-MM-DD"
class="date-range-picker" @click.stop /> -->
<el-date-picker v-else type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@click.stop />
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" 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>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
</template>
<!-- Magnify Mode -->
<div v-if="displayMode === 'magnify'" class="magnify">
<div class="magnify">
<div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue">
@ -148,21 +12,29 @@
<span class="title-text">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-button type="primary" @click.stop="pageType = 'history'">历史查询</el-button>
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
<!-- 返回上个页面 -->
<el-button type="primary" @click.stop="pageType = 'history'">历史查询</el-button>
<el-button type="primary" @click.stop="handleGoBack()">返回</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<div class="time-range-item">起始时间: {{ startTimeDisplay }}</div>
<div class="time-range-item">更新时间: {{ realtimeDeviceDataTime }}</div>
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
<el-button type="primary" @click.stop="handleGoBack()">返回</el-button>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
@ -252,121 +124,7 @@
</div>
</template>
<template v-if="pageType === 'history'">
<!-- Average Mode -->
<template v-if="displayMode === 'average'">
<div class="left average" style="width: 40%;">
<div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<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">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<button v-for="option in historyTimeGranularityOptions" :key="option.value"
:class="['time-range-btn', { active: historyTimeGranularity === option.value }]"
@click.stop="handleHistoryTimeGranularityChange(option.value as 'year' | 'month' | 'week' | 'day')">
{{ option.label }}
</button>
<el-button type="primary" @click.stop="handleDateRangeConfirm">选择并确认</el-button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div>
</div>
<div class="overview-item">
<div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div>
</div>
</div>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
<div class="right average" style="width: 40%;">
<div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)">
<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">{{ card.title }}</span>
</div>
<div v-if="card.type === 'overview'" class="time-range-selector">
<el-date-picker type="daterange" range-separator="" start-placeholder="开始日期" end-placeholder="结束日期"
format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker" @click.stop />
<button v-for="option in timeRangeOptions" :key="option.value"
:class="['time-range-btn', { active: timeRange === option.value }]"
@click.stop="handleTimeRangeChange(option.value)">
{{ option.label }}
</button>
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
</div>
</div>
<div class="card-content">
<div v-if="card.type === 'overview'" 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>
<WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div>
</div>
</div>
</template>
<!-- Magnify Mode -->
<div v-if="displayMode === 'magnify'" class="magnify">
<div class="magnify">
<div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue">
@ -383,9 +141,6 @@
<el-date-picker v-else-if="historyTimeGranularity === 'month'" v-model="dateRange" type="monthrange"
range-separator="至" start-placeholder="开始月" end-placeholder="结束月" format="YYYY-MM"
value-format="YYYY-MM" class="date-range-picker" @change="handleSelectDate" />
<!-- <el-date-picker v-else-if="historyTimeGranularity === 'week'" v-model="dateRange" type="week"
range-separator="至" start-placeholder="开始周" end-placeholder="结束周" format="YYYY-MM-DD"
value-format="YYYY-MM-DD" class="date-range-picker" @change="handleSelectDate" /> -->
<el-date-picker v-else v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日"
end-placeholder="结束日" format="YYYY-MM-DD" value-format="YYYY-MM-DD" class="date-range-picker"
@change="handleSelectDate" />
@ -402,9 +157,11 @@
</button> -->
</div>
<div v-if="card.type === 'chart'" class="show-value">
<span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span>
<div class="show-value-value">{{ chartData[timeRange][card.value] }}</div>
<div class="time-range-item">记录时间范围: {{ selectedDateRangeDisplay }}</div>
<!-- <div class="time-range-item">{{ realtimeDeviceDataTime }}</div> -->
<!-- <span class="show-value-label">{{timeRangeOptions.find(option => option.value === timeRange)?.label ||
''}}:</span> -->
<!-- <div class="show-value-value">{{ chartData[timeRange][card.value] }}</div> -->
</div>
</div>
<div class="card-content">
@ -495,11 +252,12 @@
</div>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
import WaveLineChart from './charts/WaveLineChart.vue'
import { MapApi } from "@/api/shorepower/map";
import dayjs from 'dayjs';
@ -510,7 +268,8 @@ import { formatTimestamp, parseRangeToTimestamp } from './utils';
interface Props {
realtimeDeviceData: RealtimeDeviceData[];
activeHeadGroup?: number;
handleGoBack?: () => void;
handleGoBack: () => void;
realtimeDeviceDataTime: string;
}
const props = defineProps<Props>()
@ -596,6 +355,66 @@ const pm25Reduction = ref<string>('0')
const noxReduction = ref<string>('0')
const so2Reduction = ref<string>('0')
//
const startTimeDisplay = computed(() => {
const now = new Date();
let startDate: Date;
switch (timeRange.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':
// 2020
startDate = new Date(2020, 0, 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');
const seconds = String(startDate.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
});
//
const selectedDateRangeDisplay = computed(() => {
if (!dateRange.value || dateRange.value.length !== 2) {
return '';
}
const [startDate, endDate] = dateRange.value;
//
switch (historyTimeGranularity.value) {
case 'year':
// : YYYY
return `${startDate} - ${endDate}`;
case 'month':
// : YYYY-MM
return `${startDate} - ${endDate}`;
default:
// : YYYY-MM-DD
return `${startDate} - ${endDate}`;
}
});
const displayMode = ref<'average' | 'magnify'>('magnify')
const selectedCard = ref<string>('overview')
//
@ -1126,9 +945,18 @@ watch(() => props.realtimeDeviceData, (newValue) => {
})
//
//
let cardSelectedHandler: any = null
onMounted(() => {
// handleGetAllDeviceDataByTimeRange()
handleGetRealTimeAllData(props.realtimeDeviceData)
//
cardSelectedHandler = (event: CustomEvent) => {
selectedCard.value = event.detail
}
window.addEventListener('cardSelected', cardSelectedHandler)
})
// activeHeadGroup resize
@ -1143,7 +971,9 @@ watch(() => props.activeHeadGroup, (newVal) => {
//
onBeforeUnmount(() => {
if (cardSelectedHandler) {
window.removeEventListener('cardSelected', cardSelectedHandler)
}
})
</script>
@ -1220,6 +1050,7 @@ onBeforeUnmount(() => {
.time-range-selector {
display: flex;
align-items: center;
gap: 8px;
}
@ -1268,6 +1099,15 @@ onBeforeUnmount(() => {
color: #fff;
}
.time-range-item {
font-size: 16px;
font-weight: 600;
font-weight: bold;
color: rgba(255, 255, 255, 0.7);
}
.right {
/* gap: 0 */
}

608
public/map/components/ShowData.vue

@ -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) // 202011
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) // 202011
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) // 202011
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))
// 000
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)
// 使nextTickselectedCard
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>

2
public/map/components/bk/map_index.vue

@ -366,7 +366,7 @@ const buildDlData = () => {
}
const updateDataDate = (number: number) => {
if (number == 5) {
window.open("http://106.118.88.15:801/login", '_blank');
window.open("http://server.ayaojies.com.cn:801/login", '_blank');
} else {
dataDate.value = number
usedDl.value = 835229.9 * number

25
public/map/components/cesiumMap.vue

@ -168,12 +168,12 @@ const dataWithModels = ref([]); // 存储带有模型实例的数据列表
//
const presetViews = {
overview: {
"longitude": 118.5132903711312,
"latitude": 38.84648563549342,
"height": 12675.768680075478,
"heading": 353.6789759103708,
"pitch": -36.657258995301596,
"roll": 0.0343469697692715
"longitude": 118.59194426567525,
"latitude": 38.74592604188135,
"height": 18601.357067923822,
"heading": 354.44934020869243,
"pitch": -33.90715396773357,
"roll": 359.986620026677
}
};
@ -705,19 +705,22 @@ onMounted(async () => {
},
});
console.log(item.name)
if (['国投-206泊位-高压', '国投-207泊位-高压', '国投-208泊位-高压'].includes(item.name)) {
console.log('111111111')
if (['国投-206#泊位-高压', '国投-207#泊位-高压', '国投-208#泊位-高压'].includes(item.name)) {
console.log('2222222222')
arr.arr1.push(xelectricalBoxModel)
}
if (['国投-209泊位-高压', '国投-210泊位-高压'].includes(item.name)) {
if (['国投-209#泊位-高压', '国投-210#泊位-高压'].includes(item.name)) {
arr.arr2.push(xelectricalBoxModel)
}
if (['国投-203泊位-高压', '国投-204泊位-高压', '国投-205泊位-高压'].includes(item.name)) {
if (['国投-203#泊位-高压', '国投-204#泊位-高压', '国投-205#泊位-高压'].includes(item.name)) {
arr.arr3.push(xelectricalBoxModel)
}
if (['华电-806泊位-高压', '华电-807泊位-高压'].includes(item.name)) {
if (['华电-806#泊位-高压', '华电-807#泊位-高压'].includes(item.name)) {
arr.arr4.push(xelectricalBoxModel)
}
if (['华电-808泊位-高压', '华电-809泊位-高压', '华电-810泊位-高压'].includes(item.name)) {
if (['华电-808#泊位-高压', '华电-809#泊位-高压', '华电-810#泊位-高压'].includes(item.name)) {
arr.arr5.push(xelectricalBoxModel)
}

356
public/map/index.vue

@ -8,16 +8,17 @@
</div>
</div>
<div class="head">
<div class="head-title">
<span>曹妃甸港区船舶岸电监管平台</span>
<div class="head-group">
<div class="head-group-item" size="small" type="success" @click="handleOverviewClick"
style="margin-right: 10px;">
概览视角</div>
</div>
<div class="head-group">
<div class="head-group-item" size="small" type="success" @click="handleOverviewClick"
style="margin-right: 10px;">
概览视角</div>
</div>
<div class="head-title">
<span @click="handleClickTitle">曹妃甸港区船舶岸电监管平台</span>
<div class="head-time"></div>
</div>
<!-- <div class="head-time"></div> -->
</div>
<!-- 港区概览 -->
@ -27,12 +28,16 @@
:realtime-device-data="realtimeDeviceData" :shore-power-list="ShorePowerList" :ship-data-list="shipDataList" /> -->
<!-- </template> -->
<ShowData v-show="activeHeadGroup === 0" :handleGoToModule="handleGoToModule"
:realtime-device-data-time="realtimeDeviceDataTime" />
:realtime-device-data-time="realtimeDeviceDataTime" :chart-data="chartData"
:company-shore-power-build-data="companyShorePowerBuildData" :ship-total-data="shipTotalData"
: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="realtimeDeviceData"
:active-head-group="activeHeadGroup" v-if="dataLoad" :handleGoBack="handleGoBack" />
<ShorePowerUsage v-show="activeHeadGroup === 1" :realtime-device-data-time="realtimeDeviceDataTime"
:realtime-device-data="realtimeDeviceData" :active-head-group="activeHeadGroup" v-if="dataLoad"
:handleGoBack="handleGoBack" />
<!-- </template> -->
<!-- 港口企业岸电使用 -->
@ -44,9 +49,9 @@
<!-- 船舶岸电使用情况 -->
<!-- <template > -->
<ShipShorePower v-show="activeHeadGroup === 3" :ship-data="shipDataList" :selected-ship="selectedShip"
@item-click="handleSwitch" :realtime-device-data="realtimeDeviceData" v-if="dataLoad"
:handleGoBack="handleGoBack" />
<ShipShorePower v-show="activeHeadGroup === 3" :ship-total-data="shipTotalData" :ship-data="shipDataList"
:selected-ship="selectedShip" @item-click="handleSwitch" :realtime-device-data="realtimeDeviceData"
v-if="dataLoad" :handleGoBack="handleGoBack" :realtime-device-data-time="realtimeDeviceDataTime" />
<!-- </template> -->
<template v-if="activeHeadGroup === 4">
@ -244,11 +249,20 @@ import ShipShorePower from './components/ShipShorePower.vue'
import dayjs from 'dayjs'
import { onMounted, onUnmounted, ref, computed, watch } from 'vue'
import { MapApi } from "@/api/shorepower/map";
import { RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'
import { CompanyShorePowerBuildDataItem, RealtimeDeviceData, ShipBasicInfoRespVO, ShipRespVo, ShorePowerBerth } from '@/types/shorepower'
import { BERTH_TYPE, DOCK_DISTRICT, getOperationTypeLabel, HARBOR_DISTRICT, SHORE_POWER_STATUS, UNUSED_SHORE_POWER_REASON } from './components/dictionaryTable'
import { formatDuration, formatTimestamp, getValueById, showStatus } from './components/utils'
defineOptions({ name: 'PublicMap' })
let getRealtimeIntervalId: NodeJS.Timeout | null = null
interface ChartDataItem {
name: string;
value: number;
}
const headGroupList = ref<{ value: number, label: string }[]>([
{ value: 0, label: '总体概览' },
{ value: 1, label: '港口岸电使用情况' },
@ -293,6 +307,11 @@ const realtimeDeviceDataTime = ref<string>('')
const ShorePowerList = ref<(ShorePowerBerth & { position: string; })[]>([])
const shipDataList = ref<ShipRespVo[]>([])
const dataLoad = ref<boolean>(false)
const dockList = ref<{ id: number, name: string }[]>([])
const berthList = ref<{ id: number, name: string, dockId: number }[]>([])
const yearDataRes = ref<any>({})
const monthDataRes = ref<any>({})
const dayDataRes = ref<any>({})
@ -303,6 +322,61 @@ let scrollTimeout: NodeJS.Timeout | null = null
let userScrollHandler: ((event: Event) => void) | null = null
const scrollSpeed = ref<number>(20) //
//
/* const ChartData = ref<ChartDataItem[]>({
}) */
/* const totalPowerChartData = ref<ChartDataItem[]>([])
const co2ReductionChartData = ref<ChartDataItem[]>([])
const so2ReductionChartData = ref<ChartDataItem[]>([])
const noxReductionChartData = ref<ChartDataItem[]>([])
const pm25ReductionChartData = ref<ChartDataItem[]>([])
const fuelReductionChartData = ref<ChartDataItem[]>([])
*/
const chartData = ref({
'realtime': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'day': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'month': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
'year': {
'totalPower': 0,
'fuel': 0,
'co2': 0,
'pm25': 0,
'nox': 0,
'so2': 0,
},
})
const companyShorePowerBuildData = ref<CompanyShorePowerBuildDataItem | null>(null)
const shipTotalData = ref<any>({
'berthingShips': 0,
'shorePowerShips': 0,
'noShorePowerShips': 0,
})
//
const updateTime = () => {
currentTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
@ -345,7 +419,7 @@ const handleElectricalBoxClick = (data) => {
//
const closeElectricalBoxInfo = () => {
activeHeadGroup.value = -1;
activeHeadGroup.value = 0;
selectedElectricalBox.value = {
type: '',
data: null
@ -504,6 +578,7 @@ const handleGetRealtimeAllData = async () => {
realtimeDeviceData.value = arrayOfObjects
//
realtimeDeviceDataTime.value = dayjs().format('YYYY-MM-DD HH:mm:ss')
return arrayOfObjects
// console.log('getRealtimeAllData', arrayOfObjects)
}
@ -582,17 +657,39 @@ const handleGetShipData = async () => {
shipStatus: getShipStatus(item),
}))
shipDataList.value = buildData
return buildData
}
//
onMounted(async () => {
dockList.value = await MapApi.getDockIdAndNameList()
berthList.value = await MapApi.getBerthIdAndNameList()
// DOM
await handleGetRealtimeAllData()
const firstData = await handleGetRealtimeAllData()
const res: RealtimeDeviceData[] = firstData.filter(item => item.deviceCode.includes('Kwh'));
const ids = res.map(item => item.id);
const params = {
ids: ids.join(','),
// year: new Date().getFullYear()
}
yearDataRes.value = await MapApi.getYearDataByIdList(params);
monthDataRes.value = await MapApi.getMonthDataByIdList(params);
dayDataRes.value = await MapApi.getDayDataByIdList(params);
handleGetlRangeData(firstData)
handleBuildCompanyShorePower(firstData)
await handleGetStorePower()
await handleGetShipData()
const tempBuildShipData = await handleGetShipData()
handleBuildCompanyShorePowerYear(tempBuildShipData)
dataLoad.value = true
getRealtimeIntervalId = setInterval(() => {
handleGetRealtimeAllData()
getRealtimeIntervalId = setInterval(async () => {
const data = await handleGetRealtimeAllData()
if (data) {
handleGetlRangeData(data)
handleBuildCompanyShorePower(data)
handleBuildCompanyShorePowerYear(tempBuildShipData)
}
}, 5000)
})
@ -678,6 +775,16 @@ const handleGoBack = () => {
activeHeadGroup.value = 0
}
const handleClickTitle = () => {
if (activeHeadGroup.value === 0) {
activeHeadGroup.value = -1
return
}
activeHeadGroup.value = 0
}
//
const shipStatusData = computed(() => {
if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) {
@ -712,6 +819,211 @@ const shipStatusData = computed(() => {
})
})
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()
}
if (!params.ids) return;
let yearDataRes: RealtimeDeviceData[] = []
let monthDataRes: RealtimeDeviceData[] = []
let dayDataRes: RealtimeDeviceData[] = []
try {
yearDataRes = await MapApi.getYearDataByIdList(params);
monthDataRes = await MapApi.getMonthDataByIdList(params);
dayDataRes = await MapApi.getDayDataByIdList(params);
} catch (error) {
console.log(error);
}
// const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params);
const realTimeSum = res.reduce((acc, item) => acc + item.measureValue, 0);
const daySum = realTimeSum - Object.values(dayDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const monthSum = realTimeSum - Object.values(monthDataRes).reduce((acc, item) => acc + item.measureValue, 0);
// const quarterSum = Object.values(quarterDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const yearSum = realTimeSum - Object.values(yearDataRes).reduce((acc, item) => acc + item.measureValue, 0);
// totalPower.value = realTimeSum.toFixed(2)
// co2Reduction.value = (realTimeSum * 670 / 1000000).toFixed(2) //
// so2Reduction.value = (realTimeSum * 10.5 / 1000000).toFixed(2) //
// noxReduction.value = (realTimeSum * 18.1 / 1000000).toFixed(2) //
// pm25Reduction.value = (realTimeSum * 1.46 / 1000000).toFixed(2) //
// fuelReduction.value = (realTimeSum * 0.22 / 1000).toFixed(2) //
const incrementRealTimeSum = Object.values(res).reduce((acc, item) => acc + item.measureValue, 0);
chartData.value.realtime = {
totalPower: Number(realTimeSum.toFixed(2)),
fuel: Number((realTimeSum * 0.22 / 1).toFixed(2)), //
co2: Number((realTimeSum * 670 / 1000).toFixed(2)), //
pm25: Number((realTimeSum * 1.46 / 1000).toFixed(2)), //
nox: Number((realTimeSum * 18.1 / 1000).toFixed(2)), //
so2: Number((realTimeSum * 10.5 / 1000).toFixed(2)), //
}
chartData.value.day = {
totalPower: Number(daySum.toFixed(2)),
fuel: Number((daySum * 0.22 / 1).toFixed(2)), //
co2: Number((daySum * 670 / 1000).toFixed(2)), //
pm25: Number((daySum * 1.46 / 1000).toFixed(2)), //
nox: Number((daySum * 18.1 / 1000).toFixed(2)), //
so2: Number((daySum * 10.5 / 1000).toFixed(2)), //
}
chartData.value.month = {
totalPower: Number(monthSum.toFixed(2)),
fuel: Number((monthSum * 0.22 / 1).toFixed(2)), //
co2: Number((monthSum * 670 / 1000).toFixed(2)), //
pm25: Number((monthSum * 1.46 / 1000).toFixed(2)), //
nox: Number((monthSum * 18.1 / 1000).toFixed(2)), //
so2: Number((monthSum * 10.5 / 1000).toFixed(2)), //
}
chartData.value.year = {
totalPower: Number(yearSum.toFixed(2)),
fuel: Number((yearSum * 0.22 / 1).toFixed(2)), //
co2: Number((yearSum * 670 / 1000).toFixed(2)), //
pm25: Number((yearSum * 1.46 / 1000).toFixed(2)), //
nox: Number((yearSum * 18.1 / 1000).toFixed(2)), //
so2: Number((yearSum * 10.5 / 1000).toFixed(2)), //
}
//
const now = new Date()
/* const timestamp = now.toLocaleTimeString()
//
const totalPowerValue = Number(incrementRealTimeSum.toFixed(2))
const co2Value = Number((incrementRealTimeSum * 670 / 1000).toFixed(2)) //
const so2Value = Number((incrementRealTimeSum * 10.5 / 1000).toFixed(2)) //
const noxValue = Number((incrementRealTimeSum * 18.1 / 1000).toFixed(2)) //
const pm25Value = Number((incrementRealTimeSum * 1.46 / 1000).toFixed(2)) //
const fuelValue = Number((incrementRealTimeSum * 0.22 / 1).toFixed(2)) // 转化为千克 */
//
/* totalPowerChartData.value.push({ name: timestamp, value: totalPowerValue })
co2ReductionChartData.value.push({ name: timestamp, value: co2Value })
so2ReductionChartData.value.push({ name: timestamp, value: so2Value })
noxReductionChartData.value.push({ name: timestamp, value: noxValue })
pm25ReductionChartData.value.push({ name: timestamp, value: pm25Value })
fuelReductionChartData.value.push({ name: timestamp, value: fuelValue })
// 20310
const maxDataPoints = 10
totalPowerChartData.value = totalPowerChartData.value.slice(-maxDataPoints)
co2ReductionChartData.value = co2ReductionChartData.value.slice(-maxDataPoints)
so2ReductionChartData.value = so2ReductionChartData.value.slice(-maxDataPoints)
noxReductionChartData.value = noxReductionChartData.value.slice(-maxDataPoints)
pm25ReductionChartData.value = pm25ReductionChartData.value.slice(-maxDataPoints)
fuelReductionChartData.value = fuelReductionChartData.value.slice(-maxDataPoints) */
} catch (error) {
console.error(error)
}
}
const handleBuildCompanyShorePower = (realtimeDeviceData: RealtimeDeviceData[]) => {
const realtimeBuildData = dockList.value.map(dock => {
const children = berthList.value
.filter(berth => berth.dockId === dock.id)
.map(berth => ({
...berth,
...realtimeDeviceData.find(item => (item.deviceId === berth.id) && (item.deviceCode.includes('Kwh')))
}));
// childrenmeasureValue
const totalPower = children.reduce((sum, child) => {
return sum + (child.measureValue || 0);
}, 0);
return {
...dock,
children,
totalPower
};
});
// console.log(dayDataRes.value)
const dayBuildData = dockList.value.map(dock => {
const children = berthList.value
.filter(berth => berth.dockId === dock.id)
.map(berth => ({
...berth,
...dayDataRes.value.find(item => (item.deviceId === berth.id))
}));
// childrenmeasureValue
const totalPower = children.reduce((sum, child) => {
return sum + (child.measureValue || 0);
}, 0);
return {
...dock,
children,
totalPower
};
});
const monthBuildData = dockList.value.map(dock => {
const children = berthList.value
.filter(berth => berth.dockId === dock.id)
.map(berth => ({
...berth,
...monthDataRes.value.find(item => (item.deviceId === berth.id))
}));
// childrenmeasureValue
const totalPower = children.reduce((sum, child) => {
return sum + (child.measureValue || 0);
}, 0);
return {
...dock,
children,
totalPower
};
});
const yearBuildData = dockList.value.map(dock => {
const children = berthList.value
.filter(berth => berth.dockId === dock.id)
.map(berth => ({
...berth,
...yearDataRes.value.find(item => (item.deviceId === berth.id))
}));
// childrenmeasureValue
const totalPower = children.reduce((sum, child) => {
return sum + (child.measureValue || 0);
}, 0);
return {
...dock,
children,
totalPower
};
});
companyShorePowerBuildData.value = {
realtime: realtimeBuildData,
year: yearBuildData,
month: monthBuildData,
day: dayBuildData,
}
// console.log(buildData);
}
const handleBuildCompanyShorePowerYear = (shipData: any) => {
shipTotalData.value = {
'berthingShips': shipData.length,
'shorePowerShips': shipData.filter(ship => ['正常', '超容'].includes(ship.shipStatus || '')).length,
'noShorePowerShips': shipData.filter(ship => !['正常', '超容'].includes(ship.shipStatus || '')).length,
}
}
</script>
<style lang="scss">
.cesium-viewer-bottom {
@ -942,8 +1254,8 @@ const shipStatusData = computed(() => {
height: 72px;
top: 0px;
right: 0px;
padding: 12px 24px;
padding-right: 900px;
.head-title {
// width: 420px;
@ -955,8 +1267,10 @@ const shipStatusData = computed(() => {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.head-group {
display: flex;
flex-wrap: wrap;

15
src/types/shorepower.d.ts

@ -478,3 +478,18 @@ interface RealtimeDeviceData {
measureTime: number // 时间戳(毫秒)
measureValue: number
}
type TimeRange = 'year' | 'month' | 'day' | 'realtime'
type CompanyShorePowerBuildDataItem = {
[key in TimeRange]: {
name: string
value: number
harborDistrictId: number
totalPower: number
children: {
name: string
value: number
}[]
}
}

Loading…
Cancel
Save