jiangAB 4 weeks ago
parent
commit
52fd680c7d
  1. 132
      public/map/components/CompanyShorePower.vue
  2. 174
      public/map/components/HistoryRecordDialog.vue
  3. 402
      public/map/components/PortOverview.vue
  4. 303
      public/map/components/ShipHistoryDialog.vue
  5. 1282
      public/map/components/ShipShorePower.vue
  6. 304
      public/map/components/ShorePowerHistoryDialog.vue
  7. 570
      public/map/components/ShorePowerUsage.vue
  8. 141
      public/map/components/cesiumMap.vue
  9. 18
      public/map/components/charts/BarChartMinute.vue
  10. 240
      public/map/components/charts/WaveLineChart.vue
  11. 67
      public/map/index.vue
  12. 83
      src/api/shorepower/map/index.ts
  13. 4
      src/plugins/elementPlus/index.ts

132
public/map/components/CompanyShorePower.vue

@ -1,31 +1,29 @@
<template> <template>
<div class="company-shore-power"> <div class="company-shore-power">
<div class="left" style="width: 40%;"> <div class="left" style="width: 40%;">
<!-- <div class="card digital-twin-card--deep-blue "> <!-- 码头信息卡片 -->
<div class="card digital-twin-card--deep-blue">
<div class="card-title"> <div class="card-title">
<div class="vertical-line"></div> <div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" /> <img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">港口企业岸电使用</span> <span class="title-text">码头与泊位信息</span>
</div> </div>
<div class="card-content"> <div class="card-content">
<BarChart :chart-data="companyComparisonData" title="企业岸电使用对比" color="#4CAF50" /> <div v-for="company in companyComparisonData" :key="company.name" class="company-section">
</div> <!-- 码头名称和总量 -->
</div> --> <div class="company-header">
<div class="card digital-twin-card--deep-blue "> <div class="company-name">{{ company.name }}</div>
<div class="card-title"> <div class="company-total">总量: {{ company.value }}</div>
<div class="vertical-line"></div> </div>
<img src="@/assets/svgs/data.svg" class="title-icon" /> <!-- 泊位详情 -->
<span class="title-text">码头详情</span> <div class="overview-grid">
</div> <div v-for="(berth, index) in (company.children || [])" :key="index" class="overview-item">
<div class="card-content"> <div class="overview-value">{{ berth.value }}</div>
<!-- 下拉框和饼图组件 -->
<div class="company-selector"> <div class="overview-label">{{ berth.name }}</div>
<el-select v-model="selectedCompany" placeholder="请选择公司" @change="handleCompanyChange"
style="width: 100%; margin-bottom: 20px;"> </div>
<el-option v-for="item in companyComparisonData" :key="item.name" :label="item.name" :value="item.name" /> </div>
</el-select>
<PieChart :chart-data="pieChartData" :title="`${selectedCompany}子项占比`" />
</div> </div>
</div> </div>
</div> </div>
@ -34,8 +32,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref } from 'vue'
import PieChart from './charts/PieChart.vue'
// //
interface ChartDataItem { interface ChartDataItem {
@ -49,21 +46,6 @@ interface Props {
} }
const props = defineProps<Props>() const props = defineProps<Props>()
//
const selectedCompany = ref<string>(props.companyComparisonData[0]?.name || '')
//
const pieChartData = computed(() => {
//
const selectedCompanyData = props.companyComparisonData.find(item => item.name === selectedCompany.value)
return selectedCompanyData?.children || []
})
//
const handleCompanyChange = (company: string) => {
selectedCompany.value = company
}
</script> </script>
<style scoped> <style scoped>
@ -77,6 +59,7 @@ const handleCompanyChange = (company: string) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
gap: 10px;
} }
.card { .card {
@ -89,12 +72,83 @@ const handleCompanyChange = (company: string) => {
flex: 1; flex: 1;
height: 100%; height: 100%;
min-height: 0; min-height: 0;
overflow-y: auto;
} }
.company-selector { /* 总览网格样式 */
width: 100%; .overview-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 8px;
padding: 4px;
height: 100%; height: 100%;
}
.overview-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
justify-content: center;
padding: 4px;
background-color: rgba(0, 0, 0, 0.2);
border-radius: 6px;
}
.overview-label {
text-align: center;
font-size: 24px;
color: #ccc;
margin-bottom: 2px;
}
.overview-value {
font-size: 36px;
font-weight: bold;
color: #1296db;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
margin-bottom: 2px;
}
.overview-text {
font-size: 10px;
color: #999;
text-align: center;
}
/* 企业区块样式 */
.company-section {
margin-bottom: 16px;
padding-bottom: 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.company-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
/* 企业头部样式 */
.company-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
padding: 0 4px;
}
.company-name {
font-size: 24px;
font-weight: bold;
color: #fff;
}
.company-total {
font-size: 24px;
font-weight: bold;
color: #1296db;
background-color: rgba(18, 150, 219, 0.1);
padding: 2px 8px;
border-radius: 4px;
} }
</style> </style>

174
public/map/components/HistoryRecordDialog.vue

@ -0,0 +1,174 @@
<template>
<el-dialog v-model="visible" :title="title" width="1200px" :before-close="handleClose">
<!-- 搜索和筛选区域 -->
<div class="filter-container">
<el-form :model="queryParams" label-width="80px" inline>
<el-form-item label="关键字">
<el-input v-model="queryParams.keyword" placeholder="请输入关键字搜索" clearable @keyup.enter="handleSearch" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleDateChange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe style="width: 100%" v-loading="loading">
<el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:width="column.width" :formatter="column.formatter" />
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination v-model:current-page="queryParams.pageNo" v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { ElDialog, ElForm, ElFormItem, ElInput, ElDatePicker, ElButton, ElTable, ElTableColumn, ElPagination } from 'element-plus'
//
interface Props {
modelValue: boolean
title?: string
columns: Array<{
prop: string
label: string
width?: string | number
formatter?: (row: any, column: any, cellValue: any) => string
}>
data: any[]
}
//
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'search', params: any): void
}>()
//
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
title: '历史记录',
columns: () => [],
data: () => []
})
//
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const queryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const dateRange = ref<[string, string]>(['', ''])
const loading = ref(false)
const total = ref(0)
//
const tableData = computed(() => {
//
// 使
return props.data.slice(
(queryParams.value.pageNo - 1) * queryParams.value.pageSize,
queryParams.value.pageNo * queryParams.value.pageSize
)
})
//
const tableColumns = computed(() => props.columns)
//
const handleClose = () => {
visible.value = false
}
//
const handleSearch = () => {
//
emit('search', { ...queryParams.value })
}
//
const resetQuery = () => {
queryParams.value.keyword = ''
queryParams.value.startTime = ''
queryParams.value.endTime = ''
dateRange.value = ['', '']
queryParams.value.pageNo = 1
handleSearch()
}
//
const handleDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
queryParams.value.startTime = val[0]
queryParams.value.endTime = val[1]
} else {
queryParams.value.startTime = ''
queryParams.value.endTime = ''
}
}
//
const handleSizeChange = (val: number) => {
queryParams.value.pageSize = val
queryParams.value.pageNo = 1
handleSearch()
}
//
const handleCurrentChange = (val: number) => {
queryParams.value.pageNo = val
handleSearch()
}
//
watch(
() => props.data,
(newData) => {
total.value = newData.length
},
{ immediate: true }
)
</script>
<style scoped>
.filter-container {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

402
public/map/components/PortOverview.vue

@ -1,39 +1,89 @@
<template> <template>
<div class="port-overview"> <div class="port-overview">
<div class="left" style="width: 500px;"> <div class="left" style="width:45%;">
<div class="card digital-twin-card--deep-blue "> <div style="height: 100%; width: 100%; display: flex; gap: 8px; ">
<div style="display: flex; align-items: center; justify-content: space-between;"> <div class="card digital-twin-card--deep-blue ">
<div class="card-title"> <div style="display: flex; align-items: center; justify-content: space-between;">
<div class="vertical-line"></div> <div class="card-title">
<img src="@/assets/svgs/data.svg" class="title-icon" /> <div class="vertical-line"></div>
<span class="title-text">船舶状态</span> <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="handleSearch" />
</div> </div>
<input class="search-container" type="text" placeholder="搜索船舶" v-model="searchKeyword"
@input="handleSearch" />
</div>
<div class=" card-content"> <div class=" card-content">
<div class="ship-table"> <div class="ship-table">
<div class="ship-table-header"> <div class="ship-table-header">
<div class="ship-table-column ship-name-header">轮船名称</div> <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-column ship-status-header">历史</div>
<div class="ship-table-body"> </div>
<div v-for="ship in filteredShipStatusData" :key="ship.id" class="ship-table-row" <div class="ship-table-body">
@click="handleSwitch(ship)"> <div v-for="shorepower in filteredShorePowerStatusData" :key="shorepower.id" class="ship-table-row"
<div class="ship-table-column ship-name"> @click="handleSwitch(shorepower, 'shorepower')">
<div class="ship-icon">🚢</div> <div class="ship-table-column ship-name">
<span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span> <div class="ship-icon"></div>
<span class="ship-name-text">{{ shorepower.shorePowerEquipment.name }}</span>
</div>
<div class="ship-table-column ship-status">
<div class="status-tag" :class="getStatusClass('正常')">
正常
</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 class="ship-table-column ship-status"> </div>
<div class="status-tag" :class="getStatusClass('正常')"> </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="searchKeyword"
@input="handleSearch" />
</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="handleSwitch(ship, 'ship')">
<div class="ship-table-column ship-name">
<div class="ship-icon">🚢</div>
<span class="ship-name-text">{{ ship.shipBasicInfo.name }}</span>
</div> </div>
<div class="status-tag" :class="getStatusClass('空闲')"> <div class="ship-table-column ship-status">
空闲 <div class="status-tag" :class="getStatusClass('正常')">
正常
</div>
<!-- <div class="status-tag" :class="getStatusClass('空闲')">
空闲
</div>
<div class="status-tag" :class="getStatusClass('故障')">
故障
</div> -->
</div> </div>
<div class="status-tag" :class="getStatusClass('故障')"> <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>
@ -42,8 +92,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="right" style="width: 1000px;"> <div class="right" :style="`width: ${show_type === 'ship' ? '45%' : '20%'}`">
<div v-if="selectedShip" class="right-two-row"> <div v-if="selectedItem && show_type === 'ship'" class="right-two-row">
<div class="card digital-twin-card--deep-blue"> <div class="card digital-twin-card--deep-blue">
<div class="card-title"> <div class="card-title">
<div class="vertical-line"></div> <div class="vertical-line"></div>
@ -54,51 +104,51 @@
<div class="ship-detail"> <div class="ship-detail">
<div class="detail-item"> <div class="detail-item">
<span class="label">船名:</span> <span class="label">船名:</span>
<span class="value">{{ selectedShip.shipBasicInfo.name }}</span> <span class="value">{{ selectedItem.shipBasicInfo.name }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">英文船名:</span> <span class="label">英文船名:</span>
<span class="value">{{ selectedShip.shipBasicInfo.nameEn }}</span> <span class="value">{{ selectedItem.shipBasicInfo.nameEn }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">船舶呼号:</span> <span class="label">船舶呼号:</span>
<span class="value">{{ selectedShip.shipBasicInfo.callSign }}</span> <span class="value">{{ selectedItem.shipBasicInfo.callSign }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">船长:</span> <span class="label">船长:</span>
<span class="value">{{ selectedShip.shipBasicInfo.length }} </span> <span class="value">{{ selectedItem.shipBasicInfo.length }} </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">船宽:</span> <span class="label">船宽:</span>
<span class="value">{{ selectedShip.shipBasicInfo.width }} </span> <span class="value">{{ selectedItem.shipBasicInfo.width }} </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">满载吃水深度:</span> <span class="label">满载吃水深度:</span>
<span class="value">{{ selectedShip.shipBasicInfo.fullLoadDraft }} </span> <span class="value">{{ selectedItem.shipBasicInfo.fullLoadDraft }} </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">吨位:</span> <span class="label">吨位:</span>
<span class="value">{{ selectedShip.shipBasicInfo.tonnage }} </span> <span class="value">{{ selectedItem.shipBasicInfo.tonnage }} </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">航运单位:</span> <span class="label">航运单位:</span>
<span class="value">{{ selectedShip.shipBasicInfo.shippingCompany }}</span> <span class="value">{{ selectedItem.shipBasicInfo.shippingCompany }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">岸电联系人:</span> <span class="label">岸电联系人:</span>
<span class="value">{{ selectedShip.shipBasicInfo.shorePowerContact }}</span> <span class="value">{{ selectedItem.shipBasicInfo.shorePowerContact }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">联系方式:</span> <span class="label">联系方式:</span>
<span class="value">{{ selectedShip.shipBasicInfo.shorePowerContactPhone }}</span> <span class="value">{{ selectedItem.shipBasicInfo.shorePowerContactPhone }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">船检登记号:</span> <span class="label">船检登记号:</span>
<span class="value">{{ selectedShip.shipBasicInfo.inspectionNo }}</span> <span class="value">{{ selectedItem.shipBasicInfo.inspectionNo }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">创建时间:</span> <span class="label">创建时间:</span>
<span class="value">{{ new Date(selectedShip.shipBasicInfo.createTime).toLocaleString() }}</span> <span class="value">{{ new Date(selectedItem.shipBasicInfo.createTime).toLocaleString() }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -113,42 +163,128 @@
<div class="ship-detail"> <div class="ship-detail">
<div class="detail-item"> <div class="detail-item">
<span class="label">停泊状态:</span> <span class="label">停泊状态:</span>
<span class="value">{{ selectedShip.shorePowerAndShip.type === 'left' ? '左舷停泊' : <span class="value">{{ selectedItem.shorePowerAndShip.type === 'left' ? '左舷停泊' :
selectedShip.shorePowerAndShip.type === 'right' ? '右舷停泊' : selectedShip.shorePowerAndShip.type selectedItem.shorePowerAndShip.type === 'right' ? '右舷停泊' : selectedItem.shorePowerAndShip.type
}}</span> }}</span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="label">靠泊状态:</span> <span class="label">靠泊状态:</span>
<span class="value">{{ getShorePowerStatusText(selectedShip.shorePowerAndShip.status) }}</span> <span class="value">{{ getShorePowerStatusText(selectedItem.shorePowerAndShip.status) }}</span>
</div>
</div>
</div>
</div>
</div>
<div v-else-if="selectedItem && show_type === 'shorepower'" class="right-two-row">
<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-detail">
<div class="detail-item">
<span class="label">名称:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.name }}</span>
</div>
<div class="detail-item">
<span class="label">位置:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.position }}</span>
</div>
<div class="detail-item">
<span class="label">电压:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.voltage }}</span>
</div>
<div class="detail-item">
<span class="label">频率:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.frequency }} </span>
</div>
<div class="detail-item">
<span class="label">容量:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.capacity }}</span>
</div>
<div class="detail-item">
<span class="label">当前电压:</span>
<span class="value"></span>
</div>
<div class="detail-item">
<span class="label">当前电流:</span>
<span class="value"></span>
</div>
<div class="detail-item">
<span class="label">当前频率:</span>
<span class="value"></span>
</div>
<div class="detail-item">
<span class="label">当前总用量:</span>
<span class="value">{{ selectedItem.shorePowerEquipment.measureValue }}</span>
</div> </div>
</div> </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">
<div class="ship-detail">
<div class="detail-item">
<span class="label">停泊状态:</span>
<span class="value">{{ selectedItem.shorePowerAndShip.type === 'left' ? '左舷停泊' :
selectedItem.shorePowerAndShip.type === 'right' ? '右舷停泊' : selectedItem.shorePowerAndShip.type
}}</span>
</div>
<div class="detail-item">
<span class="label">靠泊状态:</span>
<span class="value">{{ getShorePowerStatusText(selectedItem.shorePowerAndShip.status) }}</span>
</div>
</div>
</div>
</div> -->
</div> </div>
<div v-else class="card digital-twin-card--deep-blue" style="flex: 1;"> <div v-else class="card digital-twin-card--deep-blue" style="flex: 1;">
<div class="card-title"> <div class="card-title">
<div class="vertical-line"></div> <div class="vertical-line"></div>
<img src="@/assets/svgs/data.svg" class="title-icon" /> <img src="@/assets/svgs/data.svg" class="title-icon" />
<span class="title-text">船舶详情</span> <span class="title-text">设备详情</span>
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="no-selection">请选择一艘船舶以查看详细信息</div> <div class="no-selection">请选择设备以查看详细信息</div>
</div> </div>
</div> </div>
</div> </div>
<!-- 岸电箱历史记录弹窗 -->
<ShorePowerHistoryDialog v-model="shorePowerHistoryVisible" :data="shorePowerHistoryData"
:ship-connection-data="shorePowerConnectionData" @search="handleShorePowerHistorySearch"
@ship-connection-search="handleShorePowerConnectionSearch" />
<!-- 船舶历史记录弹窗 -->
<ShipHistoryDialog v-model="shipHistoryVisible" :berthing-data="shipHistoryData"
:shore-power-connection-data="shipShorePowerConnectionData" @berthing-search="handleShipHistorySearch"
@shore-power-connection-search="handleShipShorePowerConnectionSearch" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import ShorePowerHistoryDialog from './ShorePowerHistoryDialog.vue'
import ShipHistoryDialog from './ShipHistoryDialog.vue'
import { MapApi } from "@/api/shorepower/map";
// //
interface ShipItem { interface ShipItem {
id: string; id: string;
shipBasicInfo: { shipBasicInfo: {
name: string; name: string;
}; };
shorePowerEquipment: {
name: string;
measureValue: string;
};
shorePowerAndShip?: { shorePowerAndShip?: {
status?: string; status?: string;
powerUsage?: number; powerUsage?: number;
@ -158,14 +294,44 @@ interface ShipItem {
interface Props { interface Props {
shipStatusData: ShipItem[]; shipStatusData: ShipItem[];
selectedShip: ShipItem | null; shorePowerStatusData: ShipItem[];
// selectedItem: ShipItem | null;
} }
const props = defineProps<Props>() const props = defineProps<Props>()
// //
const searchKeyword = ref('') const searchKeyword = ref('')
const storeSearchKeyword = ref('')
const show_type = ref('ship')
const selectedItem = ref<ShipItem | null>(null)
//
const shorePowerHistoryVisible = ref(false)
const shipHistoryVisible = ref(false)
const currentShorePower = ref<ShipItem | null>(null)
const currentShip = ref<ShipItem | null>(null)
//
const shorePowerHistoryColumns = [
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'time', label: '时间', width: 180 },
{ prop: 'operation', label: '操作类型' },
{ prop: 'voltage', label: '电压(V)' },
{ prop: 'current', label: '电流(A)' },
{ prop: 'power', label: '功率(kW)' },
{ prop: 'energy', label: '用电量(kWh)' }
]
//
const shipHistoryColumns = [
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'time', label: '时间', width: 180 },
{ prop: 'operation', label: '操作类型' },
{ prop: 'berthPosition', label: '泊位位置' },
{ prop: 'duration', label: '停泊时长(小时)' },
{ prop: 'powerConsumption', label: '用电量(kWh)' }
]
// //
const filteredShipStatusData = computed(() => { const filteredShipStatusData = computed(() => {
if (!searchKeyword.value) { if (!searchKeyword.value) {
@ -176,22 +342,144 @@ const filteredShipStatusData = computed(() => {
) )
}) })
//
const shorePowerHistoryData = ref<any[]>([
{ id: 1, time: '2023-05-01 08:30:00', operation: '连接', voltage: '380', current: '120', power: '45.6', energy: '12.5' },
{ id: 2, time: '2023-05-01 12:15:00', operation: '运行', voltage: '382', current: '118', power: '45.1', energy: '35.2' },
{ id: 3, time: '2023-05-01 16:45:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' },
{ id: 4, time: '2023-05-02 09:20:00', operation: '连接', voltage: '379', current: '122', power: '46.3', energy: '8.7' },
{ id: 5, time: '2023-05-02 14:30:00', operation: '运行', voltage: '381', current: '119', power: '45.4', energy: '28.9' },
{ id: 6, time: '2023-05-02 18:50:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' },
{ id: 7, time: '2023-05-03 07:45:00', operation: '连接', voltage: '380', current: '121', power: '45.9', energy: '15.3' },
{ id: 8, time: '2023-05-03 13:20:00', operation: '运行', voltage: '382', current: '120', power: '45.8', energy: '42.1' },
{ id: 9, time: '2023-05-03 17:30:00', operation: '断开', voltage: '0', current: '0', power: '0', energy: '0' },
{ id: 10, time: '2023-05-04 10:15:00', operation: '连接', voltage: '378', current: '123', power: '46.5', energy: '9.8' }
])
//
const shipHistoryData = ref<any[]>([
{ id: 1, time: '2023-04-15 09:30:00', operation: '靠泊', berthPosition: '1号泊位', duration: '12.5', powerConsumption: '125.6' },
{ id: 2, time: '2023-04-16 14:20:00', operation: '离泊', berthPosition: '1号泊位', duration: '0', powerConsumption: '0' },
{ id: 3, time: '2023-04-20 08:45:00', operation: '靠泊', berthPosition: '3号泊位', duration: '8.2', powerConsumption: '89.3' },
{ id: 4, time: '2023-04-21 16:30:00', operation: '离泊', berthPosition: '3号泊位', duration: '0', powerConsumption: '0' },
{ id: 5, time: '2023-04-25 11:15:00', operation: '靠泊', berthPosition: '2号泊位', duration: '15.7', powerConsumption: '156.8' },
{ id: 6, time: '2023-04-26 18:40:00', operation: '离泊', berthPosition: '2号泊位', duration: '0', powerConsumption: '0' },
{ id: 7, time: '2023-05-01 07:20:00', operation: '靠泊', berthPosition: '1号泊位', duration: '18.3', powerConsumption: '189.2' },
{ id: 8, time: '2023-05-02 15:50:00', operation: '离泊', berthPosition: '1号泊位', duration: '0', powerConsumption: '0' },
{ id: 9, time: '2023-05-05 09:10:00', operation: '靠泊', berthPosition: '4号泊位', duration: '6.4', powerConsumption: '67.5' },
{ id: 10, time: '2023-05-06 13:45:00', operation: '离泊', berthPosition: '4号泊位', duration: '0', powerConsumption: '0' }
])
//
const shorePowerConnectionData = ref<any[]>([
{ id: 1, shipName: '远航号', time: '2023-05-01 08:30:00', duration: '4.5', powerConsumption: '125.6', status: '已断开' },
{ id: 2, shipName: '海鹰号', time: '2023-05-01 12:15:00', duration: '3.2', powerConsumption: '89.3', status: '已断开' },
{ id: 3, shipName: '巨轮号', time: '2023-05-02 09:20:00', duration: '6.8', powerConsumption: '189.2', status: '连接中' },
{ id: 4, shipName: '远洋号', time: '2023-05-03 07:45:00', duration: '2.1', powerConsumption: '67.5', status: '已断开' },
{ id: 5, shipName: '海豚号', time: '2023-05-04 10:15:00', duration: '5.3', powerConsumption: '156.8', status: '连接中' }
])
//
const shipShorePowerConnectionData = ref<any[]>([
{ id: 1, shorePowerName: '1号岸电箱', time: '2023-05-01 08:30:00', duration: '4.5', powerConsumption: '125.6', status: '已断开' },
{ id: 2, shorePowerName: '2号岸电箱', time: '2023-05-01 12:15:00', duration: '3.2', powerConsumption: '89.3', status: '已断开' },
{ id: 3, shorePowerName: '1号岸电箱', time: '2023-05-02 09:20:00', duration: '6.8', powerConsumption: '189.2', status: '连接中' },
{ id: 4, shorePowerName: '3号岸电箱', time: '2023-05-03 07:45:00', duration: '2.1', powerConsumption: '67.5', status: '已断开' },
{ id: 5, shorePowerName: '2号岸电箱', time: '2023-05-04 10:15:00', duration: '5.3', powerConsumption: '156.8', status: '连接中' }
])
const handleSelectItem = async (item: ShipItem) => {
const deviceId = item.shorePower?.totalPowerDeviceId;
const res = await MapApi.getRealtimeDataByIdList({ ids: deviceId })
console.log(res)
selectedItem.value = {
...item,
shorePowerEquipment: {
...item.shorePowerEquipment,
'measureValue': res[0].measureValue || 'N/A',
}
}
}
const filteredShorePowerStatusData = computed(() => {
if (!storeSearchKeyword.value) {
return props.shipStatusData
}
return props.shipStatusData.filter(ship =>
ship.shorePowerEquipment.name.toLowerCase().includes(storeSearchKeyword.value.toLowerCase())
)
})
// //
const handleSearch = () => { const handleSearch = () => {
// //
} }
//
const showShorePowerHistory = (shorepower: ShipItem) => {
currentShorePower.value = shorepower
shorePowerHistoryVisible.value = true
}
//
const showShipHistory = (ship: ShipItem) => {
currentShip.value = ship
shipHistoryVisible.value = true
}
//
const handleShorePowerHistorySearch = (params: any) => {
console.log('岸电箱历史记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('岸电箱历史记录搜索完成')
}, 500)
}
//
const handleShipHistorySearch = (params: any) => {
console.log('船舶历史记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('船舶历史记录搜索完成')
}, 500)
}
//
const handleShorePowerConnectionSearch = (params: any) => {
console.log('岸电箱连接船舶记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('岸电箱连接船舶记录搜索完成')
}, 500)
}
//
const handleShipShorePowerConnectionSearch = (params: any) => {
console.log('船舶连接岸电箱记录搜索参数:', params)
//
//
setTimeout(() => {
console.log('船舶连接岸电箱记录搜索完成')
}, 500)
}
// //
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'switch-ship', ship: ShipItem): void; (e: 'switch-ship', ship: ShipItem): void;
(e: 'item-click', ship: ShipItem): void; // handleSelectItem(ship)
}>() }>()
// //
const handleSwitch = (ship: ShipItem) => { const handleSwitch = (ship: ShipItem, type: string) => {
// console.log(ship) // console.log(ship)
show_type.value = type
emit('switch-ship', ship) emit('switch-ship', ship)
emit('item-click', ship) handleSelectItem(ship)
} }
const shorePowerStatusMap = { const shorePowerStatusMap = {
@ -232,6 +520,14 @@ const getStatusClass = (status: string) => {
return 'status-default' return 'status-default'
} }
} }
onMounted(() => {
console.log(props.shipStatusData)
})
onMounted(() => {
console.log(props.shorePowerStatusData)
})
</script> </script>
<style scoped> <style scoped>

303
public/map/components/ShipHistoryDialog.vue

@ -0,0 +1,303 @@
<template>
<el-dialog v-model="visible" title="船舶历史记录" width="1200px" :before-close="handleClose">
<el-tabs v-model="activeTab">
<!-- 船舶靠泊历史记录 tab -->
<el-tab-pane label="船舶靠泊历史记录" name="shipBerthing">
<!-- 搜索和筛选区域 -->
<div class="filter-container">
<el-form :model="berthingQueryParams" label-width="80px" inline>
<el-form-item label="关键字">
<el-input v-model="berthingQueryParams.keyword" placeholder="请输入关键字搜索" clearable @keyup.enter="handleBerthingSearch" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="berthingDateRange" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleBerthingDateChange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleBerthingSearch">搜索</el-button>
<el-button @click="resetBerthingQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<el-table :data="berthingTableData" border stripe style="width: 100%" v-loading="berthingLoading">
<el-table-column v-for="column in berthingColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:width="column.width" :formatter="column.formatter" />
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination v-model:current-page="berthingQueryParams.pageNo" v-model:page-size="berthingQueryParams.pageSize"
:page-sizes="[10, 20, 50, 100]" :total="berthingTotal" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleBerthingSizeChange" @current-change="handleBerthingCurrentChange" />
</div>
</el-tab-pane>
<!-- 船舶连接岸电箱历史记录 tab -->
<el-tab-pane label="船舶连接岸电箱历史记录" name="shorePowerConnection">
<!-- 搜索和筛选区域 -->
<div class="filter-container">
<el-form :model="shorePowerConnectionQueryParams" label-width="80px" inline>
<el-form-item label="关键字">
<el-input v-model="shorePowerConnectionQueryParams.keyword" placeholder="请输入关键字搜索" clearable
@keyup.enter="handleShorePowerConnectionSearch" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="shorePowerConnectionDateRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD"
@change="handleShorePowerConnectionDateChange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleShorePowerConnectionSearch">搜索</el-button>
<el-button @click="resetShorePowerConnectionQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<el-table :data="shorePowerConnectionTableData" border stripe style="width: 100%" v-loading="shorePowerConnectionLoading">
<el-table-column v-for="column in shorePowerConnectionColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:width="column.width" :formatter="column.formatter" />
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination v-model:current-page="shorePowerConnectionQueryParams.pageNo" v-model:page-size="shorePowerConnectionQueryParams.pageSize"
:page-sizes="[10, 20, 50, 100]" :total="shorePowerConnectionTotal" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleShorePowerConnectionSizeChange" @current-change="handleShorePowerConnectionCurrentChange" />
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import {
ElDialog,
ElTabs,
ElTabPane,
ElForm,
ElFormItem,
ElInput,
ElDatePicker,
ElButton,
ElTable,
ElTableColumn,
ElPagination
} from 'element-plus'
//
interface Props {
modelValue: boolean
berthingData?: any[]
shorePowerConnectionData?: any[]
}
//
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'berthing-search', params: any): void
(e: 'shore-power-connection-search', params: any): void
}>()
//
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
berthingData: () => [],
shorePowerConnectionData: () => []
})
//
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const activeTab = ref('shipBerthing')
//
const berthingQueryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const berthingDateRange = ref<[string, string]>(['', ''])
const berthingLoading = ref(false)
const berthingTotal = ref(0)
//
const berthingTableData = computed(() => {
//
// 使
return props.berthingData?.slice(
(berthingQueryParams.value.pageNo - 1) * berthingQueryParams.value.pageSize,
berthingQueryParams.value.pageNo * berthingQueryParams.value.pageSize
) || []
})
//
const berthingColumns = ref([
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'time', label: '时间', width: 180 },
{ prop: 'operation', label: '操作类型' },
{ prop: 'berthPosition', label: '泊位位置' },
{ prop: 'duration', label: '停泊时长(小时)' },
{ prop: 'powerConsumption', label: '用电量(kWh)' }
])
//
const shorePowerConnectionQueryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const shorePowerConnectionDateRange = ref<[string, string]>(['', ''])
const shorePowerConnectionLoading = ref(false)
const shorePowerConnectionTotal = ref(0)
//
const shorePowerConnectionTableData = computed(() => {
//
// 使
return props.shorePowerConnectionData?.slice(
(shorePowerConnectionQueryParams.value.pageNo - 1) * shorePowerConnectionQueryParams.value.pageSize,
shorePowerConnectionQueryParams.value.pageNo * shorePowerConnectionQueryParams.value.pageSize
) || []
})
//
const shorePowerConnectionColumns = ref([
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'shorePowerName', label: '岸电箱名称', width: 150 },
{ prop: 'time', label: '连接时间', width: 180 },
{ prop: 'duration', label: '连接时长(小时)', width: 120 },
{ prop: 'powerConsumption', label: '用电量(kWh)', width: 120 },
{ prop: 'status', label: '状态', width: 100 }
])
//
const handleClose = () => {
visible.value = false
}
//
const handleBerthingSearch = () => {
//
emit('berthing-search', { ...berthingQueryParams.value })
}
const resetBerthingQuery = () => {
berthingQueryParams.value.keyword = ''
berthingQueryParams.value.startTime = ''
berthingQueryParams.value.endTime = ''
berthingDateRange.value = ['', '']
berthingQueryParams.value.pageNo = 1
handleBerthingSearch()
}
const handleBerthingDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
berthingQueryParams.value.startTime = val[0]
berthingQueryParams.value.endTime = val[1]
} else {
berthingQueryParams.value.startTime = ''
berthingQueryParams.value.endTime = ''
}
}
const handleBerthingSizeChange = (val: number) => {
berthingQueryParams.value.pageSize = val
berthingQueryParams.value.pageNo = 1
handleBerthingSearch()
}
const handleBerthingCurrentChange = (val: number) => {
berthingQueryParams.value.pageNo = val
handleBerthingSearch()
}
//
const handleShorePowerConnectionSearch = () => {
//
emit('shore-power-connection-search', { ...shorePowerConnectionQueryParams.value })
}
const resetShorePowerConnectionQuery = () => {
shorePowerConnectionQueryParams.value.keyword = ''
shorePowerConnectionQueryParams.value.startTime = ''
shorePowerConnectionQueryParams.value.endTime = ''
shorePowerConnectionDateRange.value = ['', '']
shorePowerConnectionQueryParams.value.pageNo = 1
handleShorePowerConnectionSearch()
}
const handleShorePowerConnectionDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
shorePowerConnectionQueryParams.value.startTime = val[0]
shorePowerConnectionQueryParams.value.endTime = val[1]
} else {
shorePowerConnectionQueryParams.value.startTime = ''
shorePowerConnectionQueryParams.value.endTime = ''
}
}
const handleShorePowerConnectionSizeChange = (val: number) => {
shorePowerConnectionQueryParams.value.pageSize = val
shorePowerConnectionQueryParams.value.pageNo = 1
handleShorePowerConnectionSearch()
}
const handleShorePowerConnectionCurrentChange = (val: number) => {
shorePowerConnectionQueryParams.value.pageNo = val
handleShorePowerConnectionSearch()
}
//
watch(
() => props.berthingData,
(newData) => {
berthingTotal.value = newData?.length || 0
},
{ immediate: true }
)
watch(
() => props.shorePowerConnectionData,
(newData) => {
shorePowerConnectionTotal.value = newData?.length || 0
},
{ immediate: true }
)
</script>
<style scoped>
.filter-container {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

1282
public/map/components/ShipShorePower.vue

File diff suppressed because it is too large

304
public/map/components/ShorePowerHistoryDialog.vue

@ -0,0 +1,304 @@
<template>
<el-dialog v-model="visible" title="岸电箱历史记录" width="1200px" :before-close="handleClose">
<el-tabs v-model="activeTab">
<!-- 岸电箱历史记录 tab -->
<el-tab-pane label="岸电箱历史记录" name="shorePower">
<!-- 搜索和筛选区域 -->
<div class="filter-container">
<el-form :model="queryParams" label-width="80px" inline>
<el-form-item label="关键字">
<el-input v-model="queryParams.keyword" placeholder="请输入关键字搜索" clearable @keyup.enter="handleSearch" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="dateRange" type="daterange" range-separator="" start-placeholder="开始日期"
end-placeholder="结束日期" value-format="YYYY-MM-DD" @change="handleDateChange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<el-table :data="tableData" border stripe style="width: 100%" v-loading="loading">
<el-table-column v-for="column in tableColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:width="column.width" :formatter="column.formatter" />
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination v-model:current-page="queryParams.pageNo" v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange" @current-change="handleCurrentChange" />
</div>
</el-tab-pane>
<!-- 岸电箱连接船舶记录 tab -->
<el-tab-pane label="岸电箱连接船舶记录" name="shipConnection">
<!-- 搜索和筛选区域 -->
<div class="filter-container">
<el-form :model="shipConnectionQueryParams" label-width="80px" inline>
<el-form-item label="关键字">
<el-input v-model="shipConnectionQueryParams.keyword" placeholder="请输入关键字搜索" clearable
@keyup.enter="handleShipConnectionSearch" />
</el-form-item>
<el-form-item label="时间范围">
<el-date-picker v-model="shipConnectionDateRange" type="daterange" range-separator=""
start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD"
@change="handleShipConnectionDateChange" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleShipConnectionSearch">搜索</el-button>
<el-button @click="resetShipConnectionQuery">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 数据表格 -->
<el-table :data="shipConnectionTableData" border stripe style="width: 100%" v-loading="shipConnectionLoading">
<el-table-column v-for="column in shipConnectionColumns" :key="column.prop" :prop="column.prop" :label="column.label"
:width="column.width" :formatter="column.formatter" />
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination v-model:current-page="shipConnectionQueryParams.pageNo" v-model:page-size="shipConnectionQueryParams.pageSize"
:page-sizes="[10, 20, 50, 100]" :total="shipConnectionTotal" layout="total, sizes, prev, pager, next, jumper"
@size-change="handleShipConnectionSizeChange" @current-change="handleShipConnectionCurrentChange" />
</div>
</el-tab-pane>
</el-tabs>
<template #footer>
<span class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import {
ElDialog,
ElTabs,
ElTabPane,
ElForm,
ElFormItem,
ElInput,
ElDatePicker,
ElButton,
ElTable,
ElTableColumn,
ElPagination
} from 'element-plus'
//
interface Props {
modelValue: boolean
data?: any[]
shipConnectionData?: any[]
}
//
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void
(e: 'search', params: any): void
(e: 'ship-connection-search', params: any): void
}>()
//
const props = withDefaults(defineProps<Props>(), {
modelValue: false,
data: () => [],
shipConnectionData: () => []
})
//
const visible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val)
})
const activeTab = ref('shorePower')
//
const queryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const dateRange = ref<[string, string]>(['', ''])
const loading = ref(false)
const total = ref(0)
//
const tableData = computed(() => {
//
// 使
return props.data?.slice(
(queryParams.value.pageNo - 1) * queryParams.value.pageSize,
queryParams.value.pageNo * queryParams.value.pageSize
) || []
})
//
const tableColumns = ref([
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'time', label: '时间', width: 180 },
{ prop: 'operation', label: '操作类型' },
{ prop: 'voltage', label: '电压(V)' },
{ prop: 'current', label: '电流(A)' },
{ prop: 'power', label: '功率(kW)' },
{ prop: 'energy', label: '用电量(kWh)' }
])
//
const shipConnectionQueryParams = ref({
keyword: '',
startTime: '',
endTime: '',
pageNo: 1,
pageSize: 10
})
const shipConnectionDateRange = ref<[string, string]>(['', ''])
const shipConnectionLoading = ref(false)
const shipConnectionTotal = ref(0)
//
const shipConnectionTableData = computed(() => {
//
// 使
return props.shipConnectionData?.slice(
(shipConnectionQueryParams.value.pageNo - 1) * shipConnectionQueryParams.value.pageSize,
shipConnectionQueryParams.value.pageNo * shipConnectionQueryParams.value.pageSize
) || []
})
//
const shipConnectionColumns = ref([
{ prop: 'id', label: '记录ID', width: 80 },
{ prop: 'shipName', label: '船舶名称', width: 150 },
{ prop: 'time', label: '连接时间', width: 180 },
{ prop: 'duration', label: '连接时长(小时)', width: 120 },
{ prop: 'powerConsumption', label: '用电量(kWh)', width: 120 },
{ prop: 'status', label: '状态', width: 100 }
])
//
const handleClose = () => {
visible.value = false
}
//
const handleSearch = () => {
//
emit('search', { ...queryParams.value })
}
const resetQuery = () => {
queryParams.value.keyword = ''
queryParams.value.startTime = ''
queryParams.value.endTime = ''
dateRange.value = ['', '']
queryParams.value.pageNo = 1
handleSearch()
}
const handleDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
queryParams.value.startTime = val[0]
queryParams.value.endTime = val[1]
} else {
queryParams.value.startTime = ''
queryParams.value.endTime = ''
}
}
const handleSizeChange = (val: number) => {
queryParams.value.pageSize = val
queryParams.value.pageNo = 1
handleSearch()
}
const handleCurrentChange = (val: number) => {
queryParams.value.pageNo = val
handleSearch()
}
//
const handleShipConnectionSearch = () => {
//
emit('ship-connection-search', { ...shipConnectionQueryParams.value })
}
const resetShipConnectionQuery = () => {
shipConnectionQueryParams.value.keyword = ''
shipConnectionQueryParams.value.startTime = ''
shipConnectionQueryParams.value.endTime = ''
shipConnectionDateRange.value = ['', '']
shipConnectionQueryParams.value.pageNo = 1
handleShipConnectionSearch()
}
const handleShipConnectionDateChange = (val: [string, string] | null) => {
if (val && val[0] && val[1]) {
shipConnectionQueryParams.value.startTime = val[0]
shipConnectionQueryParams.value.endTime = val[1]
} else {
shipConnectionQueryParams.value.startTime = ''
shipConnectionQueryParams.value.endTime = ''
}
}
const handleShipConnectionSizeChange = (val: number) => {
shipConnectionQueryParams.value.pageSize = val
shipConnectionQueryParams.value.pageNo = 1
handleShipConnectionSearch()
}
const handleShipConnectionCurrentChange = (val: number) => {
shipConnectionQueryParams.value.pageNo = val
handleShipConnectionSearch()
}
//
watch(
() => props.data,
(newData) => {
total.value = newData?.length || 0
},
{ immediate: true }
)
watch(
() => props.shipConnectionData,
(newData) => {
shipConnectionTotal.value = newData?.length || 0
},
{ immediate: true }
)
</script>
<style scoped>
.filter-container {
margin-bottom: 20px;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

570
public/map/components/ShorePowerUsage.vue

@ -2,52 +2,80 @@
<div class="shore-power-usage"> <div class="shore-power-usage">
<!-- Average Mode --> <!-- Average Mode -->
<template v-if="displayMode === 'average'"> <template v-if="displayMode === 'average'">
<div class="left" style="width: 40%;"> <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" <div v-for="card in cards.slice(0, 3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)"> @click="handleSelectCard(card.id)">
<div class="card-title"> <div style="display: flex; align-items: center; justify-content: space-between;">
<div class="vertical-line"></div> <div class="card-title">
<img src="@/assets/svgs/data.svg" class="title-icon" /> <div class="vertical-line"></div>
<span class="title-text">{{ card.title }}</span> <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 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>
<div class="card-content"> <div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid"> <div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ totalPower }}</div> <div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div> <div class="overview-label">累计用电千瓦时</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div> <div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div> <div class="overview-label">减少燃油</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div> <div class="overview-label">减少二氧化碳排放千克</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div> <div class="overview-label">减少PM2.5排放千克</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div> <div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div> <div class="overview-label">减少氮氧化物千克</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div> <div class="overview-label">减少二氧化硫千克</div>
</div> </div>
</div> </div>
<BarChartMinute v-else :chart-data="commonChartData" :title="card.title" :color="card.color" /> <WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div> </div>
</div> </div>
</div> </div>
<div class="right" style="width: 40%;"> <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" <div v-for="card in cards.slice(3)" :key="card.id" class="card digital-twin-card--deep-blue"
@click="handleSelectCard(card.id)"> @click="handleSelectCard(card.id)">
<div class="card-title"> <div style="display: flex; align-items: center; justify-content: space-between;">
<div class="vertical-line"></div> <div class="card-title">
<img src="@/assets/svgs/data.svg" class="title-icon" /> <div class="vertical-line"></div>
<span class="title-text">{{ card.title }}</span> <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 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>
<div class="card-content"> <div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid"> <div v-if="card.type === 'overview'" class="overview-grid">
@ -76,7 +104,7 @@
<div class="overview-label">减少二氧化硫千克</div> <div class="overview-label">减少二氧化硫千克</div>
</div> </div>
</div> </div>
<BarChartMinute v-else :chart-data="commonChartData" :title="card.title" :color="card.color" /> <WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div> </div>
</div> </div>
</div> </div>
@ -87,78 +115,107 @@
<div class="big"> <div class="big">
<div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id" <div v-for="card in cards.filter(c => c.id === selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)"> class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)">
<div class="card-title"> <div style="display: flex; align-items: center; justify-content: space-between;">
<div class="vertical-line"></div> <div class="card-title">
<img src="@/assets/svgs/data.svg" class="title-icon" /> <div class="vertical-line"></div>
<span class="title-text">{{ card.title }}</span> <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 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>
<div class="card-content"> <div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid"> <div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ totalPower }}</div> <div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div> <div class="overview-label">累计用电(千瓦时)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div> <div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div> <div class="overview-label">减少燃油()</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div> <div class="overview-label">减少二氧化碳排放(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div> <div class="overview-label">减少PM2.5排放(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div> <div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div> <div class="overview-label">减少氮氧化物(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div> <div class="overview-label">减少二氧化硫(千克)</div>
</div> </div>
</div> </div>
<BarChartMinute v-else :chart-data="commonChartData" :title="card.title" :color="card.color" /> <WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color"
:magnify-mode="true" />
</div> </div>
</div> </div>
</div> </div>
<div class="one-row"> <div class="one-row">
<div v-for="card in cards.filter(c => c.id !== selectedCard)" :key="card.id" <div v-for="card in cards.filter(c => c.id !== selectedCard)" :key="card.id"
class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)"> class="card digital-twin-card--deep-blue" @click="handleSelectCard(card.id)">
<div class="card-title"> <div style="display: flex; align-items: center; justify-content: space-between;">
<div class="vertical-line"></div> <div class="card-title">
<img src="@/assets/svgs/data.svg" class="title-icon" /> <div class="vertical-line"></div>
<span class="title-text">{{ card.title }}</span> <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 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>
<div class="card-content"> <div class="card-content">
<div v-if="card.type === 'overview'" class="overview-grid"> <div v-if="card.type === 'overview'" class="overview-grid">
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ totalPower }}</div> <div class="overview-value">{{ chartData[timeRange].totalPower }}</div>
<div class="overview-label">累计用电千瓦时</div> <div class="overview-label">累计用电(千瓦时)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ fuelReduction }}</div> <div class="overview-value">{{ chartData[timeRange].fuel }}</div>
<div class="overview-label">减少燃油</div> <div class="overview-label">减少燃油()</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ co2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].co2 }}</div>
<div class="overview-label">减少二氧化碳排放千克</div> <div class="overview-label">减少二氧化碳排放(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ pm25Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].pm25 }}</div>
<div class="overview-label">减少PM2.5排放千克</div> <div class="overview-label">减少PM2.5排放(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ noxReduction }}</div> <div class="overview-value">{{ chartData[timeRange].nox }}</div>
<div class="overview-label">减少氮氧化物千克</div> <div class="overview-label">减少氮氧化物(千克)</div>
</div> </div>
<div class="overview-item"> <div class="overview-item">
<div class="overview-value">{{ so2Reduction }}</div> <div class="overview-value">{{ chartData[timeRange].so2 }}</div>
<div class="overview-label">减少二氧化硫千克</div> <div class="overview-label">减少二氧化硫(千克)</div>
</div> </div>
</div> </div>
<BarChartMinute v-else :chart-data="commonChartData" :title="card.title" :color="card.color" /> <WaveLineChart v-else :chart-data="getChartData(card.id)" :title="card.title" :color="card.color" />
</div> </div>
</div> </div>
</div> </div>
@ -168,7 +225,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue' import { ref, onMounted, onBeforeUnmount } from 'vue'
import BarChartMinute from './charts/BarChartMinute.vue' import WaveLineChart from './charts/WaveLineChart.vue'
import { MapApi } from "@/api/shorepower/map";
import dayjs from 'dayjs';
// //
interface ChartDataItem { interface ChartDataItem {
@ -182,23 +241,178 @@ interface CardInfo {
type: 'overview' | 'chart'; type: 'overview' | 'chart';
unit?: string; unit?: string;
color?: string; color?: string;
value: string;
}
interface ShorePowerUsageData {
totalPower: number; //
fuelReduction: number; //
co2Reduction: number; //
pm25Reduction: number; // PM2.5
noxReduction: number; //
so2Reduction: number; //
}
export interface MeasureDataRealtimeRespVO {
/**
* 创建时间
*/
createTime: Date;
/**
* 设备编号code
*/
deviceCode: string;
/**
* 设备编号
*/
deviceId: number;
/**
* 设备名称
*/
deviceName: string;
/**
* 设备状态
*/
deviceStatus: number;
/**
* 编号
*/
id: number;
/**
* 增量费用
*/
incrementCost?: number;
/**
* 增量值
*/
incrementValue: number;
/**
* 采集时间
*/
measureTime: Date;
/**
* 采集值
*/
measureValue: number;
} }
interface Props { export interface deviceData {
totalPower: number; [key: string]: MeasureDataRealtimeRespVO
fuelReduction: number;
co2Reduction: number;
pm25Reduction: number;
noxReduction: number;
so2Reduction: number;
} }
const props = defineProps<Props>() // const props = defineProps<Props>()
const displayMode = ref<'average' | 'magnify'>('average')
const totalPower = ref<string>('0')
const fuelReduction = ref<string>('0')
const co2Reduction = ref<string>('0')
const pm25Reduction = ref<string>('0')
const noxReduction = ref<string>('0')
const so2Reduction = ref<string>('0')
const displayMode = ref<'average' | 'magnify'>('magnify')
const selectedCard = ref<string>('overview') const selectedCard = ref<string>('overview')
// //
const commonChartData = ref<ChartDataItem[]>([]) const commonChartData = 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,
},
'week': {
'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,
},
'quarter': {
'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 getChartData = (cardId: string): ChartDataItem[] => {
switch (cardId) {
case 'overview':
return totalPowerChartData.value
case 'fuel':
return fuelReductionChartData.value
case 'co2':
return co2ReductionChartData.value
case 'pm25':
return pm25ReductionChartData.value
case 'nox':
return noxReductionChartData.value
case 'so2':
return so2ReductionChartData.value
default:
return commonChartData.value
}
}
//
const timeRange = ref<'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year'>('realtime')
//
const timeRangeOptions = [
{ value: 'realtime', label: '实时' },
{ value: 'day', label: '日' },
{ value: 'week', label: '周' },
{ value: 'month', label: '月' },
// { value: 'quarter', label: '' },
{ value: 'year', label: '年' }
]
//
const handleTimeRangeChange = (range: 'realtime' | 'day' | 'week' | 'month' | 'quarter' | 'year') => {
timeRange.value = range
//
}
// //
const cards = ref<CardInfo[]>([ const cards = ref<CardInfo[]>([
{ {
@ -211,35 +425,40 @@ const cards = ref<CardInfo[]>([
title: '减少燃油(吨)', title: '减少燃油(吨)',
type: 'chart', type: 'chart',
unit: '吨', unit: '吨',
color: '#4CAF50' // 绿 - color: '#4CAF50', // 绿 -
value: 'fuel'
}, },
{ {
id: 'co2', id: 'co2',
title: '减少CO₂排放(千克)', title: '减少CO₂排放(千克)',
type: 'chart', type: 'chart',
unit: '千克', unit: '千克',
color: '#2E7D32' // 绿/绿 - 绿 color: '#2E7D32', // 绿/绿 - 绿
value: 'co2'
}, },
{ {
id: 'pm25', id: 'pm25',
title: '减少PM2.5排放(千克)', title: '减少PM2.5排放(千克)',
type: 'chart', type: 'chart',
unit: '千克', unit: '千克',
color: '#9E9E9E' // - color: '#9E9E9E', // -
value: 'pm25'
}, },
{ {
id: 'nox', id: 'nox',
title: '减少NOₓ排放(千克)', title: '减少NOₓ排放(千克)',
type: 'chart', type: 'chart',
unit: '千克', unit: '千克',
color: '#FF9800' // - color: '#FF9800', // -
value: 'nox'
}, },
{ {
id: 'so2', id: 'so2',
title: '减少SO₂排放(千克)', title: '减少SO₂排放(千克)',
type: 'chart', type: 'chart',
unit: '千克', unit: '千克',
color: '#FFEB3B' // - color: '#FFEB3B', // -
value: 'so2'
} }
]) ])
@ -271,7 +490,7 @@ const generateInitialData = () => {
} }
// //
let timer: number | null = null let timer: ReturnType<typeof setInterval>;
// //
const appendNewData = () => { const appendNewData = () => {
@ -282,10 +501,152 @@ const appendNewData = () => {
}) })
} }
const handleGetRealTimeAllData = async () => {
try {
const res: deviceData = await MapApi.getRealtimeAllData({})
const ids = Object.values(res).map(item => item.id);
console.log(ids);
const params = {
ids: ids.join(','),
// year: new Date().getFullYear()
}
const yearDataRes: deviceData = await MapApi.getYearDataByIdList(params);
const weekDataRes: deviceData = await MapApi.getWeekDataByIdList(params);
const monthDataRes: deviceData = await MapApi.getMonthDataByIdList(params);
const dayDataRes: deviceData = await MapApi.getDayDataByIdList(params);
// const quarterDataRes: deviceData = await MapApi.getQuarterDataByIdList(params);
const realTimeSum = Object.values(res).reduce((acc, item) => acc + item.measureValue, 0);
const daySum = realTimeSum - Object.values(dayDataRes).reduce((acc, item) => acc + item.measureValue, 0);
const weekSum = realTimeSum - Object.values(weekDataRes).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.incrementValue, 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.week = {
totalPower: Number(weekSum.toFixed(2)),
fuel: Number((weekSum * 0.22 / 1).toFixed(2)), //
co2: Number((weekSum * 670 / 1000).toFixed(2)), //
pm25: Number((weekSum * 1.46 / 1000).toFixed(2)), //
nox: Number((weekSum * 18.1 / 1000).toFixed(2)), //
so2: Number((weekSum * 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)
//
commonChartData.value.push({ name: timestamp, value: totalPowerValue })
commonChartData.value = commonChartData.value.slice(-maxDataPoints)
} catch (error) {
console.error(error)
}
}
const handleGetAllDeviceDataByTimeRange = async () => {
try {
//
const nowTimestamp = dayjs().valueOf().toString();
//
const twoDaysAgoTimestamp = dayjs().subtract(2, 'day').valueOf().toString();
const params = {
start: twoDaysAgoTimestamp,
end: nowTimestamp,
timeType: '3'
}
const res = await MapApi.getByStartAndEndTimeAndTimeType(params)
console.log(res)
} catch (error) {
console.error(error)
}
}
// //
onMounted(() => { onMounted(() => {
commonChartData.value = generateInitialData() handleGetAllDeviceDataByTimeRange()
timer = window.setInterval(appendNewData, 1000) // commonChartData.value = generateInitialData()
// timer = window.setInterval(appendNewData, 1000)
let i = 1
handleGetRealTimeAllData()
timer = setInterval(() => {
handleGetRealTimeAllData(i)
i++
}, 5000)
}) })
// //
@ -303,6 +664,17 @@ onBeforeUnmount(() => {
gap: 10px; gap: 10px;
} }
.average {
.overview-value {
font-size: 36px;
}
.overview-label {
font-size: 18px;
}
}
.magnify { .magnify {
width: 100%; width: 100%;
padding: 12px; padding: 12px;
@ -324,11 +696,11 @@ onBeforeUnmount(() => {
} }
.overview-value { .overview-value {
font-size: 32px; font-size: 48px;
} }
.overview-label { .overview-label {
font-size: 24px; font-size: 28px;
} }
} }
@ -337,6 +709,10 @@ onBeforeUnmount(() => {
display: flex; display: flex;
gap: 8px; gap: 8px;
.card .card-title .title-text {
font-size: 14px;
}
.card { .card {
flex: 1; flex: 1;
} }
@ -348,6 +724,56 @@ onBeforeUnmount(() => {
cursor: pointer; cursor: pointer;
} }
.time-range-selector {
display: flex;
gap: 8px;
}
.show-value {
font-size: 18px;
font-weight: 600;
font-weight: bold;
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 8px;
color: #FFF;
}
.show-value-label {
font-size: 12px;
font-weight: 600;
font-weight: bold;
}
.show-value-value {
font-size: 18px;
font-weight: 600;
font-weight: bold;
}
.time-range-btn {
padding: 4px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
background: transparent;
color: rgba(255, 255, 255, 0.7);
font-size: 12px;
cursor: pointer;
transition: all 0.3s ease;
}
.time-range-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
.time-range-btn.active {
background: rgba(76, 175, 80, 0.8);
border-color: #4CAF50;
color: #fff;
}
.right { .right {
/* gap: 0 */ /* gap: 0 */
} }

141
public/map/components/cesiumMap.vue

@ -124,7 +124,27 @@ onMounted(async () => {
// //
// 1000.0 // 1000.0
controller.minimumZoomDistance = 1000; const MIN_HEIGHT = 1200;
controller.minimumZoomDistance = MIN_HEIGHT - 100;
const canvas = viewer.scene.canvas;
canvas.addEventListener(
"wheel",
(e) => {
const height = viewer.camera.positionCartographic.height;
//
if (height <= MIN_HEIGHT && e.deltaY < 0) {
e.preventDefault();
e.stopPropagation();
}
},
{
passive: false,
capture: true
}
);
// viewer.scene.screenSpaceCameraController.minimumZoomDistance = 200; // viewer.scene.screenSpaceCameraController.minimumZoomDistance = 200;
// 2: // 2:
@ -220,6 +240,9 @@ onMounted(async () => {
console.log('dataObj.icon', dataObj.icon) console.log('dataObj.icon', dataObj.icon)
if (dataObj.icon === 'ship_green' || dataObj.icon === 'ship_red') { if (dataObj.icon === 'ship_green' || dataObj.icon === 'ship_red') {
const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5) const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
if (!itemShipInfo) {
return;
}
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 15); const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 15);
const statusPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1); const statusPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35); const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 35);
@ -255,7 +278,7 @@ onMounted(async () => {
position: labelPosition, // 10 position: labelPosition, // 10
label: { label: {
text: itemShipInfo.shipBasicInfo.name || `Marker-${item.id || index}`, text: itemShipInfo.shipBasicInfo.name || `Marker-${item.id || index}`,
font: '16px sans-serif', font: '20px sans-serif',
fillColor: Cesium.Color.LIME, fillColor: Cesium.Color.LIME,
outlineColor: Cesium.Color.BLACK, outlineColor: Cesium.Color.BLACK,
outlineWidth: 2, outlineWidth: 2,
@ -264,23 +287,39 @@ onMounted(async () => {
disableDepthTestDistance: Number.POSITIVE_INFINITY // disableDepthTestDistance: Number.POSITIVE_INFINITY //
} }
}); });
// if (dataObj.icon === 'ship_red') { //
const overlayBillboard = viewer.entities.add({ // 50%25%25%线
position: statusPosition, const rand = Math.random();
billboard: { let statusImage = null;
image: '/img/故障.png', if (rand < 0.8) {
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 50%
verticalOrigin: Cesium.VerticalOrigin.CENTER, statusImage = null;
disableDepthTestDistance: Number.POSITIVE_INFINITY, } else if (rand < 0.9) {
scale: new Cesium.CallbackProperty(function (time, result) { // 25%
// t statusImage = '/img/故障.png';
const t = Cesium.JulianDate.toDate(time).getTime() / 1000; } else {
const pulse = 0.4 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2) // 25%线
return pulse; statusImage = '/img/离线.png';
}, false) }
}
}); //
// } if (statusImage) {
const overlayBillboard = viewer.entities.add({
position: statusPosition,
billboard: {
image: statusImage,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scale: new Cesium.CallbackProperty(function (time, result) {
// t
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.4 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2)
return pulse;
}, false)
}
});
}
} }
if (dataObj.type === 'text') { if (dataObj.type === 'text') {
@ -289,7 +328,7 @@ onMounted(async () => {
position: labelPosition, // 10 position: labelPosition, // 10
label: { label: {
text: dataObj.name || `Marker-${item.id || index}`, text: dataObj.name || `Marker-${item.id || index}`,
font: '12px sans-serif', font: '20px sans-serif',
fillColor: Cesium.Color.WHITE, fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK, outlineColor: Cesium.Color.BLACK,
outlineWidth: 2, outlineWidth: 2,
@ -300,6 +339,7 @@ onMounted(async () => {
}); });
} }
if (dataObj.type === 'icon' && (dataObj.icon === 'interface_blue' || dataObj.icon === 'interface_red')) { if (dataObj.type === 'icon' && (dataObj.icon === 'interface_blue' || dataObj.icon === 'interface_red')) {
const itemShipInfo = shipData.find(shipItem => (shipItem.shorePower.id === item.parentId) && item.type === 5)
const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1); const position = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 1);
const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 5); const labelPosition = Cesium.Cartesian3.fromDegrees(wgsLon, wgsLat, 5);
// //
@ -333,6 +373,7 @@ onMounted(async () => {
// //
itemWithModel.modelInstance = electricalBoxModel; itemWithModel.modelInstance = electricalBoxModel;
itemWithModel.modelType = 'electrical_box'; itemWithModel.modelType = 'electrical_box';
itemWithModel = { ...itemWithModel, ...itemShipInfo };
viewer.entities.add({ viewer.entities.add({
// pickable: false, // pickable: false,
@ -343,7 +384,7 @@ onMounted(async () => {
}, },
label: { label: {
text: '岸电箱' || `Marker-${item.id || index}`, text: '岸电箱' || `Marker-${item.id || index}`,
font: '10px sans-serif', font: '16px sans-serif',
fillColor: Cesium.Color.WHITE, fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK, outlineColor: Cesium.Color.BLACK,
outlineWidth: 2, outlineWidth: 2,
@ -353,26 +394,44 @@ onMounted(async () => {
} }
}); });
const overlayBillboard = viewer.entities.add({ //
position: position, // 50%25%25%线
// pickable: false, const rand = Math.random();
properties: { let statusImage = null;
modelType: 'electrical_box', if (rand < 0.5) {
data: itemWithModel // 50%
}, statusImage = null;
billboard: { } else if (rand < 0.75) {
image: '/img/故障.png', // 25%
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, statusImage = '/img/故障.png';
verticalOrigin: Cesium.VerticalOrigin.CENTER, } else {
disableDepthTestDistance: Number.POSITIVE_INFINITY, // 25%线
scale: new Cesium.CallbackProperty(function (time, result) { statusImage = '/img/离线.png';
// t }
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.2 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2) //
return pulse; if (statusImage) {
}, false) const overlayBillboard = viewer.entities.add({
} position: position,
}); // pickable: false,
properties: {
modelType: 'electrical_box',
data: itemWithModel
},
billboard: {
image: statusImage,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
scale: new Cesium.CallbackProperty(function (time, result) {
// t
const t = Cesium.JulianDate.toDate(time).getTime() / 1000;
const pulse = 0.2 + Math.sin(t * 4) * 0.1; // (0.6, ±0.2)
return pulse;
}, false)
}
});
}
} }
// //

18
public/map/components/charts/BarChartMinute.vue

@ -11,12 +11,14 @@ interface Props {
chartData?: Array<{ name: string; value: number }> chartData?: Array<{ name: string; value: number }>
title?: string title?: string
color?: string color?: string
magnifyMode?: boolean
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
chartData: () => [], chartData: () => [],
title: '', title: '',
color: '#1296DB' // 使 color: '#1296DB', // 使
magnifyMode: false
}) })
// //
@ -59,7 +61,7 @@ const updateChart = () => {
grid: { grid: {
left: '1%', left: '1%',
right: '1%', right: '1%',
top: '5%', top: '10%',
bottom: '1%', bottom: '1%',
containLabel: true containLabel: true
}, },
@ -108,7 +110,7 @@ const updateChart = () => {
{ {
type: 'bar', type: 'bar',
data: props.chartData.map(item => item.value), data: props.chartData.map(item => item.value),
barWidth: 1, // 1px barWidth: props.magnifyMode ? 100 : 25, //
itemStyle: { itemStyle: {
color: { color: {
type: 'linear', type: 'linear',
@ -129,6 +131,16 @@ const updateChart = () => {
}, },
borderRadius: [1, 1, 0, 0] borderRadius: [1, 1, 0, 0]
}, },
label: {
show: true,
position: 'top',
color: 'rgba(255, 255, 255, 0.7)',
fontSize: props.magnifyMode ? 32 : 8,
formatter: '+{c}',
labelLayout: {
hideOverlap: true
}
},
emphasis: { emphasis: {
itemStyle: { itemStyle: {
color: props.color // 使 color: props.color // 使

240
public/map/components/charts/WaveLineChart.vue

@ -0,0 +1,240 @@
<template>
<div ref="chartContainer" class="wave-line-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; value: number }>
title?: string
color?: string
magnifyMode?: boolean
}
const props = withDefaults(defineProps<Props>(), {
chartData: () => [],
title: '',
color: '#1296db',
magnifyMode: false
})
//
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 waveData = props.chartData.map(item => {
return {
...item,
value: item.value
}
})
const option = {
title: {
// text: props.title,
textStyle: {
color: '#fff',
fontSize: props.magnifyMode ? 24 : 14
},
left: 'center'
},
tooltip: {
trigger: 'axis',
backgroundColor: 'rgba(0, 0, 0, 0.7)',
borderColor: 'rgba(30, 120, 255, 0.4)',
borderWidth: 1,
textStyle: {
color: '#fff',
fontSize: props.magnifyMode ? 20 : 12
}
},
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)',
fontSize: props.magnifyMode ? 18 : 10,
rotate: 0,
interval: Math.ceil(props.chartData.length / 12), //
formatter: (value: string) => {
//
if (value.includes(':')) {
const parts = value.split(':');
if (parts.length >= 2) {
// HH:mm:ss
if (parts[2] && parts[2] === '00') {
return parts[0] + ':' + parts[1];
}
// HH:mm
return parts[0] + ':' + parts[1];
}
}
return value;
},
margin: 10
}
},
yAxis: {
type: 'value',
// Y
min: (value: any) => {
if (props.chartData && props.chartData.length > 0) {
const minValue = props.chartData[0].value
//
// const range = value.max - minValue;
return minValue; // 0
}
return undefined;
},
axisLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.3)'
}
},
splitLine: {
lineStyle: {
color: 'rgba(255, 255, 255, 0.1)'
}
},
axisLabel: {
color: 'rgba(255, 255, 255, 0.7)',
fontSize: props.magnifyMode ? 18 : 10
}
},
series: [
{
data: waveData.map(item => item.value),
type: 'line',
smooth: true,
showSymbol: true,
symbol: 'circle',
symbolSize: props.magnifyMode ? 8 : 4,
lineStyle: {
color: props.color,
width: props.magnifyMode ? 4 : 2
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: props.color + '80' //
},
{
offset: 1,
color: props.color + '00' //
}
]
}
},
//
emphasis: {
focus: 'series'
},
//
label: {
show: true,
position: 'top',
color: '#fff',
fontSize: props.magnifyMode ? 16 : 10,
formatter: '{c}',
distance: props.magnifyMode ? 10 : 5
}
},
//
{
data: props.chartData.map(item => item.value),
type: 'line',
smooth: true,
showSymbol: false,
lineStyle: {
color: props.color + '60',
width: props.magnifyMode ? 2 : 1,
type: 'dashed'
},
//
label: {
show: false
}
}
],
grid: {
left: '1%',
right: '1%',
top: props.title ? (props.magnifyMode ? '10%' : '15%') : '5%',
bottom: '1%',
containLabel: true
}
}
chartInstance.setOption(option)
}
//
watch(
() => props.chartData,
() => {
updateChart()
},
{ deep: true }
)
//
const handleResize = () => {
if (chartInstance) {
chartInstance.resize()
}
}
//
onMounted(() => {
// DOM
setTimeout(() => {
initChart()
}, 200)
window.addEventListener('resize', handleResize)
})
//
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
})
</script>
<style scoped>
.wave-line-chart-container {
width: 100%;
height: 100%;
min-height: 0;
}
</style>

67
public/map/index.vue

@ -19,13 +19,13 @@
<!-- 港区概览 --> <!-- 港区概览 -->
<template v-if="activeHeadGroup === 0"> <template v-if="activeHeadGroup === 0">
<PortOverview :ship-status-data="shipStatusData" :selected-ship="selectedShip" @item-click="handleSwitch" /> <PortOverview :ship-status-data="shipStatusData" :shore-power-status-data="shorePowerStatusData"
@item-click="handleSwitch" />
</template> </template>
<!-- 港口岸电使用情况 --> <!-- 港口岸电使用情况 -->
<template v-if="activeHeadGroup === 1"> <template v-if="activeHeadGroup === 1">
<ShorePowerUsage :total-power="totalPower" :fuel-reduction="fuelReduction" :co2-reduction="co2Reduction" <ShorePowerUsage />
:pm25-reduction="pm25Reduction" :nox-reduction="noxReduction" :so2-reduction="so2Reduction" />
</template> </template>
<!-- 港口企业岸电使用 --> <!-- 港口企业岸电使用 -->
@ -35,8 +35,7 @@
<!-- 船舶岸电使用情况 --> <!-- 船舶岸电使用情况 -->
<template v-if="activeHeadGroup === 3"> <template v-if="activeHeadGroup === 3">
<ShipShorePower :berthing-ships="berthingShips" :shore-power-ships="shorePowerShips" <ShipShorePower :ship-data="shipStatusData" :selected-ship="selectedShip" @item-click="handleSwitch" />
:no-shore-power-ships="noShorePowerShips" :ship-data="shipData" />
</template> </template>
<template v-if="activeHeadGroup === 4"> <template v-if="activeHeadGroup === 4">
@ -141,12 +140,12 @@ const selectHeadGroup = async (value: number) => {
const currentTime = ref<string>(dayjs().format('YYYY-MM-DD HH:mm:ss')) const currentTime = ref<string>(dayjs().format('YYYY-MM-DD HH:mm:ss'))
// //
const totalPower = ref<number>(857525.45) // const totalPower = ref<number>(857525.45)
const fuelReduction = ref<number>(108.04) // const fuelReduction = ref<number>(108.04)
const co2Reduction = ref<number>(574542.06) // const co2Reduction = ref<number>(574542.06)
const pm25Reduction = ref<number>(1251.99) // const pm25Reduction = ref<number>(1251.99)
const noxReduction = ref<number>(15521.21) // const noxReduction = ref<number>(15521.21)
const so2Reduction = ref<number>(9004.03) // const so2Reduction = ref<number>(9004.03)
// 使 // 使
const berthingShips = ref<number>(13) const berthingShips = ref<number>(13)
@ -209,7 +208,7 @@ const handleOverviewClick = () => {
/* 切换至模型视角 */ /* 切换至模型视角 */
const handleSwitch = (item) => { const handleSwitch = (item) => {
console.log(item) console.log(item)
selectedShip.value = item // selectedShip.value = item
mapComponentRef.value?.switchModelView(item.modelInstance) mapComponentRef.value?.switchModelView(item.modelInstance)
} }
@ -477,6 +476,40 @@ const totalPowerDeviceId = computed(() => {
return [...new Set(ids)] return [...new Set(ids)]
}) })
//
const shorePowerStatusData = computed(() => {
if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) {
return []
}
//
return mapComponentRef.value.dataWithModels
.filter(item => item.modelType === 'electrical_box')
.map((item, index) => {
//
let status = ['正常'] //
try {
if (item.data && typeof item.data === 'string') {
const dataObj = JSON.parse(item.data)
//
// dataObjstatus使
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 shipStatusData = computed(() => { const shipStatusData = computed(() => {
if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) { if (!mapComponentRef.value || !mapComponentRef.value.dataWithModels) {
@ -995,7 +1028,7 @@ const shipStatusData = computed(() => {
.overview-grid { .overview-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 6px; gap: 2px;
padding: 2px; padding: 2px;
height: 100%; height: 100%;
} }
@ -1005,22 +1038,22 @@ const shipStatusData = computed(() => {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 4px; // padding: 4px;
background-color: rgba(0, 0, 0, 0.2); background-color: rgba(0, 0, 0, 0.2);
border-radius: 8px; border-radius: 8px;
} }
.overview-value { .overview-value {
font-size: 18px; font-size: 24px;
font-weight: bold; font-weight: bold;
color: #1296db; color: #1296db;
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5); text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
margin-bottom: 5px; margin-bottom: 0px;
} }
.overview-label { .overview-label {
text-align: center; text-align: center;
font-size: 12px; font-size: 14px;
color: #ccc; color: #ccc;
} }

83
src/api/shorepower/map/index.ts

@ -73,5 +73,86 @@ export const MapApi = {
url: `/energy/device/getDeviceStatusByIds?ids=${ids.join(',')}`, url: `/energy/device/getDeviceStatusByIds?ids=${ids.join(',')}`,
// params // params
}) })
} },
// 查询全部设备实时数据
getRealtimeAllData: async (params: any) => {
return await request.get({
url: `/energy/measure-data-realtime/getAll`,
params
})
},
// 查询多个设备实时数据
getRealtimeDataByIdList: async (params: any) => {
return await request.get({
url: `/energy/measure-data-realtime/getByIdList`,
params
})
},
// 查询多个设备年数据
getYearDataByIdList: async (params: any) => {
return await request.get({
url: `/energy/select/year/getByIdList`,
params
})
},
// 查询多个设备周数据
getWeekDataByIdList: async (params: any) => {
return await request.get({
url: `/energy/select/week/getByIdList`,
params
})
},
// 查询多个设备月数据
getMonthDataByIdList: async (params: any) => {
return await request.get({
url: `/energy/select/month/getByIdList`,
params
})
},
// 查询多个设备日数据
getDayDataByIdList: async (params: any) => {
return await request.get({
url: `/energy/select/day/getByIdList`,
params
})
},
// 查询所有船舶和岸电设备ID和名称列表
getBerthIdAndNameList: async () => {
return await request.get({
url: `/shorepower/berth/expand/selectIdAndNameList`,
})
},
getShorepowerIdAndNameList: async () => {
return await request.get({
url: `/shorepower/berth/expand/selectIdAndNameList`,
})
},
// 查询所有码头和名称列表
getDockIdAndNameList: async () => {
return await request.get({
url: `/shorepower/dock/expand/selectIdAndNameList`,
})
},
// 查询所有起运港和到达港和名称列表
getHarborDistrictIdAndNameList: async () => {
return await request.get({
url: `/shorepower/harbor-district/expand/selectIdAndNameList`,
})
},
getByStartAndEndTimeAndTimeType: async (params: any) => {
return await request.get({
url: `/energy/select/getByStartAndEndTimeAndTimeType`,
params
})
},
} }

4
src/plugins/elementPlus/index.ts

@ -1,10 +1,10 @@
import type { App } from 'vue' import type { App } from 'vue'
// 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题 // 需要全局引入一些组件,如ElScrollbar,不然一些下拉项样式有问题
import { ElLoading, ElScrollbar, ElButton } from 'element-plus' import { ElLoading, ElScrollbar, ElButton, ElDialog, ElForm, ElFormItem, ElInput, ElDatePicker, ElTable, ElTableColumn, ElPagination, ElCol, ElRow } from 'element-plus'
const plugins = [ElLoading] const plugins = [ElLoading]
const components = [ElScrollbar, ElButton] const components = [ElScrollbar, ElButton, ElDialog, ElForm, ElFormItem, ElInput, ElDatePicker, ElTable, ElTableColumn, ElPagination, ElCol, ElRow]
export const setupElementPlus = (app: App<Element>) => { export const setupElementPlus = (app: App<Element>) => {
plugins.forEach((plugin) => { plugins.forEach((plugin) => {

Loading…
Cancel
Save